如何修复同步URL加载及Thread 1: EXC_BAD_INSTRUCTION错误?
问题
开发一款基于CSV数据的App时,在ContentView调用城市类的getStats()函数出现两个问题:
- 点击提交按钮后,代码行
let state: String = new["state", String.self][0] ?? "There was an error"触发Thread 1: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0)错误 - 终端提示:
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
相关产品推荐
相关产品推荐

