HoneyBee
介绍
HoneyBee 是一个 Swift promises 库,用于提高异步和并发编程的表达能力。HoneyBee 的设计遵循几个原则:
- 展示给我。 并发代码应该 看起来 像它实现的结构(请参见下面的示例)
- 自带代码。 HoneyBee 与您当前的异步和同步函数一起工作,无需修改。(通常如此。)
- 默认安全。 HoneyBee 强制执行适当的错误处理技术 - 同时减轻程序员负担。
文档
查看项目文档 在这里。
快速示例
示例:展示给我。
HoneyBee.start { root in
root.setErrorHandler(errorHandlingFunc)
.chain(func1)
.branch { stem in
stem.chain(func3)
.chain(func4)
+
stem.chain(func5)
.chain(func6)
}
.chain(func7)
}
在上面这个菜谱中,首先会调用 func1
。然后,func1
的结果将并行传递给 func3
和 func5
。在 func3
结束后,将调用 func4
并将 func3
的结果传递给它。同样,在 func5
结束后,将调用 func6
并将 func5
的结果传递给它。当 两个 func4
和 func6
都完成时,它们的 结果将被组合成一个元组并传递给 func7
。如果 任何 一个函数 throws
或异步返回一个 Error
,则将使用错误作为参数调用 errorHandlingFunc
。
示例:BYOC(自带代码)
func func1(completion: ([String]?, Error?) -> Void) {...}
func func2(string: String) throws -> Int {...}
func func3(int: Int, completion: (Error?) -> Void) {...}
func func4(int: Int, completion: (Result<String, Error>) -> Void) {...}
func func5(strings: [String], completion: () -> Void) {...}
func successFunc(strings: [String]) {...}
HoneyBee.start { root in
root.setErrorHandler(errorHandler)
.chain(func1)
.map { elem in
elem.chain(func2)
.chain(func3)
.chain(func4)
}
.chain(func5)
.chain(successFunc)
}
在上面这个菜谱中,我们看到了 HoneyBee 支持的六个函数签名。 func1
是 Objective-C 风格的错误回调异步调用。 func2
是同步 Swift 抛出异常的函数。 func3
可能会以错误结尾但不生成新的值。HoneyBee 会自动转发传入的值。 func4
是 Swift 风格的、基于泛型枚举的结果,可能包含一个值也可能包含一个错误。 func5
是异步的但不能产生错误(UI动画属于这一类)。而 successFunc
是一个简单的同步非异常函数。
HoneyBee 支持 34 种不同的函数签名。
示例:默认安全
“灾难金字塔”问题之一是错误处理很难正确进行。
func processImageData1(completionBlock: (result: Image?, error: Error?) -> Void) {
loadWebResource("dataprofile.txt") { dataResource, error in
loadWebResource("imagedata.dat") { imageResource, error in
decodeImage(dataResource, imageResource) { imageTmp, error in
dewarpAndCleanupImage(imageTmp) { imageResult in
completionBlock(imageResult, nil)
}
}
}
}
}
上面的简单“满意路径”代码没有错误处理。现在让我们添加最根本的错误处理形式
func processImageData2(completionBlock: (result: Image?, error: Error?) -> Void) {
loadWebResource("dataprofile.txt") { dataResource, error in
guard let dataResource = dataResource else {
completionBlock(nil, error)
return
}
loadWebResource("imagedata.dat") { imageResource, error in
guard let imageResource = imageResource else {
completionBlock(nil, error)
return
}
decodeImage(dataResource, imageResource) { imageTmp, error in
guard let imageTmp = imageTmp else {
completionBlock(nil, error)
return
}
dewarpAndCleanupImage(imageTmp) { imageResult in
guard let imageResult = imageResult else {
completionBlock(nil, error)
return
}
completionBlock(imageResult, nil)
}
}
}
}
}
看起来不太美观,对吧?这里仍然存在问题。这种形式的 processImageData
使其合同正确性依赖于被调用所有异步方法的合同正确性。如果其中一个方法未能调用其完成信号,或者回调了多次,会发生什么?如果方法调用完成信号但有两个 nil
值会发生什么?HoneyBee 会为你处理这些问题,这样你的方法的正确性就不依赖于依赖方法的正确性。让我们看看 Honeybee 的形式
func processImageData3(completionBlock: (result: Image?, error: Error?) -> Void) {
HoneyBee.start { root in
root.setErrorHandler { completionBlock(nil, $0)}
.branch { stem in
stem.chain(loadWebResource =<< "dataprofile.txt")
+
stem.chain(loadWebResource =<< "imagedata.dat")
}
.chain(decodeImage)
.chain(dewarpAndCleanupImage)
.chain{ completionBlock($0, nil) }
}
}
是不是干净多了?而且, bonus points,HoneyBee 实现允许我们并行化对 loadWebResource
的前两个异步调用,所以这个表单的性能也比其他更好的。
(如果你对 =<<
运算符感到好奇,它的发音是 bind
。它执行部分函数应用,"绑定"参数到函数。有关更多详情,请参阅 API 文档。)
错误诊断
诊断出问题的并发代码真的很难,对吗?但在 HoneyBee 下就不难了。考虑以下
func handleError(_ errorContext: ErrorContext) {
print(errorContext)
}
func stringToInt(string: String, callback: (Result<Int, Error>) -> Void) {
if let int = Int(string) {
callback(.success(int))
} else {
let error = NSError(domain: "couldn't convert string to int", code: -2, userInfo: ["string:": string])
callback(.failure(error))
}
}
HoneyBee.start { root in
root.setErrorHandler(handleError)
.insert(7)
.chain(String.init) // produces "7"
.chain(String.append =<< "dog") // produces "7dog"
.chain(stringToInt) // errors
.chain(successFunc) // not reached
}
打印的结果
subject = "7dog"
file = "/Users/HoneyBee/Tests/ErrorHandlingTests.swift"
line = 172
internalPath = 5 values {
[0] = "start: /Users/HoneyBee/Tests/ErrorHandlingTests.swift:167"
[1] = "chain: /Users/HoneyBee/Tests/ErrorHandlingTests.swift:169 insert"
[2] = "chain: /Users/HoneyBee/Tests/ErrorHandlingTests.swift:170 (Int) -> String"
[3] = "chain: /Users/HoneyBee/Tests/ErrorHandlingTests.swift:171 (String) -> String"
[4] = "chain: /Users/HoneyBee/Tests/ErrorHandlingTests.swift:172 (String, (FailableResult<Int>) -> ()) -> ()"
}
HoneyBee 可以精确指出发生错误的文件和行,以及到达该函数所采取的路径和传入的 "主题" 值。在大多数情况下,这可以将您的诊断搜索过程缩减到单个函数。
多个队列
默认情况下,HoneyBee 在全局后台队列上执行所有函数。那么,如果您需要处理主队列呢?
HoneyBee.start(on: DispatchQueue.main) { root in
root.setErrorHandler(handleError)
.chain(func1) // performed on main queue
.chain(func2) // same
}
很简单,不是吗?需要更改队列?about NSManagedObjectContext
?
HoneyBee.start(on: DispatchQueue.main) { root in
root.setErrorHandler(handleError)
.chain(func1) // performed on main queue
.setBlockPerformer(DispatchQueue.global())
.chain(func2) // performed on global background queue
.chain(func3) // performed on global background queue
.setBlockPerformer(myMOC)
.chain(func4) // performed on myMOC's internal queue.
}
HoneyBee 允许您完全控制哪些队列会调用您的函数。即使函数本身在它们被调用的队列之外回调,也是如此。
总结
HoneyBee 就是如此。灵活、简单且安全。并发的正确方式。如果你有任何问题,联系我们。