본문 바로가기
Swift

[iOS] About Router pattern

by dsungc 2024. 6. 27.

 

지난 글에서 Alamofire에 대해 설명했는데요!

이번엔 AlamofireURLRequestConvertible 이라는 프로토콜을 사용해 라우터 패턴을 사용해보고자 합니다!!

 

여기서 Router Pattern 이란?

생소한 단어인 듯 하지만 집에 있는 공유기가 바로 Router이기에 실생활에 대입해서 생각해보면 꽤나 간단하게 생각되실거에요

집에 있는 라우터를 통해 와이파이를 사용하기도 하고 직접연결을 통해 컴퓨터를 사용할 수도 있죠??

우리가 원하는 기능을 우리가 선택해서 사용할 수 있는거죠. 

앱에서도 마찬가지입니다.

여러 API요청이 발생하게 됐을 때, 하나의 Router에서 관리할 수 있다..! 정도로 보시면 될 것 같아요.

이를 사용하기 전에 여기저기 흩어져있던 코드들을 한 데 모아 관리할 수 있겠죠?

 

 

자 이번엔 코드로 한 번 가보시죠~

protocol TargetType: URLRequestConvertible {
    var baseURL: String { get }
    var method: HTTPMethod { get }
    var path: String { get }
    var header: [String: String] { get }
    var parameters: String? { get }
    var queryItems: [URLQueryItem]? { get }
    var body: Data? { get }
}

 

우선 저는 TagetType이라고 명명한 URLRequestConvertible 프로토콜을 채택한 프로토콜을 구현해주었습니다.

 

  func asURLRequest() throws -> URLRequest {
        
        let url = try baseURL.asURL()
        
        var component = URLComponents(url: url.appendingPathComponent(path), resolvingAgainstBaseURL: false)
        
        if let queryItems = queryItems {
            component?.queryItems = queryItems
        }
        guard let finalUrl = component?.url else {
            throw URLError(.badURL)
        }
        
        var request = try URLRequest(url: finalUrl, method: method)
        request.allHTTPHeaderFields = header
        request.httpBody = body
        
        return request
    }

 

위 메서드는 추가적인 파라미터나 헤더가 있을 때 사용하는 메서드인데요, 

이를 활용하여 URLRequest 객체를 생성하고, 쉽게 활용할 수 있습니다.

 

 

자 그럼 이제 세부사항을 좀 더 추가해보려 합니다.

 

enum RequestPhotoRouter {
    
    case TopicTrend(topic: String)
    case Search(query: String, page: Int, sort: String, color: String?)
    case Statistics(imageId : String)
    case Random
    
    
    var baseURL: String { return "https://api.unsplash.com" }

    var endPoint: URL {
        switch self {
        case .TopicTrend(let topic):
            guard let url = URL(string: baseURL + "/topics/\(topic)/photos") else { fatalError("Invalid URL") }
            return url
        case .Search:
            guard let url = URL(string: baseURL + "/search/photos") else { fatalError("Invalid URL") }
            return url
        case .Random:
            guard let url = URL(string: baseURL + "/photos/random") else { fatalError("Invalid URL") }
            return url
        case .Statistics(let imageId):
            guard let url = URL(string: baseURL + "/photos/\(imageId)/statistics") else { fatalError("Invalid URL") }
            return url
        }
    }
    var method: HTTPMethod {
        return .get
    }
    
    var parameter: Parameters {
        switch self {
        case .TopicTrend:
            return ["page" : 1, "client_id" : APIKey.accessKey]
        case .Search(query: let query, page: let page, sort: let sort, color: let color):
            var param: Parameters = ["query" : query, "page" : page, "per_page" : 20, "order_by" : sort, "client_id" : APIKey.accessKey]
            if let color = color {
                param["color"] = color
            }
            
            return param
        case .Random:
            return ["count" : "10", "client_id" : APIKey.accessKey]
        case .Statistics:
            return ["client_id" : APIKey.accessKey]
        }
    }
}

 

열거형을 활용하여 활용할 api 케이스를 나누어 준 뒤, 해당 케이스에 맞는 url, method, parameter 등 요소들을 기입해줍니다.

바로 여기서 우리가 활용할 케이스에 맞게 골라서 써먹는 거에요!

 

그런데 위 코드를 작성할 때 유의해야할 점이라고 하면 명세서를 정말 잘 봐야하더라구요(너무 당연한 얘기지만)

저도 한 군데 잘못 작성해서 꽤 긴 시간 애를 먹었습니다..

 

아무튼!!

 

어떻게 사용하는지 코드를 보겠습니다!!

final class CallPhotoNetwork {
    
    static var shared = CallPhotoNetwork()
    
    private init() {}
    
    func networking<T: Decodable>(api: APIRouter, model:T.Type ,completionHandler: @escaping (T?, GetError?) -> Void ) {
       
        AF.request(api.endPoint, method: api.method, parameters: api.parameter, encoding: URLEncoding(destination: .queryString)).validate(statusCode: 200..<300).responseDecodable(of: T.self) { response in
            switch response.result {
            case .success(let value):
                completionHandler(value, nil)
            case .failure(_):
                completionHandler(nil, .failedRequest)
            }
        }
    }
    
    
}

 

처음엔 정말 이해도 안되고 구현하는데 애먹었던!! Alamofire를 활용한 네트워크 코드를 구현해 주고, 

 

짜잔~ 

 

네트워크 코드가 필요한 곳에서 다음과 같이 작성 후, api 작성 칸에 .을 찍어주면 우리가 구현했는데 케이스가 쫙 뜹니다!! 

너무 신기하고 편리하더라구요

 

라우터 작성할 때는 꽤 긴 시간이 들겠지만, 한 번 작성해두면 api 호출 시, 더이상 url, 쿼리 파라미터, 헤더를 로컬로 선언하지 않아도 됩니다.

 

이렇게 Alamofire에 대해 맛보면 볼 수록.. URLSession이 멀어져만 가는 것 같은데..

Alamofire를 사용하니 보다 더 네트워킹 코드를 쉽게 작성할 수 있고, 이미 내장돼있는 프로토콜을 활용하여 라우터를 작성하는데도 역시 좀 더 쉽게 구성할 수 있었습니다. 아주 👍

 

이번 게시글에선 라우터 패턴에 대해 다뤄보았는데요,

흠.. 사실 초반엔 api를 그렇게 많이 다루지 않다보니 필요성에 대해 크게 와닿지 않았는데요.. 그런데...

api를 많이 다루는 프로젝트를 진행해보니 이만한 게 없더라구요

한 번 잘 만들어두니 정말 유용하게 잘 써먹었습니다.

 

네트워크에 대한 코드 이제는 좀 감을 잡았을지도..?

🤣

 

요기까지~

.

.

.

.

 

 

'Swift' 카테고리의 다른 글

[iOS] About 예외처리(Error Handling)  (0) 2024.07.07
[iOS] About 싱글턴 패턴  (0) 2024.06.29
[iOS] About Alamofire  (0) 2024.06.23
[iOS] About URLSession - 2  (0) 2024.06.19
[iOS] About URLSession - 1  (0) 2024.06.12