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

Swift 3下载数据时界面冻结问题求解(线程相关)

解决Swift 3聊天列表加载时界面冻结的问题

首先,界面冻结的核心原因是主线程被耗时的网络请求、数据处理操作阻塞了。iOS要求所有UI操作必须在主线程执行,但耗时的逻辑(比如数据解析、循环处理、同步网络请求)必须放到后台线程,处理完成后再切回主线程更新UI。

针对你的代码,我会分步骤说明修改点:

1. 把耗时的数据处理逻辑移到后台线程

在getChatList的SocketIO回调中,你对messages的遍历、字典解析、调用getOponentsDataForChat这些操作都是耗时的,应该放到后台队列执行,避免阻塞主线程。

修改后的getChatList大致如下:

func getChatList() {
    SocketIOManager.sharedInstance.getChatList { [weak self] (messages) in
        guard let s = self else { return }
        s.timer?.invalidate()
        
        // 把耗时的数据处理移到后台队列(选择用户发起级别的QoS,优先级适配聊天场景)
        DispatchQueue.global(qos: .userInitiated).async {
            if messages.isEmpty {
                // 空列表的UI更新必须切回主线程
                DispatchQueue.main.async {
                    s.activityIndicator.stopAnimating()
                    s.tableView.emptyDataSetSource = s
                    s.tableView.emptyDataSetDelegate = s
                    s.tableView.reloadData()
                }
            } else {
                // 遍历处理messages,这部分耗时操作放在后台执行
                messages.forEach({ (dict) in
                    guard dict["last_message"].string != nil else { return }
                    if dict["target"].string != "group" {
                        if let messageID = dict["_id"].string {
                            let str = messageID.components(separatedBy: "_")
                            let number1 = str[0]
                            let number2 = str[1]
                            if number1 != number2 || number1 != Defaults[.msisdn] {
                                let number = s.returnOpponentData(messageID: messageID)
                                let message = LastMessage(dictionary: dict)
                                // 若getOponentsDataForChat是异步请求,回调内更新UI必须切主线程
                                ChatNetworkManager.shared.getOponentsDataForChat(number: number, lastMessage: message, handler: { [weak s] result in
                                    DispatchQueue.main.async {
                                        // 比如更新chats数组、刷新表格这类UI相关操作都放这里
                                        // s?.chats.append(...)
                                        // s?.tableView.reloadData()
                                    }
                                })
                            }
                        }
                    } else {
                        // 群聊逻辑同样放在后台处理,完成后切主线程更新UI
                        // ...
                    }
                })
                
                // 所有数据处理完成后,停止加载动画并刷新UI
                DispatchQueue.main.async {
                    s.activityIndicator.stopAnimating()
                    s.tableView.reloadData()
                }
            }
        }
    }
}

2. 确保所有UI操作都在主线程执行

你已经在空列表处理时用了DispatchQueue.main.async,但还要注意:

  • 任何更新UI的操作(比如activityIndicator.stopAnimating()、tableView.reloadData()、设置数据源代理)都必须放在主线程。
  • getOponentsDataForChat的回调中,只要涉及更新数据源或UI,都要切回主线程(上面代码中已标注)。

3. 检查viewDidLoad中的线程逻辑

在viewDidLoad里,activityIndicator.startAnimating()是主线程操作没问题,但SocketIO的statusChange回调如果涉及UI更新或后续耗时操作,也要切回主线程:

socket.on(clientEvent: .statusChange) { [unowned self] data, ack in
    DispatchQueue.main.async {
        switch self.socket.status {
        // 处理状态变更的UI逻辑
        // ...
        }
        self.setupChat()
    }
}

关键注意点

  • 绝对不要用DispatchQueue.main.sync!sync会阻塞当前线程等待主线程完成,一旦当前线程是主线程就会触发死锁,你需要的是async,让主线程异步执行UI操作,不阻塞后台处理流程。
  • 后台队列的QoS选择:.userInitiated适合用户主动触发的操作(比如加载聊天列表),优先级较高,能保证处理速度。
  • 如果ChatNetworkManager.shared.getOponentsDataForChat是异步网络请求,它本身已经在后台执行,但回调大概率不在主线程,所以更新UI时必须手动切回主线程。

这样修改后,耗时的数据处理都在后台线程执行,主线程只负责UI更新,就不会出现界面冻结的问题了。

内容的提问来源于stack exchange,提问作者Muslim Ismailov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:28:18