본문 바로가기

iOS

[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (2편 - Services, Routers 구현)

https://jazz-the-it.tistory.com/25

 

[Swift] Alamofire를 Moya처럼 사용해보자! By Router Pattern (1편 - Foundation Setting)

URLSession, Alamofire, Moya 간의 선택  지금껏 iOS 앱 개발을 하면서 서버통신 시에 Moya Library를 주로 사용해 왔습니다. Moya는 Alamofire 라이브러리를 한층 더 Wrapping하여 사용하기 편리하도록 한 라이..

jazz-the-it.tistory.com

  지난 편에서는 Alamofire를 Moya처럼 사용하기 위한 기초가 되는 Foundation 폴더의 요소들에 대해서 알아보았습니다. 2편에서는 Router와 BaseService를 채택 또는 상속하여 endPoint 별로 다른 request를 구현하고, 구체적인 호출 메서드를 만들어주는 Routers와 Service 폴더의 요소에 대해서 알아보겠습니다. 

 

* Routers

 Routers 폴더에는 Foundation의 Router 프로토콜을 상속하여 구현하는 클래스들이 담깁니다. 이러한 Router들은 Service 폴더에 존재하는 각종 request 메서드들에서 사용됩니다. Router들의 주요 기능은 각각의 endpoint 별로 적절한 request를 반환하는 것입니다.

import Foundation
import Alamofire

enum AuthRouter {
    case requestSignUp(email: String, name: String, pw: String)
    case requestSignIn(email: String, pw: String)
}

extension AuthRouter: Router {
    
    var path: String {
        switch self {
        case .requestSignUp:
            return "/auth/signup"
        case .requestSignIn:
            return "/auth/signin"
        }
    }
    
    var method: HTTPMethod {
        switch self {
        case .requestSignUp, .requestSignIn:
            return .post
        }
    }
    
    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)
        }
    }
}

먼저 열거형을 선언해주고, 실제로 서비스에서 사용할 request case들을 제시합니다. 그리고 이 열거형에 Router를 채택하여, path, method, parameter와 같은 값들을 case별로 구현해 줍니다. 이러한 과정을 통해 각 request case별로 체계적으로 request property들을 관리할 수 있습니다.

 

* Service

 Service 폴더에는 위에서 Router 프로토콜을 채택하여 구현해준 Routers들을 실제로 사용하여 원하는 서버통신 요청을 실제로 수행하는 메서드들을 가진 Service들이 존재합니다. 각 Service들은 Foundation 폴더에 있는 BaseService를 상속받기에 judgeStatus 메서드와 AFManger를 가지고 있습니다. 따라서 이들을 호출하여 각각 status별 데이터의 디코딩, 서버통신 요청을 보낼 수 있게 합니다.

 

싱글턴 패턴을 사용하여 타입 메서드를 통해서만 Network에 접근할 수 있도록 했으며, 메서드를 통해서만 Router에 접근하여 service의 기능을 사용할 수 있도록 했습니다.

import Foundation

import Alamofire

class AuthService: BaseService {
    static let shared = AuthService()
    
    private override init() {}
}

extension AuthService {
    
    func requestSignIn(email: String, pw: String, completion: @escaping (NetworkResult<Any>) -> (Void)) {
        AFManager.request(AuthRouter.requestSignIn(email: email, pw: pw)).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: SignIn.self, decodingMode: .message)
                completion(networkResult)
                
            case .failure(let err):
                print(err.localizedDescription)
            }
        }
    }
    
    func requestSignUp(email: String, name: String, pw: String, completion: @escaping (NetworkResult<Any>) -> (Void)) {
        AFManager.request(AuthRouter.requestSignUp(email: email, name: name, pw: pw)).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: SignUp.self, decodingMode: .message)
                
                completion(networkResult)
                
            case .failure(let err):
                print(err.localizedDescription)
            }
        }
    }
}

 

* VC에서의 호출

 위와 같이 Service까지 작성했다면, 아래와같이 타입 프로퍼티에 접근하여 메서드를 호출할 수 있습니다. requestSignIn 메서드의 경우 DecodingMode를 .message로 적용했기 때문에, success의 경우에도 바로 message를 받아오고 있습니다.

 

AuthService.shared.requestSignIn(email: emailTextField.text ?? "", pw: passwordTextField.text ?? "") { networkResult in
    switch networkResult {
    case .success(let message):
        if let message = message as? String {
            self.makeAlert(title: message) { [weak self] UIAlertAction in
                 self?.presentMainTBC()
             }
         }
    case .requestErr(let status):
         if let status = status as? Int {
             switch status {
             case 404:
                 self.makeAlert(title: "로그인 실패", message: "존재하지 않는 사용자입니다")
             case 409:
                 self.makeAlert(title: "로그인 실패", message: "비밀번호가 올바르지 않습니다")
             default:
                 print("requestErr - requestSignIn")
             }
         }
    case .pathErr:
         print("pathErr - requestSignIn")
    default: print("ServerErr")
    }
}