正确使用Alamofire的URLRequestConvertible


78

我读过一些教程,@ mattt的自述文件,但无法弄清楚几件事。

  1. URLRequestConvertible现实世界中API的正确用法是什么?好像我将通过URLRequestConvertible为所有API实现协议来创建一个路由器一样,几乎无法读取。我应该为每个端点创建一个路由器吗?

  2. 第二个问题很可能是由于缺乏Swift语言经验引起的。我不知道为什么enum要用来建造路由器?为什么我们不将类与静态方法一起使用?这是一个示例(来自Alamofire的自述文件)

    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中)对参数名称进行“硬编码”。参数名称中的错字可能会导致错误。
    其他人正在使用2nd选项,但在那种情况下,每个参数所代表的含义一点都不明显。
    正确的方法是什么?

Answers:


110

好问题。让我们分别分解每个。

在实际的API中,URLRequestConvertible的正确用法是什么?

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

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

当然不。那会破坏使用Enum。Swift Enum对象具有强大的功能,使您可以共享大量的公共状态,并打开实际上不同的部分。能够NSURLRequest使用以下简单的内容创建一个,确实非常强大!

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

我不知道为什么将枚举用于构建路由器?为什么我们不将类与静态方法一起使用?

使用枚举是因为它是在公共接口下表达多个相关对象的更为简洁的方法。所有情况之间共享所有方法。如果使用静态方法,则每种方法的每种情况都必须有一个静态方法。否则,您将不得不在对象内部使用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创建。

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

这是一个了不起的问题。您已经提出了两个可能的选择。让我添加一个可能更适合您需求的三分之一。

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添加了全面支持,以处理无效请求并通过响应处理程序生成明智的错误。我们的自述文件中详细记录了此新系统。


8
谢谢。关于您的答案,关于每个端点一个路由器还是一个构建路由器,只需回答一个问题(例如Alamofire页面上的CRUD示例)。您是否不认为如果我说5个端点,则每个端点都有3-4个方法,即15-20个case语句。对我来说,这似乎是一个巨大的方法。我不确定这是否会导致可读代码...
OgreSwamp 2015年

1
关于第二个答案(枚举与静态方法)-对我来说,这个漏洞是在枚举/类中隐藏一个实现。我不需要知道外面的方法或路径。我想打电话给我,Router.createUser("test@mail.com", "....")并有一个解释服务器结果的块。所有详细信息(方法,路径,API根目录等)都可以为路由器专用-很好。
OgreSwamp,2015年

2
对于您的最后一条评论,如果您也有很多功能,我认为您不希望将20个不同的端点填充到一个枚举中。您的switch语句将是如此之长,以至于难以阅读。在这一点上绝对可以编码气味。对我来说,一旦您在交换机中收到5或6个案例,您就真的开始失去可读性。
cnoon 2015年

2
对于您的最后一个@cnoon评论,(我阅读了之前的评论)您是在说(以您的CRUD用户路由器为例),如果我有一些属于不同上下文的请求,例如来自Twitter和User CRUD的请求帖子,将是两个分开的路由器?
Renan Kosicki 2015年

3
是的,这是正确的@RenanKosicki。当您在Router枚举中包含过多案例时,您肯定会达到无法回报的地步。将它们分成逻辑组无疑是一个更理想的设计。
cnoon 2015年

8

这是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
    }
}

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方法的响应。例如:当我访问URL“ / users”而不是使用POST方法创建用户时,我将获取所有用户的列表,这是GET方法的响应。知道为什么会这样吗?似乎设置方法mutableURLRequest.HTTPMethod = method.rawValue不变。
Renato Parreira

您访问了哪个枚举??您必须为POST选择一个枚举,并为该枚举值设置一个POST,此处的帖子为Router.setUser(...)
David AlejandroLondoñoMejía,

可以在这里检查我的问题吗?在那里,我提供了所有详细信息。这里是链接:问题
Renato Parreira

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
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.