我读过几本教程,也读过@mattt的自述,但我还是搞不懂几件事。
>
第二个问题很可能是由于缺乏使用Swift语言的经验引起的。我不明白为什么要用
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中)“硬编码”参数名称。参数名称中的错误可能会导致错误。br>其他人使用的是第二个选项,但在这种情况下,每个参数表示什么根本就不明显。br>正确的方法是什么?
null
在真实世界的API中,URLRequestConvertible的正确用法是什么?
是否应该为每个端点创建一个路由器?
绝对不能。这将破坏使用
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
}
注意:如果没有很多属性或函数可以打开事例,那么枚举并不比结构具有太多优势。这只是一种具有不同句法糖的替代方法。
枚举可以最大化状态和代码重用。相关联的值还允许您做一些非常强大的事情,比如对有些相似,但有着难以置信的不同需求的对象进行分组。。。。例如
为枚举事例构造参数以提高可读性的正确方法是什么?(不得不把这个捣碎)
这是个很棒的问题。你已经提出了两个可能的选择。让我补充第三条,也许更适合你的需要。
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的发布,
下面是Swift3中最新的
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
}
}
你为什么不试着用SweetRouter。它将帮助您删除所有的样板,当您声明一个路由器,它还支持像多个环境这样的东西,您的代码将是真正可读的。
null
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())))