Alamofire의 URLRequestConvertible의 적절한 사용


79

@mattt의 튜토리얼 인 README를 몇 개 읽었지만 몇 가지를 알아낼 수 없습니다.

  1. URLRequestConvertible실제 API에서 올바른 사용법은 무엇입니까 ? URLRequestConvertible모든 API에 대한 프로토콜을 구현하여 하나의 라우터를 만들면 거의 읽을 수없는 것 같습니다. 엔드 포인트 당 하나의 라우터를 만들어야합니까?

  2. 두 번째 질문은 Swift 언어에 대한 경험 부족으로 인한 것 같습니다. enum라우터 구축에 사용되는 이유를 알 수 없습니까? 정적 메서드에 클래스를 사용하지 않는 이유는 무엇입니까? 여기에 예가 있습니다 (Alamofire의 README에서)

    enum Router: URLRequestConvertible {
        static let baseURLString = "http://example.com"
        static let perPage = 50
    
        case Search(query: String, page: Int)
    
        // MARK: URLRequestConvertible
    
        var URLRequest: NSURLRequest {
            let (path: String, parameters: [String: AnyObject]?) = {
                switch self {
                case .Search(let query, let page) where page > 1:
                    return ("/search", ["q": query, "offset": Router.perPage * page])
                case .Search(let query, _):
                    return ("/search", ["q": query])
                }
            }()
    
            let URL = NSURL(string: Router.baseURLString)!
            let URLRequest = NSURLRequest(URL: URL.URLByAppendingPathComponent(path))
            let encoding = Alamofire.ParameterEncoding.URL
    
            return encoding.encode(URLRequest, parameters: parameters).0
        }
    }
    
  3. 매개 변수를 전달하는 방법에는 두 가지가 있습니다.

    case CreateUser([String: AnyObject])
    case ReadUser(String)
    case UpdateUser(String, [String: AnyObject])
    case DestroyUser(String)
    

    그리고 (사용자가 4 개의 매개 변수를 가지고 있다고 가정합니다)

    case CreateUser(String, String, String, String)
    case ReadUser(String)
    case UpdateUser(String, String, String, String, String)
    case DestroyUser(String)
    

    @mattt는 예제에서 첫 번째를 사용하고 있습니다. 그러나 이는 라우터 외부 (예 : UIViewControllers)에서 "하드 코딩"매개 변수 이름으로 이어질 것입니다. 매개 변수 이름을 잘못 입력하면 오류가 발생할 수 있습니다.
    다른 사람들은 두 번째 옵션을 사용하고 있지만이 경우 각 매개 변수가 무엇을 나타내는 지 전혀 명확하지 않습니다.
    이를 수행하는 올바른 방법은 무엇입니까?

답변:


110

좋은 질문입니다. 각각을 개별적으로 분석합시다.

실제 API에서 URLRequestConvertible의 올바른 사용법은 무엇입니까?

URLRequestConvertible프로토콜은 유효을 만들 수 지정된 객체를 보장하는 경량의 방법입니다 NSURLRequest. 이 프로토콜을 특정 방식으로 사용하도록 강요하는 엄격한 규칙이나 지침은 없습니다. 다른 개체가 NSURLRequest. Alamofire와 관련된 더 많은 정보는 여기 에서 찾을 수 있습니다 .

엔드 포인트 당 하나의 라우터를 만들어야합니까?

절대 아니다. 그것은 Enum. Swift Enum 객체는 놀랍도록 강력하여 많은 양의 공통 상태를 공유하고 실제로 다른 부분을 켤 수 있습니다. NSURLRequest다음과 같이 간단한 것을 만들 수 있다는 것은 정말 강력합니다!

let URLRequest: NSURLRequest = Router.ReadUser("cnoon")

라우터 구축에 enum이 사용되는 이유를 알 수 없습니까? 정적 메서드에 클래스를 사용하지 않는 이유는 무엇입니까?

열거 형은 공통 인터페이스에서 여러 관련 객체를 훨씬 더 간결하게 표현하기 때문에 사용됩니다. 모든 방법은 모든 케이스간에 공유됩니다. 정적 메서드를 사용한 경우 각 메서드에 대한 각 경우에 대한 정적 메서드가 있어야합니다. 또는 개체 내부에 Obj-C 스타일 열거 형을 사용해야합니다. 여기 제가 의미하는 바에 대한 간단한 예가 있습니다.

enum Router: URLRequestConvertible {
    static let baseURLString = "http://example.com"

    case CreateUser([String: AnyObject])
    case ReadUser(String)
    case UpdateUser(String, [String: AnyObject])
    case DestroyUser(String)

    var method: Alamofire.HTTPMethod {
        switch self {
        case .CreateUser:
            return .post
        case .ReadUser:
            return .get
        case .UpdateUser:
            return .put
        case .DestroyUser:
            return .delete
        }
    }

    var path: String {
        switch self {
        case .CreateUser:
            return "/users"
        case .ReadUser(let username):
            return "/users/\(username)"
        case .UpdateUser(let username, _):
            return "/users/\(username)"
        case .DestroyUser(let username):
            return "/users/\(username)"
        }
    }
}

다른 끝점의 메서드를 가져 오려면 매개 변수를 전달하지 않고도 동일한 메서드를 호출하여 찾고있는 끝점 유형을 정의 할 수 있습니다. 선택한 경우에 이미 처리되어 있습니다.

let createUserMethod = Router.CreateUser.method
let updateUserMethod = Router.UpdateUser.method

또는 경로를 얻으려면 동일한 유형의 호출.

let updateUserPath = Router.UpdateUser.path
let destroyUserPath = Router.DestroyUser.path

이제 정적 메서드를 사용하여 동일한 접근 방식을 시도해 보겠습니다.

struct Router: URLRequestConvertible {
    static let baseURLString = "http://example.com"

    static var method: Method {
        // how do I pick which endpoint?
    }

    static func methodForEndpoint(endpoint: String) -> Method {
        // but then I have to pass in the endpoint each time
        // what if I use the wrong key?
        // possible solution...use an Obj-C style enum without functions?
        // best solution, merge both concepts and bingo, Swift enums emerge
    }

    static var path: String {
        // bummer...I have the same problem in this method too.
    }

    static func pathForEndpoint(endpoint: String) -> String {
        // I guess I could pass the endpoint key again?
    }

    static var pathForCreateUser: String {
        // I've got it, let's just create individual properties for each type
        return "/create/user/path"
    }

    static var pathForUpdateUser: String {
        // this is going to get really repetitive for each case for each method
        return "/update/user/path"
    }

    // This approach gets sloppy pretty quickly
}

참고 : 케이스를 전환하는 속성이나 함수가 많지 않은 경우 열거 형은 구조체에 비해 많은 이점을 제공하지 않습니다. 다른 구문 설탕을 사용하는 대체 접근 방식입니다.

열거 형은 상태 및 코드 재사용을 극대화 할 수 있습니다. 관련 값을 사용하면 다소 유사하지만 NSURLRequest생성 과 같이 매우 다른 요구 사항이있는 개체를 그룹화하는 것과 같은 매우 강력한 작업을 수행 할 수도 있습니다 .

가독성을 높이기 위해 열거 형 케이스의 매개 변수를 구성하는 올바른 방법은 무엇입니까? (이걸 함께 으깨 야 했어)

대단한 질문입니다. 이미 두 가지 가능한 옵션을 마련했습니다. 귀하의 필요에 더 잘 맞는 1/3을 추가하겠습니다.

case CreateUser(username: String, firstName: String, lastName: String, email: String)
case ReadUser(username: String)
case UpdateUser(username: String, firstName: String, lastName: String, email: String)
case DestroyUser(username: String)

연관된 값이있는 경우 튜플의 모든 값에 대해 명시적인 이름을 추가하는 것이 도움이 될 수 있다고 생각합니다. 이것은 맥락을 구축하는 데 정말 도움이됩니다. 단점은 다음과 같이 switch 문에서 해당 값을 다시 선언해야한다는 것입니다.

static var method: String {
    switch self {
    case let CreateUser(username: username, firstName: firstName, lastName: lastName, email: email):
        return "POST"
    default:
        return "GET"
    }
}

이것은 멋지고 일관된 컨텍스트를 제공하지만 꽤 장황합니다. 그것들은 현재 Swift에서 세 가지 옵션이며 사용 사례에 따라 올바른 옵션이 사용됩니다.


최신 정보

🔥🔥 Alamofire 4.0 🔥🔥의 출시와 함께, URLRequestConvertible이제 훨씬 더 똑똑해지고 던질 수도 있습니다. 유효하지 않은 요청을 처리하고 응답 핸들러를 통해 현명한 오류를 생성하기 위해 Alamofire에 완전한 지원을 추가했습니다. 이 새로운 시스템은 README 에 자세히 설명되어 있습니다.


8
감사. 하나의 라우터와 엔드 포인트 당 라우터 구축에 대한 답변에 대한 한 가지 질문입니다 (예 : Alamofire 페이지의 CRUD 예제). 내가 5 개의 엔드 포인트를 말하면 각각 3 ~ 4 개의 메소드가 있다고 생각하지 마세요 case. 15 ~ 20 개의 문장입니다. 그것은 나에게 큰 방법처럼 보입니다. 읽을 수있는 코드로
이어질지 잘 모르겠습니다

1
두 번째 답변 (열거 형 대 정적 메서드)과 관련하여 여기에서 저에게 구멍 포인트는 열거 형 / 클래스 내부의 구현을 숨기는 것입니다. 외부의 방법이나 경로를 알 필요가 없습니다. Router.createUser("test@mail.com", "....")서버에 대한 결과를 해석하기위한 블록 을 호출 하고 싶습니다 . 모든 세부 정보 (메서드, 경로, API 루트 등)는 라우터에 대해 비공개 일 수 있습니다. 괜찮습니다.
OgreSwamp 2015

2
마지막으로, 여러 기능이있는 경우 20 개의 서로 다른 엔드 포인트를 단일 열거 형으로 채우고 싶지는 않을 것입니다. switch 문이 너무 길어 읽기가 어렵습니다. 그 시점에서 확실히 코드 냄새가납니다. 나에게 스위치에 5 ~ 6 개의 케이스가 있으면 정말 가독성을 잃기 시작합니다.
cnoon

2
마지막 코멘트 @cnoon에, (이전 코멘트를 읽었습니다) (CRUD 사용자 라우터의 예를 사용하여) 트위터의 게시물 요청 및 사용자 CRUD와 같은 다른 컨텍스트에 속하는 일부 요청이있는 경우 두 개의 분리 된 라우터가 될까요?
Renan Kosicki

3
네 맞습니다 @RenanKosicki. Router 열거 형에 케이스가 너무 많으면 확실히 반환 불가 지점에 도달합니다. 그것들을 논리적 그룹으로 나누는 것은 확실히 더 바람직한 설계입니다.
cnoon

8

다음은 Alamofire의 Githubenum Router 에서 권장하는 Swift 3의 최신 버전 입니다 . .NET을 사용하여 라우터를 올바르게 구현하는 방법 측면에서 유용하다는 것을 알기를 바랍니다 .URLRequestConvertible

import Alamofire

enum Router: URLRequestConvertible
{
    case createUser(parameters: Parameters)
    case readUser(username: String)
    case updateUser(username: String, parameters: Parameters)
    case destroyUser(username: String)

    static let baseURLString = "https://example.com"

    var method: HTTPMethod
    {
        switch self {
        case .createUser:
            return .post
        case .readUser:
            return .get
        case .updateUser:
            return .put
        case .destroyUser:
            return .delete
        }
     }

    var path: String
    {
        switch self {
        case .createUser:
            return "/users"
        case .readUser(let username):
            return "/users/\(username)"
        case .updateUser(let username, _):
            return "/users/\(username)"
        case .destroyUser(let username):
            return "/users/\(username)"
        }
    }

    // MARK: URLRequestConvertible

    func asURLRequest() throws -> URLRequest
    {
        let url = try Router.baseURLString.asURL()

        var urlRequest = URLRequest(url: url.appendingPathComponent(path))
        urlRequest.httpMethod = method.rawValue

        switch self {
        case .createUser(let parameters):
            urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
        case .updateUser(_, let parameters):
            urlRequest = try URLEncoding.default.encode(urlRequest, with: parameters)
        default:
            break
        }

        return urlRequest
    }
}

7

SweetRouter 를 사용해 보지 않겠습니까 ? 라우터를 선언 할 때 가지고있는 모든 상용구를 제거하는 데 도움이되며 여러 환경과 같은 것을 지원하며 코드를 실제로 읽을 수 있습니다.

다음은 스위트 라우터가있는 라우터의 예입니다.

struct Api: EndpointType {
    enum Environment: EnvironmentType {
        case localhost
        case test
        case production

        var value: URL.Environment {
            switch self {
            case .localhost: return .localhost(8080)
            case .test: return .init(IP(126, 251, 20, 32))
            case .production: return .init(.https, "myproductionserver.com", 3000)
            }
        }
    }

    enum Route: RouteType {
        case auth, me
        case posts(for: Date)

        var route: URL.Route {
            switch self {
            case .me: return .init(at: "me")
            case .auth: return .init(at: "auth")
            case let .posts(for: date):
                return URL.Route(at: "posts").query(("date", date), ("userId", "someId"))
            }
        }
    }

    static let current: Environment = .localhost
}

사용 방법은 다음과 같습니다.

Alamofire.request(Router<Api>(at: .me))
Alamofire.request(Router<Api>(.test, at: .auth))
Alamofire.request(Router<Api>(.production, at: .posts(for: Date())))

4

작업 방법을 찾았고 라우터가있는 클래스를 만들었습니다. 요청에서 클래스 상속

request.swift 파일

class request{

    func login(user: String, password: String){
        /*use Router.login(params)*/
    }
    /*...*/
    enum Router: URLRequestConvertible {
        static let baseURLString = "http://example.com"
        static let OAuthToken: String?

        case Login([String: AnyObject])
        /*...*/

        var method: Alamofire.Method {
            switch self {
            case .Login:
                return .POST
            /*...*/
        }

        var path: String {
            switch self {
            case .Login:
                return "/login"
            /*...*/
            }
        }

        var URLRequest: NSURLRequest {
            switch self {
                case .Login(let parameters):
                    return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
                /*...*/
                default:
                    return mutableURLRequest
            }
        }
    }
}

requestContacts.swift 파일

class requestContacts: api{

    func getUser(id: String){
        /*use Router.getUser(id)*/
    }
    /*...*/

    enum Router: URLRequestConvertible {

        case getUser(id: String)
        case setUser([String: AnyObject])

        var method: Alamofire.Method {
            switch self {
                case .getUser:
                    return .GET
                case .setUser:
                    return .POST
                /*...*/
            }
        }

        var path: String {
            switch self {
            case .getUser(id: String):
                return "/user\(id)/"
            case .setUser(id: String):
                return "/user/"
            /*...*/
            }
        }
        // MARK: URLRequestConvertible

        var URLRequest: NSURLRequest {
            //use same baseURLString seted before
            let URL = NSURL(string: Router.baseURLString)!
                let mutableURLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(path))
                mutableURLRequest.HTTPMethod = method.rawValue

            if let token = Router.OAuthToken {
                mutableURLRequest.setValue("Bearer \(token)", forHTTPHeaderField: "Authorization")
            }
            switch self {
                /*...*/
                case .setUser(let parameters):
                    return Alamofire.ParameterEncoding.URL.encode(mutableURLRequest, parameters: parameters).0
                default: //for GET methods, that doesent need more
                    return mutableURLRequest
            }
        }
    }
}

그래서 아들 클래스는 부모로부터 Router의 매개 변수를 얻을 것이고, 당신은 어떤 아들에서도 Route.login을 사용할 수 있습니다. 여전히 짧은 URLRequest를 얻는 방법이 있는지 알지 못하므로 매개 변수를 반복해서 설정할 필요가 없습니다.


안녕, 나는 당신의 대답에서 말한 것처럼하려고 노력하고 있지만 POST 메서드를 사용하려고 할 때 여전히 GET 메서드 응답을 얻습니다. 예 : POST 메소드를 사용하여 사용자를 생성하는 대신 내 URL "/ users"에 액세스 할 때 GET 메소드 응답 인 모든 사용자 목록이 표시됩니다. 왜 이런 일이 일어나는지 아십니까? mutableURLRequest.HTTPMethod = method.rawValue아무것도 변경하지 않고 방법을 설정하는 것 같습니다 .
Renato Parreira

어떤 열거 형에 액세스 했습니까 ?? 당신이 게시물에 대한 열거 형을 선택하고 그 열거 값에 대한 POST를 설정해야합니다, 여기에 포스트 Router.setUser (...)입니다
데이비드 알레한드로 Londoño 메히아

여기에서 내 질문을 확인할 수 있습니까? 거기에서 모든 세부 사항을 제공합니다. 여기 링크입니다 : 질문
레나토 투 파레

4

URLRequestConvertible 프로토콜을 채택하는 유형을 사용하여 URL 요청을 구성 할 수 있습니다.

다음은 www.raywenderlich.com 에서 가져온 예입니다.

public enum ImaggaRouter : URLRequestConvertible{

  static let baseURL = "http://api.imagga.com/v1"
  static let authenticationToken = "XAFDSADGDFSG DAFGDSFGL"

  case Content, Tags(String), Colors(String)

  public var URLRequest: NSMutableURLRequest {
    let result: (path: String, method: Alamofire.Method, parameters: [String: AnyObject]) = {
      switch self {
      case .Content:
        return ("/content", .POST, [String: AnyObject]())
      case .Tags(let contentID):
        let params = [ "content" : contentID ]
        return ("/tagging", .GET, params)
      case .Colors(let contentID):
        let params = [ "content" : contentID, "extract_object_colors" : NSNumber(int: 0) ]
        return ("/colors", .GET, params)
      }
    }()

    let URL = NSURL(string: ImaggaRouter.baseURL)!
    let URLRequest = NSMutableURLRequest(URL: URL.URLByAppendingPathComponent(result.path))
    URLRequest.HTTPMethod = result.method.rawValue
    URLRequest.setValue(ImaggaRouter.authenticationToken, forHTTPHeaderField: "Authorization")
    URLRequest.timeoutInterval = NSTimeInterval(10 * 1000)

    let encoding = Alamofire.ParameterEncoding.URL
    return encoding.encode(URLRequest, parameters: result.parameters).0
  }
}

이 ImmageRouter를 다음과 같이 사용할 수 있습니다.

Alamofire.request(ImaggaRouter.Tags(contentID))
      .responseJSON{ response in

0

대 / 소문자 대신 UpdateUser (username : String, firstName : String, lastName : String, email : String)

당신은 만들 것입니다

struct UserAttributes
{
    let username: String
     ....
}

명명되지 않은 읽을 수없는 문자열의 클러스터 대신 매개 변수로 해당 모델 객체를 공급합니다.

case UpdateUser (매개 변수 : UserAttributes)

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.