[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (1편 - Foundation Setting)
[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (2편 - Services, Routers 구현)
지난 2편까지는 Alamofire를 Moya처럼 사용하기 위한 Foundation을 설계하고, VC에서 Service에 존재하는 싱글턴 객체를 이용하여 Post 통신 메서드를 직접 호출까지 해보았습니다. 이번 편에서는 다양한 request 타입 또는 메서드에 대응하기 위해 코드를 조금 수정해 보겠습니다.
RequestParams 열거형 Refactoring
HttpBody, Query 등등 다양한 리퀘스트 타입에 대응하기 위해 열거형을 Refactoring 합니다.
2편까지 만든 BaseRouter에 존재하는 RequestParams 열거형의 케이스는 아래와 같았습니다. RequestParams는 request에 필요한 httpBody와 query 등을 asURLRequest 메서드에 넘겨주는 역할을 했습니다.
enum RequestParams {
case query(_ parameter: Codable?)
case body(_ parameter: Codable?)
case requestParameters(_ parameter: [String : Any])
}
query와 body case의 경우 파라미터로 Codable한 struct를 받아서 toDictionary()메서드를 통해 딕셔너리 형태로 변환하는 방식을 사용했었습니다. 그러나 이러한 방식은 codable struct를 API 통신의 Case가 늘어날 때마다 새로 만들어줘야 했기 때문에, 파라미터로 Codable을 받는 것이 아닌 딕셔너리 형태로 받는 방식을 택하고자 했습니다.
requestParameters는 위에서 말한, 딕셔너리 타입으로 파라미터를 받는 예시입니다.
var parameters: RequestParams {
switch self {
case .requestSignUp(let email, let name, let pw):
let body: [String : Any] = [
"email": email,
"name": name,
"password": pw
]
return .requestParameters(body)
case .requestSignIn(let email, let pw):
let body: [String : Any] = [
"email": email,
"password": pw
]
return .requestParameters(body)
}
}
개인적으로 메서드에서 파라미터를 받아서 값을 하나씩 넣어주는 방식이 직관적으로 느껴졌기에, Codable로 파라미터들을 모두 딕셔너리 타입으로 바꿔줬습니다.
아래를 보시면 파라미터들이 딕셔너리 타입으로 바뀐 것을 확인할 수 있습니다.
또한 queryBody와 requestPlain 케이스가 새롭게 추가된 것을 볼 수 있습니다. 또한 requestParameter라는 이름을 requestBody라는 이름으로 조금 더 직관성을 띄게 수정했습니다.
enum RequestParams {
case queryBody(_ query: [String : Any], _ body: [String : Any])
case query(_ query: [String : Any])
case requestBody(_ body: [String : Any])
case requestPlain
}
queryBody : API가 query와 body를 모두 요구하는 경우에 사용
requestPlain : API가 아무런 query나 body를 요구하지 않는 경우에 사용. Get 통신의 경우 이러한 케이스가 많다.
그러면 이러한 타입들이 각각 어떠한 작업들을 해주는지 살펴보겠습니다.
private func makeParameterForRequest(to request: URLRequest, with url: URL) throws -> URLRequest {
var request = request
switch parameters {
case .query(let query):
let queryParams = query.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
var components = URLComponents(string: url.appendingPathComponent(path).absoluteString)
components?.queryItems = queryParams
request.url = components?.url
case .requestBody(let body):
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
case .queryBody(let query, let body):
let queryParams = query.map { URLQueryItem(name: $0.key, value: "\($0.value)") }
var components = URLComponents(string: url.appendingPathComponent(path).absoluteString)
components?.queryItems = queryParams
request.url = components?.url
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
case .requestPlain:
break
}
return request
}
query : 딕셔너리 타입을 queryItem으로 매핑한 다음에 URLComponents에 할당하고 있다.
requestbody : 딕셔너리 타입을 JSONSerialization하고, request 객체에 할당하고 있다.
queryBody : 위의 query와 requestboy에서 했던 일을 모두 해주고 있다.
requestPlain : 아무런 일도 하지 않고 request를 그대로 반환한다.
Multipart 통신(UploadTask) 대응
지금까지는 dataTask에 대해서만 다뤘는데, uploadTask인 Multipart 통신의 경우도 다룰 수 있도록 BaseRouter를 Refactoring 합니다.
지금까지 상정한 task들은 모두 dataTask의 경우였습니다. 이러한 경우 말고도 multiPart 통신도 굉장히 자주 쓰이는 통신 방식이기 때문에 Multipart 통신도 편하게 사용할 수 있도록 BaseRouter를 Refactoring 해보았습니다.
1. 먼저 아래와 같이 multipart라는 새로운 변수를 프로토콜에 선언해줍니다. 추후에 Service에서 multipart에 접근하여 그 API에 해당하는 MultipartFormData를 반환해줄 것입니다.
protocol BaseRouter: URLRequestConvertible {
var baseURL: String { get }
var method: HTTPMethod { get }
var path: String { get }
var parameters: RequestParams { get }
var header: HeaderType { get }
var multipart: MultipartFormData { get }
}
2. 다음으로 특정 Service들은 아예 Multipart 통신을 포함하고 있지 않을 수 있기 때문에, BaseRouter에 초기구현 해줍니다.
extension BaseRouter {
var baseURL: String {
return URLConstants.baseURL
}
var header: HeaderType {
return HeaderType.withToken
}
var multipart: MultipartFormData {
return MultipartFormData()
}
}
3. 여기까지 했으면, 이제 실제 Router에서 채택하여 Multipart 통신이 있는 경우에 multipart 변수를 새로 구현해주면 됩니다.
아래는 username, content, image를 받는 multiPartFormData를 반환하는 예시입니다.
enum AuthRouter {
case requestSignUp(email: String, name: String, pw: String)
case requestSignIn(email: String, pw: String)
case uploadProfile(username: String, content: String, image: UIImage)
}
extension AuthRouter: BaseRouter {
...
var multipart: MultipartFormData {
switch self {
case .uploadProfile(let username, let content, let image):
let multiPart = MultipartFormData()
let usernameData = username.data(using: .utf8) ?? Data()
let contentData = content.data(using: .utf8) ?? Data()
let imageData = image.pngData() ?? Data()
multiPart.append(usernameData, withName: "username")
multiPart.append(contentData, withName: "content")
multiPart.append(imageData, withName: "image", fileName: "Image.png", mimeType: "image/png")
return multiPart
default: return MultipartFormData()
}
}
}
4. 이렇게 정의해준 multipart를 service에서 호출하여 사용하면 됩니다.
func uploadProfile(username: String, content: String, image: UIImage, completion: @escaping (NetworkResult<Any>) -> (Void)) {
AFManager.upload(
multipartFormData: AuthRouter.upload(username: username, content: content, image: image).multipart,
with: AuthRouter.upload(username: username, content: content, image: image).responseData { response in
switch response.result {
case .success:
guard let statusCode = response.response?.statusCode else { return }
guard let data = response.data else { return}
let networkResult = self.judgeStatus(by: statusCode, data, type: Profile.self, decodingMode: .message)
completion(networkResult)
case .failure(let err):
print(err.localizedDescription)
}
}
}
다음 편에서는 API 통신의 결과를 Intercept하여 로그를 찍어주는 EventLogger를 구현해보겠습니다.
'iOS' 카테고리의 다른 글
[Swift] Local Notification을 이용하여 유저에게 알림 보내기 (0) | 2022.06.01 |
---|---|
[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (4편 - EventLogger로 통신 결과 확인하기) (0) | 2022.05.31 |
[iOS] AppDelegate.Swift는 무슨 역할을 하는 것일까? (3) | 2022.05.20 |
[Swift] Coordinator Pattern 기본!! With RayWanderlich Tutorial! (0) | 2022.05.19 |
[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (2편 - Services, Routers 구현) (4) | 2022.05.19 |