목차

티스토리 뷰

iOS/Swift

[iOS] Moya 적용하기

Assum 2023. 6. 1. 14:53
728x90
반응형

안녕하세요 🐾

iOS에서는 URLSession을 조금 더 직관적으로 사용하기 위해 추상화 한 라이브러리인 Alamofire를 사용합니다.

그리고 Moya의 소개말엔 아래와 같은 글이 있습니다.

You probably use Alamofire to abstract away access to URLSession and all those nasty details you don't really care about. But then, like lots of smart developers, you write ad hoc network abstraction layers. They are probably called "APIManager" or "NetworkModel", and they always end in tears.

당신은 아마도 Alamofire를 사용하여 URL 세션에 대한 액세스와 당신이 별로 신경 쓰지 않는 모든 불편한 세부사항에 대한 액세스를 추상화할 것입니다. 그리고 많은 스마트한 개발자들처럼 임시 네트워크 추상화 계층을 작성합니다. 그들은 아마도 "APIManager" 또는 "NetworkModel"로 불리며, 항상 눈물로 끝납니다.

눈물 닦았습니다😢

 

의 사이즈가 커지고 서버와 주고받는 API가 많아질수록 네트워크 레이어의 추상화 작업의 피로도가 증가할텐데요. 

MoyaAlamofire'충분히 캡슐화' 하여 한번 더 추상화한 네트워크 계층의 템플릿을 제공하는 라이브러리 입니다.


Moya를 통해 다수의 개발자가 네트워크 계층을 공통된 규칙으로 작업 할 수 있고, 새 앱을 만들 때에도 네트워크 계층을 어떻게 만들어야 하는지대한 고민들을 해결 할 수 있는 이점이 있습니다.

 

 


 

1. 설치

CocoaPod을 이용하여 설치를 진행하였습니다.

아래와 같이 podfile에 pod입력 후 pod install 실행

저는 일반적인 사용 코드와 Rx사용시 코드 둘 다 적어보기 위해 Rx+Moya도 추가하였습니다.

pod 'Moya'
pod 'Moya/RxSwift' # Rx 사용시 추가

 



2. 네트워크 계층 구현

네트워크 레이어 역할을 하게될 클래스를 하나 생성합니다.

음식 검색시 사용되는 API라고 가정하여 FoodAPI.swift를 만들었습니다.

이제 이곳에서 사용할 APIEnum을 통해 아래와 같이 입력합니다.

/// 사용할 API 목록 작성
enum FoodAPI {
    case searchFood(name: String)
    case searchRandom
}

 

다음으로 MainAPI에서 TargetType 프로토콜을 extension하면 구현에 필요한 메소드가 생성됩니다. 

이를 이용하여 아래와 같이 API별로 필요한 값들을 세팅 할 수 있습니다 👍

let HEADERS: [String : String] = ["Content-Type":"application/json",
                                  "Accept":"application/json",
                                  "JWT": "JWT입력"]
let BASE_URL = URL(string: "https://doggyfootstep.tistory.com")!

extension FoodAPI: TargetType {
    /// 서버 도메인
    var baseURL: URL {
        return URL(string: "https://doggyfootstep.tistory.com")!
    }
    /// 도메인 뒤에 붙게 될 API Path
    var path: String {
        switch self {
        case .searchFood:
            return "search/food/"
        case .searchRandom:
            return "search/food/random"
        }
    }
    /// HTTP Method
    var method: Moya.Method {
        switch self {
        case .searchFood(name: _):
            return .post
        case .searchRandom:
            return .get
        }
    }
    /// Request 파라미터 설정
    var task: Moya.Task {
        switch self {
        case .searchFood(name: let name):
            //JSONEncoding 혹은 URLEncoding
            return .requestParameters(parameters: ["food":name], encoding: JSONEncoding.default)
        case .searchRandom:
            // 파라미터가 없을 경우
            return .requestPlain
        }
    }
    /// HTTP Header
    var headers: [String : String]? {
        switch self {
        case .searchFood(name: _):
            return HEADERS
        case .searchRandom:
            return nil
        }
    }
}

 

API에서 공용적으로 자주 사용되는 baseURL이나 headers에 대한 부분은 예제 코드와 같이 전역으로 빼두어 호출하시는 것도 도움이 됩니다.

 

이제 FoodAPI에서 받아온 결과는 JSON 타입이라 가정하고 이를 위한 구조체를 아래와 같이 하나 만들도록 하겠습니다.

/// FoodAPI Response 구조체
struct FoodSearchResponse: Codable {
    var foodID: Int
    var foodName: String
    var foodImageURL: String
}

 

그 다음 command(⌘) 키를 누른 상태에서 마우스로 FoodSearchResponse를 눌러주세요.

Add Expliclit Codable Implementation을 선택하면 Codable 구현을 위한 스니펫이 선택되어 아래와 같이 자동완성이 됩니다. 좋습니다 😎

 

저는 응답 파라미터의 Key값 중 구조체 변수와 다른것이 있다고 가정하고, 아래처럼 필요한 부분만 남겨 살짝 수정하였습니다.

/// FoodAPI Response 구조체
struct FoodSearchResponse: Codable {
    var foodID: Int
    var foodName: String
    var foodImageURL: String
    
    enum CodingKeys: String, CodingKey {
        case foodID
        case foodName
        case foodImageURL = "food_image_url"
    }
    
    init(from decoder: Decoder) throws {
        let container: KeyedDecodingContainer<FoodSearchResponse.CodingKeys> = try decoder.container(keyedBy: FoodSearchResponse.CodingKeys.self)
        
        self.foodID = try container.decode(Int.self, forKey: FoodSearchResponse.CodingKeys.foodID)
        self.foodName = try container.decode(String.self, forKey: FoodSearchResponse.CodingKeys.foodName)
        self.foodImageURL = try container.decode(String.self, forKey: FoodSearchResponse.CodingKeys.foodImageURL)
        
    }
}

네트워크 요청을 위한 모든 준비가 끝났습니다!

 

3. 네트워크 요청

네트워크 요청시엔 Moya에서는 MoyaProvider를 사용합니다.

MoyaProvider의 정의를 살펴보면 아래와 같이 TargetType 프로토콜을 구현한 Enum을 제네릭타입으로 받고있는 것을 볼 수 있네요.

 

이제 네트워크 요청을 수행할 곳에서 아래의 코드를 입력해 주세요.

class ViewController: UIViewController {

    private let disposeBag = DisposeBag()
    private let foodProvider = MoyaProvider<FoodAPI>()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        /// Moya Request
        foodProvider.request(.searchFood(name: "라면땅")) { result in
            switch result {
            case .success(let result):
                guard let data = try? result.map(FoodSearchResponse.self) else { return }
                print(data.foodName)
            case .failure(let error):
                print(error.localizedDescription)
            }
        }
        
        /// RxMoya Request
        foodProvider.rx.request(.searchFood(name: "라면땅"))
            .subscribe { [weak self] result in
                switch result {
                case .success(let result):
                    guard let data = try? result.map(FoodSearchResponse.self) else { return }
                    print(data.foodName)
                case .failure(let error):
                    print(error.localizedDescription)
            }
        }.disposed(by: disposeBag)
    }
}

 

클린아키텍처 혹은 MVVM 등의 디자인패턴을 사용하면 뷰컨트롤러에 바로 MoyaProvider 인스턴스를 선언하는 경우는 드물겠지만 예제 코드 입력을 위해 뷰컨트롤러에 입력하였습니다.

예로 보셨듯이, MoyaProvider 인스턴스들은 동일한 기능을 요구하는 다른 화면에서도 재사용 될 수 있습니다.

 

4. 결론

1. MoyaTargetType으로 API 네트워크 레이어를 만든다

2. MoyaProvider을 통해 해당 API 인스턴스를 생성한다.

3. 인스턴스에서는 RuquestResponse만 처리한다.

 

Moya는 위와 같은 일련의 작업들이 재사용성이 높은 구조로 간결하게 이루어져 있어 생산성을 높이는 좋은 도구라고 생각합니다.

자주 사용되는 라이브러리라 이미 관련된 포스팅이 많지만 리마인드를 위해 다시 정리해봅니다.

혹시나 틀린곳이 있다면 꼬옥 알려주세요 :)

반응형
댓글
300x250
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함