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

如何修复同步URL加载及Thread 1: EXC_BAD_INSTRUCTION错误?

问题

开发一款基于CSV数据的App时,在ContentView调用城市类的getStats()函数出现两个问题:

  1. 点击提交按钮后,代码行let state: String = new["state", String.self][0] ?? "There was an error"触发Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)错误
  2. 终端提示:Synchronous URL loading of https://raw.githubusercontent.com/shadowdragon2805/CrimeData/main/docs/csv/crime_data_w_population_and_crime_rate.csv should not occur on this application's main thread as it may lead to UI unresponsiveness. Please switch to an asynchronous networking API such as URLSession.

想了解:

  • 同步加载是否会导致上述崩溃错误?
  • 如何将现有代码切换为异步加载?

出错的getStats()函数代码

func getStats(input: String) -> DataFrame.Slice{

    let columns = ["zip", "primary_city", "state", "county", "latitude", "longitude"]
    let statsColumns = ["county_name","crime_rate_per_100000","CPOPARST","CPOPCRIM", "MURDER","RAPE","ROBBERY","AGASSLT","BURGLRY","LARCENY","MVTHEFT","ARSON","population"
    ]
    do{
        let convert1 = try DataFrame(contentsOfCSVFile: URL(string: "https://raw.githubusercontent.com/shadowdragon2805/CrimeData/main/docs/csv/zip_code_database.csv")!, columns: columns, types: ["zip": .string, "primary_city": .string, "state": .string, "county": .string, "latitude": .double, "longitude": .double])
        
        convert = convert1
        
    }catch{
        Swift.print("Convert Error")
    }
    
    do{
        let stats1 = try DataFrame(contentsOfCSVFile: URL(string: "https://raw.githubusercontent.com/shadowdragon2805/CrimeData/main/docs/csv/crime_data_w_population_and_crime_rate.csv")!, columns: statsColumns)
       
        stats = stats1
    }catch{
        Swift.print("Stats Error")
    }
    

    convert.combineColumns("latitude", "longitude", into: "location") { (latitude: Double?, longitude: Double?) -> CLLocation? in
        guard let latitude = latitude, let longitude = longitude else{
            return nil
        }
        return CLLocation(latitude: latitude, longitude: longitude)
    }//combine columns
    
    let new = convert.filter(on: "zip", String.self){$0 == input}//find zip code
    let state: String = new["state", String.self][0] ?? "There was an error"//find state
    let loc: CLLocation = new["location", CLLocation.self][0]!
    location = loc//use later

    let county = new.rows[0][3, String.self]
    let countyStats = stats.filter(on: "county_name", String.self){$0 == (county ?? "error") + ", " + state}//what I want
    return countyStats//return my stats
}

ContentView中调用代码

func submit() {
    print("The city/county/zip inputted: \(input)")
    
    let stringStats = test.getStats(input: input.zip)
    print(stringStats)
    resignKeyboard()
}

解答

1. 同步加载是否会导致崩溃错误?

同步加载本身不会直接触发EXC_BAD_INSTRUCTION崩溃,但它会引发两个间接问题:

  • 主线程被阻塞,导致UI卡顿甚至无响应,极端情况下系统可能触发watchdog机制终止App(但你的错误不属于这种情况)
  • 更关键的是:同步加载CSV时,若网络请求失败或数据解析出错,convert或stats可能未被正确初始化,后续执行convert.filter后得到的new是空切片,直接访问索引[0]会触发数组越界,这才是你看到的崩溃根源。

终端的提示只是警告主线程阻塞问题,但崩溃的直接原因是空数据切片的索引越界,而同步加载下不完善的错误处理(仅打印错误却继续执行后续代码)放大了这个问题。

2. 如何切换为异步加载?

核心思路是把网络加载和数据处理移到后台线程,用async/await(Swift 5.5+推荐)处理异步结果,同时完善错误校验,避免空数据访问。

方案:用async/await重构getStats()

假设你使用的DataFrame库支持同步解析本地数据,先通过URLSession异步下载CSV内容再解析:

// 重构为异步函数,用Result类型统一处理成功/失败
func getStats(input: String) async -> Result<DataFrame.Slice, Error> {
    let columns = ["zip", "primary_city", "state", "county", "latitude", "longitude"]
    let statsColumns = ["county_name","crime_rate_per_100000","CPOPARST","CPOPCRIM", "MURDER","RAPE","ROBBERY","AGASSLT","BURGLRY","LARCENY","MVTHEFT","ARSON","population"]
    
    do {
        // 异步下载第一个CSV文件
        let zipCSVURL = URL(string: "https://raw.githubusercontent.com/shadowdragon2805/CrimeData/main/docs/csv/zip_code_database.csv")!
        let zipCSVData = try await URLSession.shared.data(from: zipCSVURL).0
        let convert1 = try DataFrame(contentsOfCSVData: zipCSVData, columns: columns, types: ["zip": .string, "primary_city": .string, "state": .string, "county": .string, "latitude": .double, "longitude": .double])
        
        // 异步下载第二个CSV文件
        let crimeCSVURL = URL(string: "https://raw.githubusercontent.com/shadowdragon2805/CrimeData/main/docs/csv/crime_data_w_population_and_crime_rate.csv")!
        let crimeCSVData = try await URLSession.shared.data(from: crimeCSVURL).0
        let stats1 = try DataFrame(contentsOfCSVData: crimeCSVData, columns: statsColumns)
        
        // 处理数据
        var convert = convert1
        convert.combineColumns("latitude", "longitude", into: "location") { (latitude: Double?, longitude: Double?) -> CLLocation? in
            guard let latitude = latitude, let longitude = longitude else { return nil }
            return CLLocation(latitude: latitude, longitude: longitude)
        }
        
        let new = convert.filter(on: "zip", String.self){ $0 == input }
        guard !new.isEmpty else {
            return .failure(NSError(domain: "DataError", code: 1, userInfo: [NSLocalizedDescriptionKey: "未找到匹配的邮编数据"]))
        }
        
        guard let state = new["state", String.self][0], let loc = new["location", CLLocation.self][0] else {
            return .failure(NSError(domain: "DataError", code: 2, userInfo: [NSLocalizedDescriptionKey: "数据解析失败"]))
        }
        location = loc
        
        guard let county = new.rows[0][3, String.self] else {
            return .failure(NSError(domain: "DataError", code: 3, userInfo: [NSLocalizedDescriptionKey: "无法获取县信息"]))
        }
        
        let countyStats = stats1.filter(on: "county_name", String.self){ $0 == "\(county), \(state)" }
        return .success(countyStats)
        
    } catch {
        return .failure(error)
    }
}

修改ContentView中的调用代码

func submit() {
    print("The city/county/zip inputted: \(input)")
    
    // 在Task中调用异步函数,避免阻塞主线程
    Task {
        let result = await test.getStats(input: input.zip)
        switch result {
        case .success(let stats):
            print(stats)
            // 回到主线程更新UI(如果需要)
            DispatchQueue.main.async {
                // 这里写UI更新逻辑,比如展示统计数据
            }
        case .failure(let error):
            print("获取数据失败:\(error.localizedDescription)")
            // 主线程提示用户错误
            DispatchQueue.main.async {
                // 比如弹出错误提示框
            }
        }
        resignKeyboard()
    }
}

关键改进点:

  • 用URLSession.shared.data(from:)异步下载CSV,配合async/await简化异步逻辑,避免主线程阻塞
  • 每一步数据都做合法性校验(如new.isEmpty、可选值解包),提前返回错误,避免空数据访问越界
  • 用Result类型统一封装成功/失败结果,调用方可以清晰处理不同场景
  • 所有UI操作强制回到主线程执行,符合iOS线程规范

额外建议

  • 可以缓存下载的CSV数据到本地,避免重复请求,提升性能和离线使用体验
  • 对用户输入的邮编做格式校验(如长度、数字格式),减少无效请求

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 18:20:42