一尘不染

正确使用Alamofire的URLRequestConvertible

swift

我读过一些教程,@ 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
        }
    }
  1. 有两种方法可以传递参数:
        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选项,但在那种情况下,每个参数所代表的含义一点都不明显。
正确的方法是什么?


阅读 728

收藏
2020-07-07

共1个答案

一尘不染

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

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

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

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

当然不。那会破坏使用的全部目的Enum。SwiftEnum对象具有惊人的强大功能,可让您共享大量的公共状态,并打开实际上不同的部分。能够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添加了全面支持,以处理无效请求并通过响应处理程序生成明智的错误。我们的自述文件中详细记录了此新系统。

2020-07-07