Swift 的功能链和承诺
注意:目前仍处于实验状态。一切都有可能改变。我非常希望得到一些反馈。请通过 @ulrikdamm 在推特上给我写邮件。
Forbind 是一个库,它将表达式功能链引入到您的 Swift 代码中。它包含很多强大的组件,用于编写无状态和表达式的代码。它的主要特性包括
• 一个用于将表达式连接在一起并处理错误的绑定操作符 (=>)
• 一个用于合并两个潜在的可选值的合并操作符 (++)
• 用于更好地处理错误的 Result 类型
• 用于更好地处理异步值的 Promise 类型
• 一些 Foundation 和 UIKit 的扩展,它将这些 Forbind 概念引入了常用类中。
当您将这些特性组合起来,您就可以开始以全新方式编写代码。无需 if-lets,无需散布在代码中的错误处理,无需多层缩进的代码。
想法是您可以编写一串表达式,这些表达式最终产生一个结果。所有错误处理都留到最后一刻,即在展开结果时进行。对于异步操作也同样有效。不再需要 if-lets,不再需要 NSErrorPointer 检查,不再需要完成区块。您的代码将变成这样
if let data = readFile("file") {
if let result = parseJson(data, error: nil) as? NSDictionary {
if let thingy = parseData(result) {
handleResult(thingy)
}
}
}
到这样
readFile("file") => parseJson => parseData => handleResult
让我们尝试用 NSURLConnection 编做一个简单的网络请求。以下是它今天可能看起来像
class NetworkRequestExampleOldWay {
func handleResponse(response : NSURLResponse?, data : NSData?) -> String? {
if let data = data {
return NSString(data: data, encoding: NSUTF8StringEncoding) as? String
} else {
return nil
}
}
func performRequest(completion : (String?, NSError?) -> Void) {
if let url = NSURL(string: "http://ufd.dk") {
let request = NSURLRequest(URL: url)
NSURLConnection.sendAsynchronousRequest(request, queue: NSOperationQueue.mainQueue()) { response, data, error in
if let error = error {
completion(nil, error)
} else {
if let result = self.handleResponse(response, data: data) {
completion(result, nil)
} else {
completion(nil, nil)
}
}
}
}
}
}
这里有几个问题
• 所有的代码都需要嵌套在 if-let 中。
• 错误和数据可以在同一时间具有值。
• 您可能会忘记处理错误。
• 错误处理和逻辑混合在一起。
• 在多个不同位置进行错误处理。
• 多级缩进。
• 完成调用带有两个 nil?让我们希望这不会造成问题。
• 25 行。
下面是如何使用 Forbind 正确完成相同的任务
class NetworkRequestExampleWithForbind {
func handleResponse(response : NSURLResponse?, data : NSData?) -> String? {
return data => { NSString(data: $0, encoding: NSUTF8StringEncoding) as? String }
}
func performRequest() -> ResultPromise<String> {
let request = NSURL(string: "http://ufd.dk") => { NSURLRequest(URL: $0) }
let response = request ++ NSOperationQueue.mainQueue() => NSURLConnection.sendAsynchronousRequest
return response => handleResponse
}
}
这个问题通过这种方式解决
• 在进行任何错误处理之前,您定义了所有逻辑。
• 您被迫处理所有错误。
• 仅在接收最终结果时才进行缩进。
• 只有 9 行!!
有些约定与常规的代码编写方式有所不同
• 嵌套调用变成了链式调用(func1 => func2
而不是 func2(func1())
)
• 带有多个参数的链式调用用 ++
连结起来(arg1 ++ arg2 => func
而不是 func(arg1, arg2)
)
• 最后进行错误处理。如果出现任何问题,则跳过其余部分。
为了获得更多示例,请打开 Xcode 项目并运行 ForbindDemo iOS 应用。它包含一些实际示例。或者,您可以直接查看 动画演示、网络请求演示 和 打印 IP 演示 的源代码文件。
整个库都在以下文件中: bind.swift、combine.swift 和 dataStructures.swift。每个文件都有注释,详细说明了其工作原理。
如果您想了解更多关于绑定操作符背后的概念,您可以阅读我关于它的 博客文章。
它仍然非常实验性,所以我非常希望得到关于它的反馈。我现在不推荐将其用于产品代码。如果您有好的想法或只是有问题,请在 @ulrikdamm 上提交拉取请求或联系我。
仍需考虑的事项
• 项目的总体方向(是否存在一些根本性的缺陷?)
• 暂定取消(支持在暂定被释放时取消异步操作)
• 处理队列(目前暂定回调在操作完成的队列上运行)
• 为常见的 UIKit/AppKit/Foundation 方法添加更多扩展,以使用 Promise 和 Result 而不是 NSErrorPointer 和完成块。
• 对于大表达式,“表达式过于复杂,难以在合理时间内解决”