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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 00:37:51