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

主线程调用报错:API请求成功但错误弹窗致应用崩溃

iOS网络请求异常时弹窗崩溃的原因与修复方案

报错原因

URLSession的dataTask闭包默认在后台线程执行。你的代码存在明显的线程处理不一致问题:

  • 成功获取数据时,特意用DispatchQueue.main.async把回调切到了主线程
  • 但网络错误、数据解析错误等失败场景,直接在后台线程调用了completion回调
  • 而所有UI操作(比如present弹窗)必须在主线程执行,因此调用showAlertWith时触发了"Call must be made on main thread"崩溃

修复方案

推荐在ApiService层统一处理线程切换,保证所有completion回调都在主线程执行,这样上层VC不需要关心线程问题,代码更健壮。

修改后的ApiService代码

import Foundation

class ApiService {
    
    private var dataTask: URLSessionDataTask?
    
    // MARK: - Get popular movies data
    func getPopularMoviesData(completion: @escaping (Result<MovieData, Error>) -> Void) {
        
        let popularMoviesURL = "https://api.themoviedb.org/3/movie/popular?api_key=4e0be2c22f7268edffde97481d49064a&language=en-US&page=1"
        
        guard let url = URL(string: popularMoviesURL) else {return}
        
        dataTask = URLSession.shared.dataTask(with: url) { (data, response, error) in
            // 统一把所有回调切换到主线程
            DispatchQueue.main.async {
                // Handle Error
                if let error = error {
                    completion(.failure(error))
                    print("DataTask error: \(error.localizedDescription)")
                    return
                }
                
                guard let response = response as? HTTPURLResponse else {
                    print("Empty Response")
                    // 可选:自定义空响应错误并回调
                    return
                }
                print("Response status code: \(response.statusCode)")
                
                guard let data = data else {
                    print("Empty Data")
                    // 可选:自定义空数据错误并回调
                    return
                }
                
                do {
                    let decoder = JSONDecoder()
                    let jsonData = try decoder.decode(MovieData.self, from: data)
                    completion(.success(jsonData))
                } catch let error {
                    completion(.failure(error))
                }
            }
        }
        dataTask?.resume()
    }
}

备选方案(仅在VC层临时处理)

如果不想修改ApiService,也可以在VC的失败回调里手动切换到主线程调用弹窗:

private func loadPopularMoviesData() {
    apiService.getPopularMoviesData { [weak self] (result) in
        switch result {
        case .success(let listOf):
            print(result)
        case .failure(let error):
            // 切换到主线程再执行UI操作
            DispatchQueue.main.async {
                self?.showAlertWith(title: "Could not connect!", message: "Please check your internet connection \n or try again later")
            }
            print("Error processing json data: \(error)")
        }
    }
}

额外优化提示

  • 修正代码拼写错误:"Plese"改为"Please"
  • 空响应、空数据场景建议自定义Error并通过completion回调,让VC可以统一处理所有错误弹窗逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 08:25:31