You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何将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
    }
}

最终方案解析

  1. 线程安全保障:用Actor替代信号量,Actor天然保证内部方法串行执行,彻底避免多线程竞争,更符合Swift并发模型设计。
  2. 后台执行逻辑:Firebase的async版本fetch和activate方法本身会在后台处理网络请求与配置激活,无需额外指定后台队列;若需明确优先级,可将调用包裹在Task(priority: .background)中。
  3. 主线程返回结果:兼容旧闭包的扩展中,通过await MainActor.run确保回调在主线程执行;对于async/await版本的调用者,若需更新UI,可在调用后用await MainActor.run切换线程。

内容的提问来源于stack exchange,提问作者Michał Ziobro

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.24 04:07:11