我正试图将我的头围绕在 Combine 上。
这是我想转换为Combine 的方法,以便它返回AnyPublisher。
func getToken(completion: @escaping (Result<String, Error>) -> Void) {
dispatchQueue.async {
do {
if let localEncryptedToken = try self.readTokenFromKeychain() {
let decryptedToken = try self.tokenCryptoHelper.decrypt(encryptedToken: localEncryptedToken)
DispatchQueue.main.async {
completion(.success(decryptedToken))
}
} else {
self.fetchToken(completion: completion)
}
} catch {
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
整个过程在单独的调度队列上执行,因为从 Keychain 读取和解密可能很慢。
我第一次尝试拥抱结合
func getToken() -> AnyPublisher<String, Error> {
do {
if let localEncryptedToken = try readTokenFromKeychain() {
let decryptedToken = try tokenCryptoHelper.decrypt(encryptedToken: localEncryptedToken)
return Result.success(decryptedToken).publisher.eraseToAnyPublisher()
} else {
return fetchToken() // also rewritten to return AnyPublisher<String, Error>
}
} catch {
return Result.failure(error).publisher.eraseToAnyPublisher()
}
}
Run Code Online (Sandbox Code Playgroud)
但是我如何将钥匙串中的读取和解密移动到单独的队列中?它可能看起来像
func getToken() -> AnyPublisher<String, Error> {
return Future<String, Error> { promise in
self.dispatchQueue.async {
do {
if let localEncryptedToken = try self.readTokenFromKeychain() {
let decryptedToken = try self.tokenCryptoHelper.decrypt(encryptedToken: localEncryptedToken)
promise(.success(decryptedToken))
} else {
// should I fetchToken().sink here?
}
} catch {
promise(.failure(error))
}
}
}.eraseToAnyPublisher()
}
Run Code Online (Sandbox Code Playgroud)
我如何从我的私有方法调用中返回一个发布者?(见代码注释)
有没有更漂亮的解决方案?
假设您\xe2\x80\x99已经重构readTokenFromKeyChain, decrypt, 并fetchToken返回AnyPublisher<String, Error>自身,那么您可以执行以下操作:
func getToken() -> AnyPublisher<String, Error> {\n readTokenFromKeyChain()\n .flatMap { self.tokenCryptoHelper.decrypt(encryptedToken: $0) }\n .catch { _ in self.fetchToken() }\n .receive(on: DispatchQueue.main)\n .eraseToAnyPublisher()\n}\nRun Code Online (Sandbox Code Playgroud)\n\n这将读取钥匙串,如果成功,则解密它,如果\xe2\x80\x99不成功,它将调用fetchToken. 完成所有这些后,它将确保最终结果传递到主队列上。
我认为 \xe2\x80\x99 是正确的一般模式。现在,让我们谈谈这个dispatchQueue:坦率地说,我不确定我是否看到这里有任何保证在后台线程上运行的东西,但让 \xe2\x80\x99s 想象你想要的要在后台队列中启动它,然后,您readTokenFromKeyChain可以将其分派到后台队列:
func readTokenFromKeyChain() -> AnyPublisher<String, Error> {\n dispatchQueue.publisher { promise in\n let query: [CFString: Any] = [\n kSecReturnData: true,\n kSecClass: kSecClassGenericPassword,\n kSecAttrAccount: "token",\n kSecAttrService: Bundle.main.bundleIdentifier!]\n\n var extractedData: AnyObject?\n let status = SecItemCopyMatching(query as CFDictionary, &extractedData)\n\n if\n status == errSecSuccess,\n let retrievedData = extractedData as? Data,\n let string = String(data: retrievedData, encoding: .utf8)\n {\n promise(.success(string))\n } else {\n promise(.failure(TokenError.failure))\n }\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n顺便说一句,\xe2\x80\x99s 使用一个简单的小方法,publisher我添加到DispatchQueue:
extension DispatchQueue {\n /// Dispatch block asynchronously\n /// - Parameter block: Block\n\n func publisher<Output, Failure: Error>(_ block: @escaping (Future<Output, Failure>.Promise) -> Void) -> AnyPublisher<Output, Failure> {\n Future<Output, Failure> { promise in\n self.async { block(promise) }\n }.eraseToAnyPublisher()\n }\n}\nRun Code Online (Sandbox Code Playgroud)\n\n为了完整起见,这是一个示例fetchToken实现:
func fetchToken() -> AnyPublisher<String, Error> {\n let request = ...\n\n return URLSession.shared\n .dataTaskPublisher(for: request)\n .map { $0.data }\n .decode(type: ResponseObject.self, decoder: JSONDecoder())\n .map { $0.payload.token }\n .eraseToAnyPublisher()\n}\nRun Code Online (Sandbox Code Playgroud)\n
| 归档时间: |
|
| 查看次数: |
1477 次 |
| 最近记录: |