이번 글에서는 4번 강의를 참조하여 API Request를 구성하는 방법에 대해 알아본다.
API Request의 구성 요소
- Base URL
- End Point
- Path Components (optional)
- Query parameters
전체적인 순서
우리는 Service, Request, Endpoint의 세 파일을 생성하였다.
각 파일에 있는 타입들은 Endpoint의 정보를 Request에서 사용하여 Request를 완성하고 다시 이를 받아 Service에서 통신을 하게 된다. 굳이 이 순서대로 만들 필요는 없다. 자연스럽게 필요할 때 정의하여 사용하면 된다. 하지만 이 틀은 앞으로도 유지되므로 순서를 따르면 좋을 거 같다.
먼저, RMService에서 execute라는 메서드를 만든다.
위에 서술한 대로 데이터가 준비되면 실제로 행동하는 역할을 한다. request와 컴플리션 핸들러로 Result를 다루는 것에 주목하자.
final class RMService {
public func execute(_ request: RMRequest,
completion: @escaping (Result<String, Error>) -> Void) {
}
}
RMRequest 구성
위에서 언급한 네 가지 요소를 만들어 주자. 클래스에 초기화도 같이 해줘야 하므로 아래와 같이 진행한다.
주목할 점은 pathComponents, queryParameters의 경우 따로 만들어줘서 사용해도 되지만 빈배열로 초기화해서 사용한다는 점이다.
final class RMRequest {
/// API Contants
private struct Constants {
static let baseUrl = "https://rickandmortyapi.com/api"
}
/// Desired endpoint
private let endpoint: RMEndpoint
/// Path components for API, if any
private let pathComponents: [String]
/// Query arguments for API, if any
private let queryParameters: [URLQueryItem]
public init(endpoint: RMEndpoint, pathComponents: [String] = [], queryParameters: [URLQueryItem] = []) {
self.endpoint = endpoint
self.pathComponents = pathComponents
self.queryParameters = queryParameters
}
}
URL 구성
안 중요한게 없지만 상당히 중요한 부분이다. 이 강의를 보기 전 URL을 구성할 때 이런 식으로 구성을 해본 적이 없는데 꽤나 도움이 됐다.
Computed Property를 사용하여 구현한 것이 특징으로 URL에 대한 기본적인 이해가 있어야 한다.
final class RMRequest {
...
private var urlString: String {
var string = Constants.baseUrl
string += "/"
string += endpoint.rawValue
// 설정되어 있는 pathComponents 안에 있는 값들을 차례로 더한다.
// url path이므로 /을 붙여준다.
if !pathComponents.isEmpty {
string += "/"
pathComponents.forEach {
string += "/\($0)"
}
}
if !queryParameters.isEmpty {
string += "?"
// 예시: name1=value1&name2=value2
// path와는 다르게 쿼리는 위와 같은 양식을 따른다.
// URLQueryItem에서 뽑아오려면 다음과 같이 compactMap이 적당한 구현이다.
let argumentString = queryParameters.compactMap {
guard let value = $0.value else { return nil}
return "\($0.name)=\(value)"
}.joined(separator: "&")
// 각 요소간에 &를 붙인다.
string += argumentString
}
return string
}
public var url: URL? {
return URL(string: urlString)
}
}
테스트
어떤 VC든 상관없이 가서 해당 request를 실행해 보자
let request = RMRequest(endpoint: .character,
pathComponents: ["1"],
queryParameters: [
RLQueryItem(name: "name", value: "rick"),
URLQueryItem(name: "status", value: "alive")
])
print(request.url)
잘 나오면 성공적으로 구현한 Request이다.
RmService의 execute 수정
아까 써놓은 걸 기억하는가? Request를 구성하고 이를 이용하여 Service에서 실행하는 게 정석이다. 방금은 하드 코딩하여 정확한 데이터를 우리가 입력해서 테스트했지만 실제로는 받아오는 값에 따라 상황에 맞게 변화해야 한다. 이를 해결하기 위한 것이 execute 메서드이다.
일단 안에 있는 내용을 작성하기 전에 전반적인 구조에 대해 짚고 넘어가자. 우리는 API 통신을 shared라는 객체가 담당하도록 할 것이다. (싱글톤)
먼저 shared를 만들어 두고 execute 메서드를 제네릭을 받아 처리하도록 하자.
세 가지 파라미터를 받는 걸 볼 수가 있는데 아까 보여준 execute에서 타입을 받는 expecting type을 사용하고 있다. 보면 url과 같은 정보를 가진 RMRequest로 목표 대상을 타게팅하고 컴플리션 핸들러과 Result를 이용하여 성공/실패에 따라 행동을 정의하는 작업을 거칠 것이다.
final class RMService {
static let shared = RMService()
private init() {
}
public func execute<T: Codable>(_ request: RMRequest,
expecting type: T.Type,
completion: @escaping (Result<T, Error>) -> Void) {
}
}
위 코드를 사용할 때는 아래의 코드처럼 사용할 것이다.
RMService.shared.execute(request, expecting: RMCharacter.self) { result in
switch result {
case .success( let data: RMCharacter ):
print(data)
case .failure( let error):
print(error)
}
}
주의
아프라즈는 강의 마지막에서 pathComponents를 Set으로 설정하라고 한다.
하면 안 된다. 왜냐면 순서가 보장이 안 되기 때문이다. 아프라즈도 다음 강의에서 바꾼다.
'Swift > Rick & Morty' 카테고리의 다른 글
[iOS] Rick&Morty - #6 Character List View (0) | 2023.03.03 |
---|---|
[iOS] Rick&Morty - #5 API Call (0) | 2023.03.02 |
[iOS] Rick&Morty - #3 Data Models (0) | 2023.02.28 |
[iOS] Rick&Morty - #2 Source Control과 API Design (0) | 2023.02.27 |
[iOS] Rick&Morty - #1 Setup (0) | 2023.02.26 |