正确使用Alamofire的URLRequestConvertible

Ogr*_*amp 76 ios swift alamofire

我已经阅读了@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
        }
    }
    
    Run Code Online (Sandbox Code Playgroud)
  3. 传递参数有两种方法:

    case CreateUser([String: AnyObject])
    case ReadUser(String)
    case UpdateUser(String, [String: AnyObject])
    case DestroyUser(String)
    
    Run Code Online (Sandbox Code Playgroud)

    和(比如用户有4个参数)

    case CreateUser(String, String, String, String)
    case ReadUser(String)
    case UpdateUser(String, String, String, String, String)
    case DestroyUser(String)
    
    Run Code Online (Sandbox Code Playgroud)

    @mattt正在使用示例中的第一个.但这将导致路由器外部的"硬编码"参数名称(例如在UIViewControllers中).参数名称中的错字可能导致错误.
    其他人正在使用第二个选项,但在这种情况下,每个参数代表什么并不明显.
    什么是正确的方法呢?

cno*_*oon 105

好问题.让我们分别分解每一个.

在真实世界的API中,URLRequestConvertible的正确用法是什么?

URLRequestConvertible协议是一种轻量级方法,可确保给定对象可以创建有效对象NSURLRequest.实际上并没有一套严格的规则或指南强制您以任何特定方式使用此协议.它只是一个便利协议,允许其他对象存储正确创建所需的状态NSURLRequest.有关Alamofire的更多信息可以在这里找到.

我应该每个端点创建一个路由器吗?

当然不.那会破坏使用的全部目的Enum.Swift Enum对象非常强大,允许您共享大量的常见状态,并打开实际不同的部分.能够NSURLRequest用以下简单的东西创建一个非常强大的东西!

let URLRequest: NSURLRequest = Router.ReadUser("cnoon")
Run Code Online (Sandbox Code Playgroud)

我无法弄清楚为什么枚举用于构建路由器?为什么我们不使用静态方法的类?

正在使用枚举,因为它是在通用接口下表达多个相关对象的更简洁的方式.所有方法都共享所有方法.如果使用静态方法,则每个方法的每个案例都必须有一个静态方法.或者您必须在对象内使用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)"
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

要获取任何不同端点的方法,您可以调用相同的方法,而无需传入任何参数来定义您要查找的端点类型,它已经由您选择的情况处理.

let createUserMethod = Router.CreateUser.method
let updateUserMethod = Router.UpdateUser.method
Run Code Online (Sandbox Code Playgroud)

或者如果你想获得路径,那么相同类型的呼叫.

let updateUserPath = Router.UpdateUser.path
let destroyUserPath = Router.DestroyUser.path
Run Code Online (Sandbox Code Playgroud)

现在让我们尝试使用静态方法的相同方法.

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
}
Run Code Online (Sandbox Code Playgroud)

注意:如果您没有很多可以打开案例的属性或函数,那么枚举与结构相比没有很多优点.它只是一种具有不同语法糖的替代方法.

枚举可以最大化状态和代码重用.相关值还允许您执行一些非常强大的操作,例如对有些类似的对象进行分组,但具有令人难以置信的不同要求...例如NSURLRequest创建.

为枚举案例构造参数以提高可读性的正确方法是什么?(不得不将这个混合在一起)

这是一个很棒的问题.你已经列出了两个可能的选择.让我添加一个可能更适合您需求的三分之一.

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)
Run Code Online (Sandbox Code Playgroud)

如果您有关联的值,我认为为元组中的所有值添加显式名称会很有帮助.这确实有助于构建上下文.缺点是你必须在switch语句中重新声明这些值.

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

虽然这给你一个漂亮,一致的上下文,但它变得非常冗长.这些是您在Swift中的三个选项,其中一个是正确使用取决于您的用例.


更新

随着Alamofire 4.0的发布,URLRequestConvertible现在可以变得更聪明,也可以投掷.我们已经为Alamofire添加了全面的支持,用于处理无效请求并通过响应处理程序生成合理的错误.这个新系统在我们的README中有详细记录.

  • 谢谢.关于每个端点的一个路由器与构建路由器的答案只有一个问题(例如来自Alamofire页面的CRUD示例).难道你不认为如果我说5个端点,每个端点有3-4个方法,那就是15-20个`case`语句.对我来说,这看起来像一个巨大的方法.我不确定这是否会导致可读代码...... (8认同)
  • 是的,这是正确的@RenanKosicki.当你在路由器枚举中有太多的情况时,你肯定会达到一个不归路的地步.将它们分成逻辑组肯定是一个更理想的设计. (3认同)
  • 对于你的最后一条评论,如果你还有一堆函数,我认为你不想把20个不同的端点填充到一个枚举中.你的switch语句会很长,因此不太可读.那时肯定是代码味道.对我来说,一旦你的开关中遇到5或6个案例,你真的开始失去可读性. (2认同)
  • 至于你最后的评论@cnoon,(我读过以前的评论)你说的是(使用你的CRUD用户路由器的例子),如果我有一些属于不同上下文的请求,比如来自twitter和用户CRUD的请求帖子,那些将是两个分开的路由器? (2认同)

And*_*kas 7

这是enum RouterSwift 3中的最新版本,在Alamofire的Github上推荐使用。我希望您发现它对如何正确地使用实施路由器很有用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
    }
}
Run Code Online (Sandbox Code Playgroud)


Noo*_*ass 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
}
Run Code Online (Sandbox Code Playgroud)

以下是您将如何使用它:

Alamofire.request(Router<Api>(at: .me))
Alamofire.request(Router<Api>(.test, at: .auth))
Alamofire.request(Router<Api>(.production, at: .posts(for: Date())))
Run Code Online (Sandbox Code Playgroud)


小智 5

我找到了一种使用它的方法,我创建了一个包含路由器的类:从请求继承类

文件 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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

文件 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
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

所以子类将从父类获取路由器的参数,您甚至可以在任何子类中使用 Route.login。仍然,不知道有没有办法得到一个短的 URLRequest,所以我不需要一次又一次地设置参数


Abo*_*tef 5

采用 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
  }
}
Run Code Online (Sandbox Code Playgroud)

我们可以使用这个 ImageRouter 如下:

Alamofire.request(ImaggaRouter.Tags(contentID))
      .responseJSON{ response in
Run Code Online (Sandbox Code Playgroud)