Swift中UITableView异步Firebase请求的顺序保持问题
解决异步请求后维持数组顺序及TableView显示异常问题
核心问题分析
你的代码逻辑本身已经能保证最终结果顺序与eventIds一致,但TableView短暂显示错误顺序的原因大概率是旧数据残留、Firebase缓存提前回调或UI更新时机冲突。以下是针对性的优化方案:
一、确保结果顺序绝对匹配输入ID
现有通过字典收集结果再按原ID顺序组装的逻辑是正确的,可以用compactMap简化写法,同时强化日期判断的可读性:
func downloadRequestEventsFromFirebase(withIds: [String], completion: @escaping (_ events: [EventObject]) -> Void) { let currentDateTime = Date() // 建议将DateFormatter设为静态属性,避免重复创建消耗性能 static let dateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.dateFormat = "MM dd, yyyy" return formatter }() var foundEvents = [String: EventObject]() let group = DispatchGroup() for eventId in withIds { group.enter() // 强制从服务器获取数据,避免Firebase缓存导致的提前回调 FirebaseReference(.Event).document(eventId).getDocument(source: .server) { (snapshot, error) in defer { group.leave() } guard let snapshot = snapshot, snapshot.exists else { return } let event = EventObject(_dictionary: snapshot.data()!) guard let eventDate = Self.dateFormatter.date(from: event.eventDate) else { return } // 简化日期判断逻辑 let isCurrentOrFuture = currentDateTime <= eventDate || currentDateTime.longDate() == eventDate.longDate() if isCurrentOrFuture { foundEvents[eventId] = event } } } group.notify(queue: .main) { // 用compactMap直接按原ID顺序组装数组,逻辑更简洁 let eventsArray = withIds.compactMap { foundEvents[$0] } completion(eventsArray) } }
二、修复TableView短暂显示错误顺序的问题
1. 加载前清空旧数据
在发起新请求前,先清空现有数据并刷新TableView,避免旧内容残留:
private func loadUserRequests() { print("loading event requests") guard let currentUser = FUser.currentUser() else { return } // 加载开始时清空旧数据,立即刷新UI DispatchQueue.main.async { self.requestEventPairs.removeAll() self.requestsTableView.reloadData() } FirebaseListener.shared.downloadArtistRequestsFromFirebase(withId: currentUser.objectId) { [weak self] artistRequests in guard let self = self else { return } let eventIds = artistRequests.map { $0.eventId } FirebaseListener.shared.downloadRequestEventsFromFirebase(withIds: eventIds) { eventObjects in // 优化配对逻辑:转字典后按原请求顺序直接取值,效率远高于firstWhere遍历 let eventDict = Dictionary(uniqueKeysWithValues: eventObjects.map { ($0.eventId, $0) }) let temporaryRequestEventPairs = artistRequests.compactMap { request in eventDict[request.eventId].map { (request, $0) } } DispatchQueue.main.async { self.requestEventPairs = temporaryRequestEventPairs print("count: \(self.requestEventPairs.count)") self.requestsTableView.reloadData() } } } }
2. 避免重复触发加载
检查loadUserRequests的调用时机(比如是否同时在viewDidLoad和viewWillAppear中调用),确保同一时间只有一个请求在执行,防止多次回调交替更新UI。
3. 线程安全保障
确保requestEventPairs仅在主线程修改,现有代码已通过DispatchQueue.main.async实现,避免多线程写入导致的数组混乱。
额外优化建议
- 避免循环引用:在闭包中使用
[weak self]防止内存泄漏,如上述loadUserRequests中的处理。 - 错误处理增强:可以将completion的返回类型改为
Result<[EventObject], Error>,统一处理网络请求、数据解析等错误场景。 - 日期格式化性能:将
DateFormatter设为静态属性,避免每次调用函数重复创建,提升性能。
内容的提问来源于stack exchange,提问作者Dani
相关产品推荐
相关产品推荐

