[ios] Alamofire의 URLRequestConvertible의 적절한 사용

@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)에서 “하드 코딩”매개 변수 이름으로 이어질 것입니다. 매개 변수 이름을 잘못 입력하면 오류가 발생할 수 있습니다.
    다른 사람들은 두 번째 옵션을 사용하고 있지만이 경우 각 매개 변수가 무엇을 나타내는 지 전혀 명확하지 않습니다.
    이를 수행하는 올바른 방법은 무엇입니까?



답변

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

실제 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 에 자세히 설명되어 있습니다.


답변

다음은 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
    }
}


답변

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())))


답변

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

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를 얻는 방법이 있는지 알지 못하므로 매개 변수를 반복해서 설정할 필요가 없습니다.


답변

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


답변

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

당신은 만들 것입니다

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

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

case UpdateUser (매개 변수 : UserAttributes)


답변