我正在尝试读取钥匙串,但文档警告我以下内容:
SecItemCopyMatching 会阻塞调用线程,因此如果从主线程调用,可能会导致应用程序的 UI 挂起。相反,从后台调度队列或异步函数调用 SecItemCopyMatching。
所以我想写一个在后台运行的异步方法。
actor Keychain {
public static let standard = Keychain()
public enum Error: Swift.Error {
case failed(String)
}
public func get(_ key: String) async throws -> Data? {
let backgroundTask = Task(priority: .background) {
var query: [String: Any] = [
type(of: self).klass : kSecClassGenericPassword,
type(of: self).attrAccount : key,
type(of: self).matchLimit : kSecMatchLimitOne,
type(of: self).returnData : kCFBooleanTrue as CFBoolean
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
if let errorMessage = SecCopyErrorMessageString(status, nil) {
throw Error.failed(String(errorMessage))
} else {
throw Error.failed("unsupported")
}
}
return item as? Data
}
return try await backgroundTask.value
}
}
我的问题是..演员是否已经使其线程安全?
通常我会添加一个
NSLock
以确保安全。
public func get(_ key: String) async throws -> Data? {
lock.lock()
defer { lock.unlock() }
(...)
return try await task.value
}
但是现在我收到警告
Instance method 'lock' is unavailable from asynchronous contexts; Use async-safe scoped locking instead; this is an error in Swift 6
。
那么我如何才能实现这一目标?
是的,演员将使其线程安全。不需要锁之类的。请参阅 WWDC 2021 视频使用 Swift Actor 保护可变状态。
顺便说一句,既然你已经在演员身上得到了这个,你真的不需要制作
get(_:)
一个 async
方法。 Actor 已经在后台线程上运行。因此,删除 async
限定符,然后删除 Task
:
actor Keychain {
...
public func get(_ key: String) throws -> Data? {
var query: [String: Any] = [
type(of: self).klass : kSecClassGenericPassword,
type(of: self).attrAccount : key,
type(of: self).matchLimit : kSecMatchLimitOne,
type(of: self).returnData : kCFBooleanTrue as CFBoolean
]
var item: CFTypeRef?
let status = SecItemCopyMatching(query as CFDictionary, &item)
guard status == errSecSuccess else {
if let errorMessage = SecCopyErrorMessageString(status, nil) {
throw Error.failed(String(errorMessage))
} else {
throw Error.failed("unsupported")
}
}
return item as? Data
}
}
顺便说一句,虽然在 Swift 并发中锁通常是不必要的,因为我们通常与 actor 实现类似的同步,但在某些边缘情况下我们可能会使用它们。在那些罕见的情况下,我们只需要小心使用它们。例如,编译器会警告我们,Swift 6 中可能不允许以下操作:
// Warning: Instance method 'lock' is unavailable from asynchronous contexts;
// Use async-safe scoped locking instead; this is an error in Swift 6
lock.lock()
someSynchronousStuff()
lock.unlock()
但是这是安全的,因为编译器可以推断锁的本地使用:
// OK
lock.withLock {
someSynchronousStuff()
}
但是您不能从内部调用异步例程
withLock
(因为我们从不想要跨并发上下文边界使用锁:
// Error: Cannot pass function of type '() async -> ()' to parameter expecting
// synchronous function type
lock.withLock {
await someSynchronousStuff()
}
例如,以下是 Xcode 15.3 产生的警告/错误:
看起来您正在使用所有局部变量(没有实例或静态变量),所以应该没问题。
为什么要把Keychain操作放在不需要的异步块中。没有它你就可以走了。钥匙串操作是线程安全的,这是由 Apple 保证的。请参阅此文档。 苹果
因此,不要使用异步块,并将所有 ui 更改放在主线程中(如果还没有)。 UI 更改需要在主线程中进行。