본문 바로가기

iOS/iOS (응용)

[iOS] URL및 endpoint관리하기

 

1️⃣ URL extension활용하기


  • URL extension을 활용하여 필요한 URL마다 추가하는 방법
  • 파라미터가 필요하다면 함수로 작성
  • 💡  이 방법은 편리하지만 enum을 활용하면 좀 더 Endpoint들을 한눈에 파악하기 편리합니다.
extension URL {
    static var recommendations: URL {
        makeForEndpoint("recommendations")
    }

    static func article(withID id: Article.ID) -> URL {
        makeForEndpoint("articles/\\(id)")
    }
}

private extension URL {
    static func makeForEndpoint(_ endpoint: String) -> URL {
        URL(string: "<https://api.myapp.com/\\(endpoint)>")!
    }
}

 

 

2️⃣ Endpoint (enum활용방법)


  • enum의 연관값을 활용하여 파라미터를 추가할 수 있습니다.
  • 💡 enum을 활용하는 방법은 모든 endpoint를 한 곳에서 정의해야 하므로 실용적이지 않을 수 있습니다. (예를들어, 네트워킹 코드를 별도의 모듈로 추출하고 싶은 경우)
    • 즉 case가 추가될 때 마다 enum에서 case를 추가해야 합니다.
enum Endpoint {
    case recommendations
    case article(id: Article.ID)
    case search(query: String, maxResultCount: Int = 100)
}

extension Endpoint {
    var url: URL {
        switch self {
        case .recommendations:
            return .makeForEndpoint("recommendations")
        case .article(let id):
            return .makeForEndpoint("articles/\\(id)")
        case .search(let query, let count):
            return .makeForEndpoint("search/\\(query)?count=\\(count)")
        }
    }
}

private extension URL {
    static let baseURL = "<https://api.myapp.com>"
    
    static func makeForEndpoint(_ endpoint: String) -> URL {
        URL(string: baseURL + endpoint)!
    }
}

 

3️⃣ Endpoint (Struct 활용방법)


  • 💡 enum으로 작성할 때 case 추가를 하는것은 명세화 측면이나 휴먼에러 측면에서 파악하기 쉬운 장점이 있습니다.
struct Endpoint {
    var path: String
    var queryItems: [URLQueryItem] = []
}

extension Endpoint {
    var url: URL {
        var components = URLComponents()
        components.scheme = "https"
        components.host = "api.myapp.com"
        components.path = "/" + path
        components.queryItems = queryItems

        guard let url = components.url else {
            preconditionFailure(
                "Invalid URL components: \\(components)"
            )
        }

        return url
    }
}

extension Endpoint {
    static var recommendations: Self {
        Endpoint(path: "recommendations")
    }

    static func article(withID id: Article.ID) -> Self {
        Endpoint(path: "articles/\\(id)")
    }

    static func search(for query: String,
                       maxResultCount: Int = 100) -> Self {
        Endpoint(
            path: "search/\\(query)",
            queryItems: [URLQueryItem(
                name: "count",
                value: String(maxResultCount)
            )]
        )
    }
}

 

 

4️⃣ Endpoint (Protocol 활용방법)


  • Path프로토콜을 활용하여 CustomURL프로토콜을 만들면 추후에 TDD를 수행할 때 sampleData와 url을 불러와서 사용하기 편리할 것 같다.
enum Endpoint {
    case recommendations
    case article(id: Article.ID)
    case search(query: String, maxResultCount: Int = 100)
}

protocol Path {
    var path : String { get }
}

extension Endpoint : Path {
    var path: String {
        switch self {
        case .recommendations: return "/recommendations"
        case .article(let id): return "/articles/\\(id)"
				case .search(let query, let count): return "search/\\(query)?count=\\(count)"
        }
    }
}

protocol CustomURL: Path {
    var baseURL: URL { get }
    var sampleData: String { get }
}

extension Endpoint: CustomURL {
    var baseURL: URL { return URL(string: "<https://api.myapp.com>")! }
    var sampleData: String {
        switch self {
        case .recommendations: return "recommendations Sample Data"
        case .article(let id): return "{id: \\"\\(id)\\"}"
        case .search(let query, let count): return "{query: \\"\\(query)\\", count: \\"\\(count)\\"}"
        }
    }
}

func url(_ route: CustomURL) -> URL {
    return route.baseURL.appendingPathComponent(route.path)
}

let sample: Endpoint = .recommendations
print(url(sample))

 

🤔 고민한 부분


Endpoint를 관리하는 방법이 여러방법이 있는데 각각의 장단점은 무엇일까?

  1. url extension
    • 장점 : 가장 간편하게 사용할 수 있다.
    • 단점 : extension에 작성되어 있어 한눈에 파악하기 어려울 것 같다.
  2. enum
    • 장점 : 명세화 측면이나 휴먼에러측면에서 case를 통해 한눈에 파악하기 쉬울 것 같다. , 연관값이 생기면서 쿼리를 추가하기도 용이해졌다.
    • 단점 : 만약 네트워킹 코드를 별도의 모듈로 추출하는 경우 모든 endpoint를 한곳에서 정의해야 하므로 불편함이 있을 것 같다.
  3. struct
    • 장점 : enum의 단점을 해결할 수 있다. let temp = Endpoint(path: "temp") 식으로 어디서든 추가 가능
    • 단점 : enum의 장점, 명세화측면에서 불편할 수도 있을 것 같다. Endpoint 구조체인스턴스를 enum으로 묶으면 해결될 것 같다.
endpoint와 url만 관리할 때는 특별한 경우가 아니라면 enum을 사용할 것같다. moya같은 라이브러리도 비슷한 방식으로 관리한다고 합니다.

 

🔗 참고링크