ios 正确使用 Alamofire 的 URLRequestConvertible
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/28333241/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Proper usage of the Alamofire's URLRequestConvertible
提问by OgreSwamp
I've read couple tutorials, README from @mattt but can't figure out couple things.
我已经阅读了几个教程,来自@mattt 的自述文件,但无法弄清楚几件事。
What is the proper usage of
URLRequestConvertible
in real world API? It looks like if I will create one router by implementingURLRequestConvertible
protocol for all API - it will be barely readable. Should I create one Router per endpoint?Second question most likely caused by lack of experience with Swift language. I can't figure out why
enum
is used for building router? Why we don't use class with static methods? here is an example (from Alamofire's 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 } }
There are 2 ways to pass parameters:
case CreateUser([String: AnyObject]) case ReadUser(String) case UpdateUser(String, [String: AnyObject]) case DestroyUser(String)
and (say user has 4 parameters)
case CreateUser(String, String, String, String) case ReadUser(String) case UpdateUser(String, String, String, String, String) case DestroyUser(String)
@mattt is using the first one in the example. But that will lead to "hardcoding" parameters' names outside the router (e.g. in UIViewControllers). Typo in parameter name can lead to error.
Other people are using 2nd option, but in that case it not obvious at all what each parameter represents.
What will be the right way to do it?
URLRequestConvertible
现实世界 API的正确用法是什么?看起来如果我通过URLRequestConvertible
为所有 API实现协议来创建一个路由器- 它几乎不可读。我应该为每个端点创建一个路由器吗?第二个问题很可能是由于缺乏 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 } }
传递参数有两种方式:
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 中)“硬编码”参数的名称。参数名称中的拼写错误可能会导致错误。
其他人正在使用第二个选项,但在这种情况下,每个参数代表什么根本不明显。
什么是正确的方法呢?
回答by cnoon
Great questions. Let's break down each one individually.
很好的问题。让我们逐一分解。
What is the proper usage of URLRequestConvertible in real world API?
URLRequestConvertible 在现实世界 API 中的正确用法是什么?
The URLRequestConvertible
protocol is a lightweight way to ensure a given object can create a valid NSURLRequest
. There's not really a strict set of rules or guidelines that exist forcing you to use this protocol in any particular way. It's merely a convenience protocol to allow other objects to store state required to properly create the NSURLRequest
. Some more information relating to Alamofire can be found here.
该URLRequestConvertible
协议是确保给定对象可以创建有效NSURLRequest
. 并没有一套严格的规则或指导方针强制您以任何特定方式使用此协议。它只是一个方便的协议,允许其他对象存储正确创建NSURLRequest
. 可以在此处找到有关 Alamofire 的更多信息。
Should I create one Router per endpoint?
我应该为每个端点创建一个路由器吗?
Definitely not. That would defeat the entire purpose of using an Enum
. Swift Enum objects are amazingly powerful allowing you to share a large amount of common state, and switch on the parts that actually different. Being able to create an NSURLRequest
with something as simple as the following is really powerful!
当然不。这将违背使用Enum
. Swift Enum 对象非常强大,允许您共享大量公共状态,并打开实际不同的部分。能够创建一个NSURLRequest
像下面这样简单的东西真的很强大!
let URLRequest: NSURLRequest = Router.ReadUser("cnoon")
I can't figure out why enum is used for building router? Why we don't use class with static methods?
我不明白为什么枚举用于构建路由器?为什么我们不使用带有静态方法的类?
An enum is being used because it is a much more concise way of expressing multiple related objects under a common interface. All the methods are shared between all the cases. If you used static methods, you'd have to have a static method for each case for each method. Or you would have to use an Obj-C styled enum inside the object. Here's a quick example of what I mean.
使用枚举是因为它是在公共接口下表达多个相关对象的更简洁的方式。所有方法在所有案例之间共享。如果您使用静态方法,则必须为每种方法的每种情况设置一个静态方法。或者您必须在对象内使用 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)"
}
}
}
To get the method of any of the different endpoints, you can call the same method without having to pass in any parameters to define what type of endpoint you are looking for, it's already handled by the case you select.
要获取任何不同端点的方法,您可以调用相同的方法而无需传入任何参数来定义您要查找的端点类型,它已经由您选择的案例处理。
let createUserMethod = Router.CreateUser.method
let updateUserMethod = Router.UpdateUser.method
Or if you want to get the path, same types of calls.
或者,如果您想获取路径,请使用相同类型的调用。
let updateUserPath = Router.UpdateUser.path
let destroyUserPath = Router.DestroyUser.path
Now let's try the same approach using static methods.
现在让我们使用静态方法尝试相同的方法。
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
}
NOTE: If you don't have many properties or functions that switch on the cases, then an enum doesn't present many advantages over a struct. It is simply an alternative approach with different syntactic sugar.
注意:如果您没有很多属性或函数可以打开案例,那么枚举与结构相比并没有太多优势。它只是一种具有不同语法糖的替代方法。
Enums can maximize state and code reuse. The associated values also allow you to do some really powerful things like grouping objects that are somewhat similar, but have incredibly different requirements...such as NSURLRequest
creation.
枚举可以最大化状态和代码重用。关联的值还允许您执行一些非常强大的操作,例如将有些相似但具有难以置信的不同要求的对象分组……例如NSURLRequest
创建。
What is the right way to construct parameters for enum cases to improve readability? (had to mash this one together)
为枚举案例构造参数以提高可读性的正确方法是什么?(必须把这个混在一起)
That's a terrific question. You've already laid out two possible options. Let me add a third that may suit your needs a bit better.
这是一个很棒的问题。您已经列出了两个可能的选项。让我添加第三个可能更适合您的需求。
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)
In cases where you have associated values, I think it can be helpful to add explicit names for all the values in the tuple. This really helps build the context. The downside is that you then have to redeclare those values in your switch statements like so.
在有关联值的情况下,我认为为元组中的所有值添加显式名称会很有帮助。这确实有助于构建上下文。缺点是您必须像这样在 switch 语句中重新声明这些值。
static var method: String {
switch self {
case let CreateUser(username: username, firstName: firstName, lastName: lastName, email: email):
return "POST"
default:
return "GET"
}
}
While this gives you a nice, consistent context, it gets pretty verbose. Those are your three options at the moment in Swift, which one is the correct one to use depends on your use case.
虽然这为您提供了一个很好的、一致的上下文,但它变得非常冗长。这是您目前在 Swift 中的三个选项,哪个是正确的使用取决于您的用例。
Update
更新
With the release of Alamofire 4.0 , the URLRequestConvertible
can now be MUCH smarter and can also throw. We've added full support into Alamofire for handling invalid requests and generating sensible errors through the response handlers. This new system is documented in detail in our README.
随着 Alamofire 4.0 的发布,URLRequestConvertible
现在可以更智能,也可以投掷。我们在 Alamofire 中添加了全面支持,用于处理无效请求并通过响应处理程序生成合理的错误。我们的README中详细记录了这个新系统。
回答by AndreasLukas
Here is the up to date enum Router
in Swift 3, which is recommend on Alamofire's Github. I hope you find it useful in terms of how to properly implement a Router with URLRequestConvertible
.
这是enum Router
Swift 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
}
}
回答by Noobass
Why don't you try to use SweetRouter. It will help you to remove all the boilerplate that you have when declaring a Router and it also supports such things as multiple environments and your code will be really readible.
你为什么不尝试使用SweetRouter。它将帮助您删除声明 Router 时拥有的所有样板文件,它还支持多种环境等内容,您的代码将非常易读。
Here's an example of the Router with sweet router:
这是一个带有甜蜜路由器的路由器示例:
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
}
And here's how you would use it:
以下是您将如何使用它:
Alamofire.request(Router<Api>(at: .me))
Alamofire.request(Router<Api>(.test, at: .auth))
Alamofire.request(Router<Api>(.production, at: .posts(for: Date())))
回答by David Alejandro Londo?o Mejía
I found a way to work with it, I created a Class with the Router in it: inherit classes from a request
我找到了一种使用它的方法,我创建了一个包含路由器的类:从请求继承类
file request.swift
文件 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
}
}
}
}
file requestContacts.swift
文件 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
}
}
}
}
so the son class will get paramethers of Router from the parent, and you can even use Route.login in any son. still, dont know if there is a way to get a short URLRequest, so i dont need to set parameters again and again
所以子类将从父类获取路由器的参数,您甚至可以在任何子类中使用 Route.login。仍然,不知道有没有办法得到一个短的 URLRequest,所以我不需要一次又一次地设置参数
回答by Abo3atef
Types adopting the URLRequestConvertible protocol can be used to construct URL requests.
采用 URLRequestConvertible 协议的类型可用于构造 URL 请求。
Here is an example taken from www.raywenderlich.com
这是从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
}
}
and we can use this ImmageRouter as the following:
我们可以使用这个 ImageRouter 如下:
Alamofire.request(ImaggaRouter.Tags(contentID))
.responseJSON{ response in