我已经阅读了几本教程,来自@mattt的README,但无法弄清楚几件事情。
现实世界API中URLRequestConvertible
的正确用法是什么?看起来我是通过为所有API实现URLRequestConvertible
协议来创建一个路由器 - 它几乎不可读。我应该为每个端点创建一个路由器吗?
第二个问题很可能是由于缺乏使用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中)。 参数名称中的错字可能导致错误 其他人正在使用第二个选项,但在这种情况下,每个参数代表什么并不明显 什么是正确的方法呢?
答案 0 :(得分:103)
很棒的问题。让我们分别分解每一个。
现实世界API中URLRequestConvertible的正确用法是什么?
URLRequestConvertible
协议是一种轻量级方法,可确保给定对象可以创建有效的NSURLRequest
。实际上并没有一套严格的规则或指南强制您以任何特定方式使用此协议。它只是一个便利协议,允许其他对象存储正确创建NSURLRequest
所需的状态。有关Alamofire的更多信息可以在here找到。
我应该为每个端点创建一个路由器吗?
绝对不是。这会破坏使用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添加了全面的支持,用于处理无效请求并通过响应处理程序生成合理的错误。我们的README中详细记录了这个新系统。
答案 1 :(得分: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())))
答案 2 :(得分:5)
以下是Swift 3中的最新enum Router
,建议Alamofire's 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
}
}
答案 3 :(得分:4)
我找到了一种使用它的方法,我创建了一个带有路由器的类: 继承请求中的类
file 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
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
}
}
}
}
所以son类将从父级获得Router的参数,你甚至可以在任何儿子中使用Route.login。 仍然,不知道是否有办法获得一个简短的URLRequest,所以我不需要一次又一次地设置参数
答案 4 :(得分:3)
采用URLRequestConvertible协议的类型可用于构造URL请求。
的示例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
答案 5 :(得分:0)
而不是UpdateUser(用户名:字符串,名字:字符串,姓氏:字符串,电子邮件:字符串)
你会做
struct UserAttributes
{
let username: String
....
}
并以THAT模型对象作为参数,而不是一群未命名的不可读字符串
案例UpdateUser(参数:UserAttributes)