BookApp의 APIManager을 moya로

sonny·2024년 12월 31일
1

TIL

목록 보기
88/133

원래 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")!
    }
}

.
.
.


Moya를 이용한 리팩토링

이번 프로젝트에서 네트워크 요청을 처리하는 방법을 Alamofire에서 Moya로 리팩토링했다.

Moya는 Alamofire를 기반으로 동작하는 라이브러리라고 설명한 적이 있는데,

네트워크 통신을 훨씬 더 구조적이고 일관성 있게 관리할 수 있도록 도와준다.


1. 코드의 가독성과 유지보수성 향상됨

URLSession으로 네트워크 요청을 처리할 때 URL, HTTP 메소드, 파라미터 등을 각각의 요청마다 처리해야 했는데 Moya에서는 TargetType 프로토콜을 사용해서 모든 네트워크 관련 설정을 하나의 enum에 묶을 수 있다보니 코드의 가독성이 향상된다.

enum BookApi {
    case searchBooks(query: String)
}

이렇게 각 API를 하나의 enum으로 정의하고, 각 API에 필요한 설정을 메소드, 경로, 파라미터 등으로 분리하게 되니 한 곳에서 모든 네트워크 설정을 관리할 수 있었다.

이 방식은 코드 중복을 줄이고, 새로운 API를 추가할 때도 구조적으로 확장이 가능하게 만들어줬다.

2. 일관된 네트워크 요청 처리

Moya는 TargetType 프로토콜을 통해 각 요청에 대해 공통적인 설정을 처리할 수 있게 해준다.

예를 들면 HTTP 메소드나 URL, 헤더 등을 각 API마다 설정하는 방식을 벗어나서 enum을 기반으로 한 관리 방식으로 좀더 일관된 네트워크 요청을 처리할 수 있다.

var headers: [String : String]? {
    return ["Authorization": "KakaoAK 0000000000000000000000"]
}

위처럼 API마다 반복되는 헤더 설정을 매번 작성하는 대신에 TargetTypeheaders 속성에서 일괄적으로 관리할 수 있어 코드 중복이 줄어들었다.

3. 네트워크 응답 처리의 일관성

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와 결합된 처리가 간단하게 이루어졌다.

onSuccessonFailure 덕분에 성공 및 실패 케이스를 명확히 구분할 수 있고 코드도 깔끔해졌다.

4. 타입 안전성 강화

Moya는 각 API 요청에 대해 명시적인 타입 선언을 요구하기 때문에 네트워크 응답에 대한 타입 안전성을 확보할 수 있다는 장점이 있다.

TargetType 프로토콜을 따르면서 각 API의 요청과 응답에 대한 타입을 정확히 정의할 수 있어서 실수로 잘못된 요청을 보내거나, 잘못된 데이터를 받는 경우를 미연에 방지할 수 있다.


음...

Moya로의 리팩토링은 일관성과 유지보수성에서 큰 장점을 제공해줬다.

특히 여러 개의 API 요청을 처리할 때 각 API를 하나의 enum으로 묶어 관리할 수 있어 훨씬 더 깔끔하고 구조적인 코드가 완성 된 것같다.

TargetType 프로토콜을 통해 공통된 네트워크 요청 설정을 관리할 수 있게 되어 코드의 재사용성과 확장성이 높아졌다.

Moya와 RxSwift의 결합은 비동기 처리를 훨씬 간편하게 만들어 줬고 응답 처리 로직이 간결하고 명확하게 변한 것 같다.

이번 리팩토링으로 네트워크 요청 관리가 더 직관적이고 일관되게 개선된 느낌이다. (아직 할 것이 더 남았지만...)

앞으로 Moya를 사용해서 네트워크 요청을 처리하는 방식이 더 효율적일 것이라는 확신이 들 것 같기도..

profile
iOS 좋아. swift 좋아.

0개의 댓글

관련 채용 정보