如何确保Swift Manager类在API数据拉取完成后同步初始化
解决Swift DataManager初始化时等待异步数据加载的问题
问题核心
你的代码在init中启动了Task执行异步数据拉取,但Swift的初始化是同步完成的,shared实例会立刻创建完成,此时data仍为空数组,后续提前访问fetchedData就会拿到空值,这是问题的根本原因。
可行解决方案
方案1:异步静态方法创建单例(推荐)
将单例的创建改为异步方法,确保数据加载完成后才返回可用实例,从根源避免空数据问题:
import Foundation class DataManager { static private var _shared: DataManager? private let apiService: APIService private let data: [String] // 私有初始化,仅允许通过异步方法创建实例 private init(apiService: APIService, initialData: [String]) { self.apiService = apiService self.data = initialData } // 异步单例获取方法 static func shared() async throws -> DataManager { if let shared = _shared { return shared } let apiService = APIService() let initialData = try await apiService.fetchData() let manager = DataManager(apiService: apiService, initialData: initialData) _shared = manager return manager } var fetchedData: [String] { return data } }
使用示例:
在异步上下文内调用,确保拿到的实例已加载完成数据:
// 在ViewModel或其他异步代码块中 let manager = try await DataManager.shared() let loadedData = manager.fetchedData // 此时数据一定已准备好
方案2:异步数据访问方法(兼容现有单例结构)
保持现有初始化逻辑,但将数据访问改为异步方法,确保访问时等待数据加载完成:
class DataManager { static let shared = DataManager() private let apiService: APIService private var data: [String]? private let loadLock = NSLock() private init(apiService: APIService = APIService()) { self.apiService = apiService // 后台启动数据加载 Task { try await loadInitialData() } } private func loadInitialData() async throws { loadLock.lock() defer { loadLock.unlock() } if data != nil { return } // 避免重复加载 data = try await apiService.fetchData() } // 异步获取数据,保证返回时已加载完成 func getFetchedData() async throws -> [String] { if let existingData = data { return existingData } try await loadInitialData() return data! } }
使用示例:
let loadedData = try await DataManager.shared.getFetchedData()
方案3:同步等待异步任务(不推荐,易死锁)
如果必须在init内完成数据加载,可以用信号量强制等待,但主线程调用会导致死锁,仅适合非主线程初始化场景:
private init(apiService: APIService = APIService()) { self.apiService = apiService let semaphore = DispatchSemaphore(value: 0) Task { do { self.data = try await self.fetchInitialData() print("Initial data fetched: \(self.data)") } catch { print("Error fetching initial data: \(error)") } semaphore.signal() } // 等待异步任务完成,注意:主线程调用会死锁! semaphore.wait() }
选择建议
- 优先采用方案1,符合Swift异步编程设计理念,确保实例完全可用后才对外暴露,彻底避免空值隐患。
- 方案2适合无法修改单例创建逻辑的场景,通过异步访问方法保证数据可用性。
- 方案3尽量避免,容易引发死锁和主线程阻塞,严重影响App性能。
内容的提问来源于stack exchange,提问作者cole
相关产品推荐
相关产品推荐

