TaskUtilities

TaskUtilities 是一个小型类型集合,可帮助排查异步 Swift 代码问题,并使同步代码可以安全地从异步上下文调用。

重要提示

RecursiveTaskLockLockedValue 仅适用于必须使同步代码可以安全地从多个异步任务调用的不常见情况。请谨慎使用这些类,因为它们是阻塞的,并且容易出现死锁,例如 哲学家就餐问题。如果可以选择编写异步代码,Swift Actor 是保护可变状态的更好方法。

RecursiveTaskLock

RecursiveTaskLock 提供了一个锁,可以从单个任务中递归锁定。当锁已被另一个任务锁定时,尝试从第二个任务获取锁将导致第二个任务阻塞,直到锁被释放。这使您可以在无法使用 Swift Actor 的情况下,使同步代码可以安全地从异步代码调用。

class Names {
    let lock = RecursiveTaskLock()
    var names: [String] = []

    func contains(name: String) -> Bool {
        lock.withLock {
            names.contains(name)
        }
    }

    func add(name: String) {
        lock.withLock {
            names.append(name)
        }
    }
}

为什么不用 NSLock?

在上面的示例中,NSLock 也能正常工作。但是在更复杂的情况下,如果可能存在嵌套锁,则需要递归锁。例如,如果 add 调用了 contains,或者 add 调用了可能调用 contains 的外部闭包,则需要递归锁。

为什么不用 NSRecursiveLock?

NSRecursiveLock 锁定一个线程,但 Swift 任务可以在线程之间移动。这使得 NSRecursiveLock 不安全,因为

  1. 在同一线程上运行的两个任务可能共享一个锁
  2. 在不同线程上运行的同一任务可能会阻塞等待锁

RecursiveTaskLock 将操作锁定到一个任务,而不是一个线程。

LockedValue

LockedValue 将锁包装在可变数据周围,以便一次只能从一个任务访问它。这比直接使用 RecursiveTaskLock 更可取,因为它可以防止您在不锁定锁的情况下意外访问数据。

class Names {
    let names = LockedValue<[String]>([])

    func contains(name: String) -> Bool {
        names.withLockedValue { names in
            names.contains(name)
        }
    }

    func add(name: String) {
        names.withLockedValue { names in
            names.append(name)
        }
    }
}

TaskPath

TaskPath 是一种调试辅助工具,可帮助您了解异步代码的任务结构。它允许您为任务命名,并从代码中的任意点检索该名称。

Task.detached {
    TaskPath.with(name: "Fetch image") {
        ...
    }
}

// In other code called from that task:
print(TaskPath.current) // Prints "{Task Fetch image}"

如果在代码中的不同点命名相同的任务,则调用结构将被保留

Task.detached {
    TaskPath.with(name: "Fetch image") {
        ...
        TaskPath.with(name: "Constructing request") {
            ...
        }
    }
}

// In other code called from the inner task:
print(TaskPath.current) // Prints "{Task Fetch image > Constructing request}"

安装

将包 https://github.com/samalone/task-utilities 添加到您的 Xcode 项目中,或添加

   .package(url: "https://github.com/samalone/task-utilities.git", from: "1.0.0"),

到您的 Package.swift 文件中的包依赖项。然后添加

   .product(name: "TaskUtilities", package: "task-utilities"),

到您的包目标的 target dependencies 中。