원래 APIManager가 굉장히 길었다.
import Foundation
import RxSwift
class APIManager {
static let shared = APIManager()
private init() {}
func fetchBooks(query: String) -> Single<[KakaoBook]> {
let urlString = "https://dapi.kakao.com/v3/search/book?query=\(query)"
guard let url = URL(string: urlString) else {
return Single.error(NSError(domain: "Invalid URL", code: -1, userInfo: nil))
}
print("Request URL: \(urlString)") // 요청 URL 확인
var request = URLRequest(url: url)
request.setValue("KakaoAK 3a4facecbe99c3bf5eab0a5480368bd5", forHTTPHeaderField: "Authorization") // KakaoAK로 변경
// 헤더와 요청 정보 확인
print("Request Headers: \(request.allHTTPHeaderFields ?? [:])")
return Single.create { single in
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Network Error: \(error.localizedDescription)") // 네트워크 에러 로그
single(.failure(error))
return
}
if let response = response as? HTTPURLResponse {
print("HTTP Status Code: \(response.statusCode)") // HTTP 상태 코드 확인
}
guard let data = data else {
print("No data received") // 데이터가 없을 경우
single(.failure(NSError(domain: "No data", code: -1, userInfo: nil)))
return
}
do {
let response = try JSONDecoder().decode(KakaoBookResponse.self, from: data)
print("Decoded Response: \(response)") // 디코딩된 데이터 출력
single(.success(response.documents))
} catch {
print("Decoding Error: \(error.localizedDescription)") // 디코딩 오류 로그
single(.failure(error))
}
}
task.resume()
return Disposables.create { task.cancel() }
}
}
}
하지만 Moya 로 리팩토링을 하고나서의 APIManager는...
import Moya
import RxSwift
import RxMoya
class APIManager {
static let shared = APIManager()
private let provider = MoyaProvider<BookApi>()
private init() {}
func fetchBooks(query: String) -> Single<[KakaoBook]> {
return provider.rx.request(.searchBooks(query: query))
.filterSuccessfulStatusCodes()
.map(KakaoBookResponse.self)
.map { $0.documents }
}
}
물론 따로 BookApi 이라는 파일을 만든거고 이게 API 정의 파일인데,
실제 API 요청을 만드는 데 필요한 설정을 모아 놓은 것이다.
각 케이스에 맞는 URL, HTTP 메소드, 파라미터 등을 설정해두고 이 설정을 기반으로 네트워크 요청을 쉽게 할 수 있도록 도와준다.
import Moya
import Foundation
enum BookApi {
case searchBooks(query: String)
}
extension BookApi: TargetType {
var path: String {
switch self {
case .searchBooks:
return "/v3/search/book"
}
}
var method: Moya.Method {
switch self {
case.searchBooks:
return .get
}
}
var task: Moya.Task {
switch self {
case .searchBooks(let query):
return .requestParameters(parameters: ["query" : query], encoding: URLEncoding.default)
}
}
var headers: [String : String]? {
return ["Authorization": "KakaoAK 0000000000000000000000"]
}
var baseURL: URL {
return URL(string: "https://dapi.kakao.com")!
}
}
.
.
.
이번 프로젝트에서 네트워크 요청을 처리하는 방법을 Alamofire에서 Moya로 리팩토링했다.
Moya는 Alamofire를 기반으로 동작하는 라이브러리라고 설명한 적이 있는데,
네트워크 통신을 훨씬 더 구조적이고 일관성 있게 관리할 수 있도록 도와준다.
URLSession으로 네트워크 요청을 처리할 때 URL, HTTP 메소드, 파라미터 등을 각각의 요청마다 처리해야 했는데 Moya에서는 TargetType 프로토콜을 사용해서 모든 네트워크 관련 설정을 하나의 enum에 묶을 수 있다보니 코드의 가독성이 향상된다.
enum BookApi {
case searchBooks(query: String)
}
이렇게 각 API를 하나의 enum으로 정의하고, 각 API에 필요한 설정을 메소드, 경로, 파라미터 등으로 분리하게 되니 한 곳에서 모든 네트워크 설정을 관리할 수 있었다.
이 방식은 코드 중복을 줄이고, 새로운 API를 추가할 때도 구조적으로 확장이 가능하게 만들어줬다.
Moya는 TargetType
프로토콜을 통해 각 요청에 대해 공통적인 설정을 처리할 수 있게 해준다.
예를 들면 HTTP 메소드나 URL, 헤더 등을 각 API마다 설정하는 방식을 벗어나서 enum을 기반으로 한 관리 방식으로 좀더 일관된 네트워크 요청을 처리할 수 있다.
var headers: [String : String]? {
return ["Authorization": "KakaoAK 0000000000000000000000"]
}
위처럼 API마다 반복되는 헤더 설정을 매번 작성하는 대신에 TargetType
의 headers
속성에서 일괄적으로 관리할 수 있어 코드 중복이 줄어들었다.
Moya를 사용하면 응답을 처리하는 로직을 쉽게 캡슐화할 수 있는데,
onSuccess
, onFailure
를 사용하여 응답을 처리하고,
Moya의 RxSwift 연동을 통해 비동기 요청을 관리할 수 있다보니 비동기 코드 처리가 훨씬 간결하다.
APIManager.shared.fetchBooks(query: searchText)
.observe(on: MainScheduler.instance)
.subscribe(onSuccess: { books in
self.searchResults = books
self.tableView.reloadData()
}, onFailure: { error in
print("Error fetching books: \(error.localizedDescription)")
})
.disposed(by: disposeBag)
Moya를 사용한 후에 네트워크 응답 처리 과정에서 RxSwift와 결합된 처리가 간단하게 이루어졌다.
onSuccess
와 onFailure
덕분에 성공 및 실패 케이스를 명확히 구분할 수 있고 코드도 깔끔해졌다.
Moya는 각 API 요청에 대해 명시적인 타입 선언을 요구하기 때문에 네트워크 응답에 대한 타입 안전성을 확보할 수 있다는 장점이 있다.
TargetType
프로토콜을 따르면서 각 API의 요청과 응답에 대한 타입을 정확히 정의할 수 있어서 실수로 잘못된 요청을 보내거나, 잘못된 데이터를 받는 경우를 미연에 방지할 수 있다.
Moya로의 리팩토링은 일관성과 유지보수성에서 큰 장점을 제공해줬다.
특히 여러 개의 API 요청을 처리할 때 각 API를 하나의 enum
으로 묶어 관리할 수 있어 훨씬 더 깔끔하고 구조적인 코드가 완성 된 것같다.
TargetType
프로토콜을 통해 공통된 네트워크 요청 설정을 관리할 수 있게 되어 코드의 재사용성과 확장성이 높아졌다.
Moya와 RxSwift의 결합은 비동기 처리를 훨씬 간편하게 만들어 줬고 응답 처리 로직이 간결하고 명확하게 변한 것 같다.
이번 리팩토링으로 네트워크 요청 관리가 더 직관적이고 일관되게 개선된 느낌이다. (아직 할 것이 더 남았지만...)
앞으로 Moya를 사용해서 네트워크 요청을 처리하는 방식이 더 효율적일 것이라는 확신이 들 것 같기도..