正确使用Alamofire的URLRequestConvertible
我已经阅读了@mattt的几个教程,README,但无法弄清楚几件事情。
-
在真实世界API中
URLRequestConvertible
的正确用法是什么? 看起来好像我将通过为所有API实现URLRequestConvertible
协议来创build一个路由器 – 它几乎不可读。 我应该创build一个路由器每个端点? -
第二个问题很可能是由于缺乏Swift语言的经验造成的。 我不明白为什么使用
enum
来build立路由器? 为什么我们不使用静态方法的类? 这里是一个例子(来自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 } }
-
有两种方法可以传递参数:
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中)。 input参数名称会导致错误。
其他人正在使用第二个选项,但在这种情况下,每个参数所代表的并不明显。
什么才是正确的做法呢?
很好的问题。 我们分别分解每一个。
在真实世界API中URLRequestConvertible的正确用法是什么?
URLRequestConvertible
协议是确保给定对象可以创build有效的NSURLRequest
一种轻量级方法。 目前还没有一套严格的规则或指导方针,迫使您以任何特定的方式使用此协议。 这只是一个方便的协议,允许其他对象存储正确创buildNSURLRequest
所需的状态。 有关阿拉莫菲尔的更多信息可以在这里find。
我应该创build一个路由器每个端点?
当然不。 这将打败使用Enum
的整个目的。 Swift枚举对象非常强大,可以让你分享大量的通用状态,并切换实际上不同的部分。 能够用如下简单的东西来创build一个NSURLRequest
是非常强大的!
let URLRequest: NSURLRequest = Router.ReadUser("cnoon")
我不明白为什么使用枚举来build立路由器? 为什么我们不使用静态方法的类?
一个枚举正在被使用,因为它是一个在一个公共接口下expression多个相关对象的更加简洁的方式。 所有的方法都是共享的。 如果你使用静态方法,你必须为每个方法的每个情况有一个静态方法。 或者你将不得不在对象内部使用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.Method { 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)" } } }
要获得任何不同端点的方法,您可以调用相同的方法,而不必传入任何参数来定义要查找的端点types,它已经由您select的情况处理。
let createUserMethod = Router.CreateUser.method let updateUserMethod = Router.UpdateUser.method
或者如果你想获得path,相同types的调用。
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 }
注意:如果没有许多属性或函数可以打开这些情况,那么枚举不会比结构有更多的优点。 这只是一个不同的语法糖的替代方法。
枚举可以最大化状态和代码重用。 相关的值还允许你做一些非常强大的事情,比如分组有些类似的对象,但是具有令人难以置信的不同的需求,比如创buildNSURLRequest
。
什么是正确的方法来构build枚举案例的参数,以提高可读性? (不得不把这个混在一起)
这是一个了不起的问题。 你已经提出了两个可能的select。 让我补充一点,可能会更适合你的需求。
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)
在有关联值的情况下,我认为为元组中的所有值添加显式名称会很有帮助。 这确实有助于构build上下文。 缺点是你必须在你的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添加了全面的支持来处理无效请求,并通过响应处理程序生成明智的错误。 我们的自述文件详细logging了这个新系统。
你为什么不尝试使用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())))
我find了一个方法来处理它,我创build了一个包含路由器的类:从请求中inheritance类
文件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协议的types可以用来构造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
这里是最新的enum Router
在Swift 3,这是build议在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 } }