HoneyBee 3.0.0

HoneyBee 3.0.0

测试已测试
Lang语言 SwiftSwift
许可证 MIT
发布上次发布2020年10月
SPM支持 SPM

Alex Lynch 维护。



HoneyBee 3.0.0

  • Alex Lynch

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 的结果将并行传递给 func3func5。在 func3 结束后,将调用 func4 并将 func3 的结果传递给它。同样,在 func5 结束后,将调用 func6 并将 func5 的结果传递给它。当 两个 func4func6 都完成时,它们的 结果将被组合成一个元组并传递给 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 就是如此。灵活、简单且安全。并发的正确方式。如果你有任何问题,联系我们