如何将Firebase Remote Config闭包代码改写为async/await版本?
Firebase Remote Config 闭包代码转 async/await 实现方案
需求背景
需要将访问Firebase Remote Config的旧闭包代码改写为async/await版本,原代码如下:
final class RemoteConfigService { private let isDebugEnabled: Bool private let semaphore = DispatchSemaphore(value: 1) private let concurrentQueue = DispatchQueue.global(qos: .background) init(isDebugEnabled: Bool) { self.isDebugEnabled = isDebugEnabled } func fetch( defaultValuesPlistFile: PlistFile, expirationDuration: TimeInterval, onComplete: @escaping (RemoteConfig) -> Void, onError: @escaping (Error?) -> Void ) { concurrentQueue.async { [weak self] in guard let strongSelf = self else { return } strongSelf.semaphore.wait() let expirationDuration = !strongSelf.isDebugEnabled ? expirationDuration : 0 let defaultValues = defaultValuesPlistFile.plist?.dictionary as? [String: NSObject] let remoteConfig = RemoteConfig.remoteConfig() remoteConfig.setDefaults(defaultValues) remoteConfig.fetch(withExpirationDuration: expirationDuration) { [remoteConfig] status, error in strongSelf.semaphore.signal() if status == .success || status == .throttled { remoteConfig.activate { _, _ in DispatchQueue.main.async { onComplete(remoteConfig) } } } else { DispatchQueue.main.async { onError(error) } } } } } }
Xcode已为FirebaseRemoteConfig的闭包方法生成了async版本,但遇到三个核心问题:
- 原代码用信号量同步RemoteConfig访问,需在async/await版本保留该线程安全机制
- 原代码在后台队列执行配置获取,需在async代码中实现相同的后台执行逻辑
- 原代码通过
DispatchQueue.main.async在主线程返回结果,新代码也要保证这一点
尝试的实现方案
初始async/await尝试
不确定是否保留了同步机制、是否在后台执行,且未处理主线程返回逻辑:
func fetch(defaultValuesPlistFile: PlistFile, expirationDuration: TimeInterval) async throws -> RemoteConfig { try await Task.detached(priority: .background) { [isDebugEnabled] in let expirationDuration = !isDebugEnabled ? expirationDuration : 0 let defaultValues = defaultValuesPlistFile.plist?.dictionary as? [String: NSObject] let remoteConfig = RemoteConfig.remoteConfig() remoteConfig.setDefaults(defaultValues) let status = try await remoteConfig.fetch(withExpirationDuration: expirationDuration) guard status == .success || status == .throttled else { throw FeatureStatusError.unsupported } _ = try await remoteConfig.activate() return remoteConfig }.value }
思路更新:用Actor实现线程安全
利用Actor的串行执行特性替代信号量,天然保证RemoteConfig访问的线程安全:
actor RemoteConfigService { let remoteConfig = RemoteConfig.remoteConfig() private let isDebugEnabled: Bool init(isDebugEnabled: Bool) { self.isDebugEnabled = isDebugEnabled } func fetch(defaultValuesPlistFile: PlistFile, expirationDuration: TimeInterval) async throws -> RemoteConfig { let expirationDuration = !isDebugEnabled ? expirationDuration : 0 let defaultValues = defaultValuesPlistFile.plist?.dictionary as? [String: NSObject] remoteConfig.setDefaults(defaultValues) let status = try await remoteConfig.fetch(withExpirationDuration: expirationDuration) guard status == .success || status == .throttled else { throw FeatureStatusError.unsupported } _ = try await remoteConfig.activate() return remoteConfig } }
为兼容旧闭包代码,添加非隔离扩展处理主线程回调:
extension RemoteConfigService { nonisolated func fetch( defaultValuesPlistFile: PlistFile, expirationDuration: TimeInterval, onComplete: @escaping (RemoteConfig) -> Void, onError: @escaping (Error?) -> Void ) { Task { do { let remoteConfig = try await fetch(defaultValuesPlistFile: defaultValuesPlistFile, expirationDuration: expirationDuration) await MainActor.run { onComplete(remoteConfig) } } catch { await MainActor.run { onError(error) } } } } }
思路更新2:优化Actor初始化逻辑
将默认配置初始化移至Actor构造方法,简化fetch方法逻辑:
final actor RemoteConfigService { let remoteConfig = RemoteConfig.remoteConfig() private let isDebugEnabled: Bool private let expirationDuration: TimeInterval init(isDebugEnabled: Bool, defaultValuesPlistFile: PlistFile, expirationDuration: TimeInterval) { self.isDebugEnabled = isDebugEnabled self.expirationDuration = expirationDuration let defaultValues = defaultValuesPlistFile.plist?.dictionary as? [String: NSObject] remoteConfig.setDefaults(defaultValues) } func fetch() async throws -> RemoteConfig { let expirationDuration = !isDebugEnabled ? expirationDuration : 0 let status = try await remoteConfig.fetch(withExpirationDuration: expirationDuration) guard status == .success || status == .throttled else { throw FeatureStatusError.unsupported } _ = try await remoteConfig.activate() return remoteConfig } }
最终方案解析
- 线程安全保障:用
Actor替代信号量,Actor天然保证内部方法串行执行,彻底避免多线程竞争,更符合Swift并发模型设计。 - 后台执行逻辑:Firebase的async版本
fetch和activate方法本身会在后台处理网络请求与配置激活,无需额外指定后台队列;若需明确优先级,可将调用包裹在Task(priority: .background)中。 - 主线程返回结果:兼容旧闭包的扩展中,通过
await MainActor.run确保回调在主线程执行;对于async/await版本的调用者,若需更新UI,可在调用后用await MainActor.run切换线程。
内容的提问来源于stack exchange,提问作者Michał Ziobro
相关产品推荐
相关产品推荐

