Brisk 3.1.1

Brisk 3.1.1

测试已测试
语言语言 SwiftSwift
许可证 MIT
发布上次发布2017年3月
SwiftSwift 版本3.0
SPM支持 SPM

Jason Fieldman 维护。



Brisk 3.1.1

Brisk

Swift支持块和异步代码功能强大,但可能会导致嵌套的逻辑混乱,快速变得难以阅读和容易出错。

Brisk提供两种明显不同但相互补充的功能

  1. 提供简便的操作符来切换您函数的并发性(类似于async/await)
  2. 扩展DispatchQueue,提供一些函数以帮助使标准使用更简洁。
// Example: Making an asynchronous function be synchronous 
let (data, response, error) = <<-{ URLSession.shared.dataTask(url, completionHandler: $0).resume() }

版本控制

为了帮助Cocoapods版本控制语法,Brisk的所有版本将兼容Swift 2.2,其版本号将从主/次版本2.2开始,所有兼容Swift 2.3的版本将从主/次版本2.3开始,所有兼容Swift 3.0的版本将从主/次版本3.0开始,以此类推。

这意味着您的Cocoapods引入可以看起来像这样:

pod 'Brisk', '~> 2.2' # Latest version compatible with Swift 2.2
pod 'Brisk', '~> 2.3' # Latest version compatible with Swift 2.3
pod 'Brisk', '~> 3.0' # Latest version compatible with Swift 3.0
pod 'Brisk', '~> 3.1' # Latest version compatible with Swift 3.1

Brisk API在Swift 2.x中有所不同。请参阅 README_SWIFT2.md

快速浏览:并发旋转

考虑以下假设的异步API

// API we're given:
func findClosestPokemon(within: Double,
             completionHandler: (pokemon: Pokemon?, error: NSError?) -> Void)

func countPokeballs(completionHandler: (number: Int?, error: NSError?) -> Void)

func throwPokeballAt(pokemon: Pokemon,
           completionHandler: (success: Bool, error: NSError?) -> Void)

让我们假设所有完成处理程序都在主线程上被调用。我们想使此实用函数

// Utility we want:
func throwAtClosestPokemon(within: Double,
                completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void)

此函数表示将异步函数链式连接到一个辅助工具以进行单一用例的常见情况。仅使用标准的GCD库,您的函数可能看起来像这样

// The old way...
func throwAtClosestPokemon(within: Double,
                completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) {
    // Step 1
    findClosestPokemon(within: within) { pokemon, error in
        guard let p = pokemon where error == nil else {
            DispatchQueue.main.async {
                completionHandler(success: false, pokemon: nil, error: error)
            }
            return
        }

        // Step 2
        countPokeballs { number, error in
            guard let n = number where error == nil else {
                DispatchQueue.main.async {
                    completionHandler(success: false, pokemon: nil, error: error)
                }
                return
            }

            // Step 3
            throwPokeballAt(pokemon: p) { success, error in
                DispatchQueue.main.async {
                    completionHandler(success: success, error: error)
                }
            }
        }
    }
}

糟糕!如果您的异步逻辑需要分支,那么它看起来会更快地变差。让我们看看Brisk中的作用域/流程如何工作

// The new way...
func throwAtClosestPokemon(within: Double,
                completionHandler: (success: Bool, pokemon: Pokemon?, error: NSError?) -> Void) {

    // Run everything inside a specified async queue, or DispatchQueue.global()
    myQueue.async {

        // Step 1
        let (pokemon, error) = <<+{ findClosestPokemon(within: within, completionHandler: $0) }
        guard let p = pokemon where error == nil else {
            return completionHandler +>> (success: false, error: error)
        }

        // Step 2
        let (number, error2) = <<+{ countPokeballs($0) }
        guard let n = number where error2 == nil else {
            return completionHandler +>> (success: false, error: error2)
        }

        // Step 3
        let (success, error3) = <<+{ throwPokeballAt(pokemon: p, completionHandler: $0) }
        completionHandler +>> (success: success, error: error3)
    }
}

使用Brisk,异步函数可以使用看似同步的流程进行编码。异步方法背后的异步性质被自定义操作符隐藏了。与PromiseKit不同,所有返回值都保持在作用域内

同步调用异步函数

本节介绍将异步函数以同步方式调用的概念,通常是为了将多个异步操作链式连接。这本质上与PromiseKit的提供相同,但无需处理与它相关的多余缩进和作用域调整。

要查看实际的用例,请参阅上面的快速浏览示例。

当我们讨论异步函数时,它必须遵守以下特点:

  • 返回Void
  • 可以接受任意数量的输入参数
  • 有一个单一的“完成”参数,它接受一个函数的形式为(...) -> Void

这些都是适合的异步函数示例

func getAlbum(named: String, handler: (album: PhotoAlbum?, error: NSError?) -> Void)
func saveUser(completionHandler: (success: Bool) -> Void)
func verifyUser(name: String, password: String, completion: (valid: Bool) -> Void)

// Typical use of a function would look like:
getAlbum("pics") { photo, error in
    // ...
}

使用Brisk,您可以使用 <<+<<~<<- 运算符以阻塞调用线程的方式调用您的函数,直到您的函数调用其完成处理程序。

// <<+ will execute getAlbum on the main queue
let (album, error) = <<+{ getAlbum("pics", handler: $0) }

// <<~ will execute saveUser on the global concurrent background queue
let success        = <<~{ saveUser($0) }

// <<- will execute verifyUser immediately in the current queue (note that the
//     current thread will wait for the completion handler to be called before
//     returning the final value.)
let valid          = <<-{ verifyUser("myname", password: "mypass", completion: $0) }

// You can also specify *any* queue you want.  Here saveUser is called on myQueue.
let myQueue        = dispatch_queue_create("myQueue", nil)
let valid          = <<~myQueue ~~~ { saveUser($0) }

提示:对于需要调用在主线程上的函数(如UI更新),请使用 <<+。对于其他函数,请使用 <<-

在上述所有示例中,外部线程的执行将暂停,直到调用完成处理程序 $0。一旦调用 $0,传递给它的值将返回到原始赋值操作。

请注意,$0 处理程序可以容纳任意数量的参数(例如,上面的 getAlbum 可以接收 albumerror),但它必须分配给相同元组的变量。此外,请注意,无法提取 NSError 参数以将其转换为 do/try/catch 方法 - 您必须将 NSError 作为返回元组的一部分进行检查。

请注意,外部线程 将等待 直到 $0 被调用。 这意味着Brisk只能用于确保其完成处理程序将在未来的某个可确定性点上被调用的函数。它不适用于类似 NSNotification 处理程序这类开放式异步函数。

以异步方式调用同步函数

有很多理由要以异步方式调用同步函数。您在任何看到此模式的时候都可能会发生这种情况

dispatch_async(someQueue) {
    completionHandler(..)
}

您正在用三条线和缩进范围仅为了将一个函数调用路由到另一个队列。

这是一个示例,来自文档开头部分的快速查看示例。每次在主队列上调用完成处理程序时,都必须执行此路由。这对整体函数的可读性有负面影响,因为实际函数名被埋在了分发的范围中。如果能用一行就能完成,并且函数名在最前面,那岂不是很好?

Brisk中引入的 ~>>+>> 运算符可以被视为同步到异步的翻译器。两者的主要区别在于,+>> 运算符将任务发送到主队列,而 ~>> 运算符允许您指定队列(或默认使用并发后台队列)。

对于下面的示例,考虑以下常规同步函数

func syncReturnsVoid() { }
func syncReturnsParam(p: Int) -> Int { return p+1 }
func syncReturnsParamTuple(p: Int) -> (Int, String) { return (p+1, "\(p+1)") }

使用函数和其参数之间的中缀运算符快速将同步函数调度到另一个队列。

dispatch_async(someQueue) {

    // syncReturnsVoid() is called on the main thread
    syncReturnsVoid +>> ()

    // syncReturnsParam(p: 3) is called on the main thread
    // Note in this case the return value is ignored!
    syncReturnsParam +>> (p: 3)

    // syncReturnsVoid() is called on the global concurrent background queue
    syncReturnsVoid ~>> ()

    // syncReturnsParam(p: 3) is called on the global concurrent background queue
    // Note in this case the return value is ignored!
    syncReturnsParam ~>> (p: 3)

    let otherQueue = dispatch_queue_create("otherQueue", nil)

    // syncReturnsVoid() is called on otherQueue
    syncReturnsVoid ~>> otherQueue ~>> ()

    // syncReturnsParam(p: 3) is called on otherQueue
    // Note in this case the return value is ignored!
    syncReturnsParam ~>> otherQueue ~>> (p: 3)
}

您还可以使用后缀方式使用这些运算符,以获得更函数式的语法

dispatch_async(someQueue) {    
    let otherQueue = dispatch_queue_create("otherQueue", nil)

    // The following three lines are equivalent
    syncReturnsParam~>>.on(otherQueue).async(p: 3)
    syncReturnsParam~>>otherQueue~>>(p: 3)
    syncReturnsParam ~>> otherQueue ~>> (p: 3)
}

在上述所有示例中,都忽略了返回值。对于返回 Void(如大多数完成处理程序)的同步函数来说,这通常是可以的。因为这些函数是异步调用的,所以您必须异步处理返回值。

dispatch_async(someQueue) {

    // syncReturnsParam(p: 3) is called on the main thread
    // Its response is also handled on the main thread
    syncReturnsParam +>> (p: 3) +>> { i in print(i) } // prints 4

    // syncReturnsParam(p: 3) is called on the main thread
    // Its response is handled on the global concurrent background queue
    // Note the positions and difference between +>> and ~>>
    syncReturnsParam +>> (p: 3) ~>> { i in print(i) } // prints 4

    // syncReturnsParamTuple(p: 3) is called on the global concurrent background queue
    // Its response is handled on an instantiated queue
    syncReturnsParamTuple ~>> (p: 3) ~>> otherQueue ~>> { iInt, iStr in print(pInt) }

    // Using the more functional style
    syncReturnsParam~>>.on(otherQueue).async(p: 3) +>> { i in print(i) }    
}

可选

当您正在路由的函数是可选的时,您必须使用 ?~>>?+>> 运算符来引用函数

func myTest(param: Int, completionHandler: (Int -> Int)? = nil) {

    // These will cause a compiler error because the handler is optional:
    completionHandler +>> (param)
    completionHandler+>>.async(param) +>> { i in print(i) }

    // Instead use these:
    completionHandler ?+>> (param)
    completionHandler?~>>.async(param)

    // For anything past the initial function, use normal operators:
    //     (+>> instead of ?+>>) --v
    completionHandler ?+>> (param) +>> { i in print(i) }

}

请注意,如果您使用可选的语法

-- Swift 3.1 does not allow ? characters at the beginning of postfix
operators so you can no longer explicitly choose a queue name
like ".main".  You can still use queues implicitly with the operator, like
```completionHandler?+>>.async(param)```

Swift 3.x LibDispatch 扩展

Brisk扩展了 DispatchQueue 的函数,使 async 函数更简洁

/// LibDispatch:
func asyncAfter(deadline: DispatchTime,
                     qos: DispatchQoS = default,
                   flags: DispatchWorkItemFlags = default,
                 execute: () -> Void)

// Brisk allows you to specify time/intervals as a Double instead of DispatchTime.
// It also allows you to capture the timer used to dispatch the block, in case
// you want to cancel it.
func async(after seconds: Double,
                  leeway: QuickDispatchTimeInterval? = nil,
                     qos: DispatchQoS = .default,
                   flags: DispatchWorkItemFlags = [],
           execute block: @escaping () -> Void) -> DispatchSourceTimer

同时考虑安排一个块以间隔重复运行

// LibDispatch Requires:
let timer = DispatchSource.makeTimerSource(flags: ..., queue: ...)
timer.setEventHandler(qos: ..., flags: ..., handler: ...)
timer.scheduleRepeating(deadline: ..., interval: ..., leeway: ...)
timer.resume()

// Brisk allows you to schedule timers in one function, and passes the timer
// into the block so it can be canceled based on logic inside or outside the handler.
func async(every interval: Double,
               startingIn: Double? = nil,
               startingAt: NSDate? = nil,
                   leeway: QuickDispatchTimeInterval? = nil,
                      qos: DispatchQoS = .default,
                    flags: DispatchWorkItemFlags = [],
            execute block: @escaping (_ tmr: DispatchSourceTimer) -> Void) -> DispatchSourceTimer

另一个新函数允许您根据 operationId 将多个异步调用合并为单个执行。这在有几个同时进行的异步操作希望触发一个块发生(但你只想让该块执行一次)时非常有用。

func once(operationId: String,
       after interval: Double? = nil,
              at date: NSDate? = nil,
               leeway: QuickDispatchTimeInterval? = nil,
                  qos: DispatchQoS = .default,
                flags: DispatchWorkItemFlags = [],
        execute block: @escaping () -> Void) -> DispatchSourceTimer

上述函数有几种变体。有关详细信息,请参阅 BriskDispatch.swift

弃用的 Swift 2.x GCD 扩展

以下代码示例展示了 Brisk 为 Swift 2.x 语法提供的 GCD 扩展。这些代码相对直观。有关每个方法的更多信息,请参阅其注释部分。它们包含在 Swift 3.x 版本中以提高向后兼容性。

dispatch_main_async {
    // Block runs on the main queue
}

dispatch_main_sync {
    // Block runs on the main queue; this function does not return until
    // the block completes.
}

dispatch_bg_async {
    // Block runs on the global concurrent background queue
}

dispatch_async("myNewQueue") {
    // Block runs on a brisk-created serial queue with the specified string ID.
    // Calling this function multiple times with the same string will reuse the
    // named queue.  Useful for dynamic throw-away serial queues.
}

dispatch_main_after(2.0) {
dispatch_after(2.0, myQueue) {
    // Block is called on specified queue after specified number of seconds using
    // a loose leeway (+/- 0.1 seconds).
}

dispatch_main_after_exactly(2.0) {
dispatch_after_exactly(2.0, myQueue) {
    // Block is called on specified queue after specified number of seconds using
    // as tight a timer leeway as possible.  Useful for animation timing but
    // uses more battery power.
}

dispatch_main_every(2.0) { timer in
dispatch_every(2.0, myQueue) { timer in
dispatch_main_every_exact(2.0) { timer in
dispatch_every_exact(2.0, myQueue) { timer in
    // Block is run on specified thread every N seconds.
    // Stop the timer with:
    dispatch_source_cancel(timer)
}

dispatch_main_once_after(2.0, "myOperationId") {
dispatch_once_after(2.0, myQueue, "myOperationId") {
    // Block runs after specified time on specified queue.  The block is
    // only executed ONCE -- repeat calls to this function with the same
    // operation ID will reset its internal timer instead of calling the
    // block again.  Useful for calling a completion block after several
    // disparate asynchronous methods (e.g. saving the database to disk
    // after downloading multiple records on separate threads.)
}

dispatch_each(myArray, myQueue) { element in
    // Each element in the array has this block called with it as a parameter.
    // Should be used on a concurrent queue.
}