Taskig: Evil Genius Task Management for Swift

CI Status Version License Platform Swift

一个受 async/await 启发的库,使 Swift 中的异步编程成为一种乐趣!

Taskig 可以轻松地在不同的线程(例如,后台或主线程)上执行代码,然后通过 async/await 方法组合这些任务。Taskig 的灵感很大程度上来自于 AsyncTask (https://github.com/zhxnlai/AsyncTask)

特性

没有 Taskig

// submit a task to the global queue for background execution
DispatchQueue.global(qos: .userInteractive).async {
    let enhancedImage = self.applyImageFilter(image) // expensive operation taking a few seconds

    // update UI on the main queue
    DispatchQueue.main.async {
        self.imageView.image = enhancedImage

        UIView.animateWithDuration(0.3, animations: {
            self.imageView.alpha = 1
        }) { completed in
            // add code to happen next here
        }
    }
}

使用 Taskig

Task.async {
    let enhancedImage = self.applyImageFilter(image)

    Task.async(executionQueue: .main) { self.imageView.image = enhancedImage }

    UIView.animateTask(withDuration: 0.3) { self.label.alpha = 1 }.await()

    // add code to happen next here
}

它甚至允许您扩展现有类型

let (data, response) = try! NSURL(string: "www.google.com")!.await()

安装

CocoaPods

Taskig 可通过 CocoaPods 获得。要安装它,只需将以下行添加到您的 Podfile

pod "Taskig"

用法

在 Taskig 中,Task 代表异步操作的最终结果,就像其他库中的 Future 和 Promise 一样。它可以包装同步和异步 API。要创建 Task,请使用闭包初始化它。为了使其可重用,请编写返回任务的函数。

// synchronous API wrapped in task
func encrypt(message: String) -> Task<String> {
    return Task {
        encrypt(message)
    }
}

// asynchronous API wrapped in task
func get(URL: NSURL) -> Task<(NSData?, NSURLResponse?, NSError?)> {
    return Task {completionHandler in
        NSURLSession().dataTaskWithURL(URL, completionHandler: completionHandler).resume()
    }
}

要获取 Task 的结果,请使用 asyncawaitasync 就像 dispatch_async,您可以提供完成处理程序。相反,await 会阻塞当前线程并等待任务完成。为了避免主线程上的死锁,Taskig 包含一个前提条件检查,如果您尝试在主线程上调用 await,它将导致崩溃!

// async
encrypt(message).async { ciphertext in /* do somthing */ }
get(URL).async {(data, response, error) in /* do somthing */ }

// await
let ciphertext = encrypt(message).await()
let (data, response, error) = get(URL).await()

当您创建任务时,您可以指定应在哪个队列上执行该任务。在底层,Taskig 使用 dispatch 队列来执行任务,因此所有标准 dispatch 队列都可用于执行:background、utility、userInitiated、userInteractive 和 main,但您也可以使用应用程序特定的队列。

Task<Void>(executionQueue: .main) {
    print("On Main Thread")
    //Update UI here
}

Task<Int>(executionQueue: .background) { () -> Int in
    // Calculate something in the background
    return 42
}

组合任务

您可以使用多个 await 表达式来确保每个语句在执行下一个语句之前完成

Task {
    print(“downloading image”)
    var image = downloadImage.await()
    imageView.updateWithImage(image).await()
    
    print(“processing image”)
    image = processImage(image).await()
    imageView.updateWithImage(image).await()
    
    print(“finished)
}.async()

任务集合

Taskig 还支持任务的集合、字典和序列。在它们两者上,您都可以调用 awaitFirstawaitAll 以并行执行它们

// Get first result returned
let uLs = ["https://web1.swift.org", "https://web2.swift.org"]
let first = replicatedURLs.map(get).awaitFirst()

// Get all results
let messages = ["1", "2", "3"]
let all = messages.map(encrypt).awaitAll()

您可以使用 concurrency 参数控制并发并行任务的数量

let numbersStrings = (0...900).map{ String($0) }

// Maximum of 5 parallel tasks
let all = numbersStrings.map(encrypt).awaitAll(concurrency: 5)

处理错误

Swift 为 错误处理 提供了第一类支持。在 Taskig 中,ThrowableTask 接受一个抛出闭包并传播错误。

extension String: Error {}

func toStringExceptZero(number: Int) -> ThrowableTask<String> {
    return ThrowableTask<String> {
        guard number != 0 else {
            throw "FoundZero"
        }
    
        return "\(number)"
    }
}

do {
    try toStringExceptZero(number: 0).await()
} catch {
    // Prints "FoundZero" error
    print(error)
}

或者,您可以使用 awaitResult() 来获取结果枚举,该枚举可以是 .success(value)(带有任务结果值)或 .failure(error)(带有任务错误)。

if case let .failure(error) = toStringExceptZero(number: 0).awaitResult() {
    print(error)
}

扩展任务

Taskig 是面向协议的;它定义了 TaskTypeThrowableTaskType,并使用协议扩展提供了 asyncawaitResultawait 的默认实现。换句话说,这些协议很容易实现,您可以在任何符合它们的对象上 await。能够扩展任务非常强大,因为它允许任务封装状态和行为。

在以下示例中,通过扩展 NSURL 使其成为 TaskType,我们将数据获取作为 NSURL 类的一部分。要符合 TaskType 协议,只需指定一个 action 和返回类型。

extension URL: ThrowableTaskType {
    typealias ReturnType = (Data, HTTPURLResponse)
    
    public var executionQueue: DispatchQueue { return DispatchQueue.global() }

    public func action(completion: @escaping (TaskResult<(Data, HTTPURLResponse)>) -> Void) {
        URLSession.shared.dataTask(with: self) { (data, response, error) in
            guard error == nil else {
                completion(.failure(error!))
                return
            }
        
            completion(.success((data!, response as! HTTPURLResponse)))
        }.resume()
    }
}

此扩展允许我们编写以下代码

let (data, response) = try! NSURL(string: "www.google.com")!.await()

取消支持

ThrowableTaskType 任务可以通过 CancellableTaskType 协议支持取消。已取消的任务将抛出 CancellableTaskError.taskWasCancelled 错误。ThrowableTask 实现已经支持这一点。

var task = ThrowableTask<String> { () -> String in
    return "Foobar"
}

task.isCancelled = true

do {
    try task.await()
} catch {
    // CancellableTaskError.taskWasCancelled thrown
    print(error)
}

作者

Thomas Sempf

许可证

Taskig 在 MIT 许可证下可用。有关更多信息,请参阅 LICENSE 文件。