Swift中TabularData DataFrame删除行触发EXC_BAD_ACCESS错误
TabularData框架删除DataFrame行触发EXC_BAD_ACCESS问题
问题现象
使用Swift的TabularData框架处理CSV数据时出现以下异常:
- 将CSV加载到DataFrame后,复制到第二个DataFrame,执行删除行操作时触发
EXC_BAD_ACCESS错误 - 仅当CSV中
timings列的内容长度超过15个字符时才会触发该错误 - 如果在删除行前修改
timings列(哪怕设置为与原内容完全相同的值),代码就能正常运行 - 直接对从CSV加载的单个DataFrame执行删除行操作,也会触发同样的错误
重现代码
func loadCSV() { let documentsDirectory = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first! let url = documentsDirectory.appendingPathComponent("example.csv") var dataframe: DataFrame do { dataframe = try .init( contentsOfCSVFile: url, columns: ["user", "filename", "syllable count", "timings"], types: ["user": .string, "filename": .string, "syllable count": .integer, "timings": .string] ) } catch { fatalError("Failed to load csv data") } print("First data frame", dataframe, separator: "\n") var secondFrame = DataFrame() secondFrame.append(column: Column<String>(name: "user", capacity: 1000)) secondFrame.append(column: Column<String>(name: "filename", capacity: 1000)) secondFrame.append(column: Column<Int>(name: "syllable count", capacity: 1000)) secondFrame.append(column: Column<String>(name: "timings", capacity: 1000)) for row in 0..<dataframe.rows.count { secondFrame.appendEmptyRow() for col in 0..<4 { secondFrame.rows[row][col] = dataframe.rows[row][col] } } // 取消注释该行则不会崩溃 // secondFrame.rows[row][3, String.self] = String("0123456789ABCDEF") print("Second data frame before removing row", dataframe, separator: "\n") secondFrame.removeRow(at: 0) print("Second data frame after removing row", dataframe, separator: "\n") // 此处触发EXC_BAD_ACCESS }
测试用CSV内容
user,filename,syllable count,timings john,john-001,12,0123456789ABCDEF jane,jane-001,10,0123456789ABCDE
问题原因分析
这是TabularData框架的底层内存管理bug:
- Swift对短字符串(≤15字符)采用栈存储的小字符串优化,长字符串则存储在堆上
- 从CSV加载的DataFrame中,长字符串可能持有了框架内部的共享内存引用
- 手动逐行复制时,只是简单复制了引用,并未创建独立的字符串实例;删除行时,框架的内存释放逻辑与共享引用冲突,导致野指针访问
- 修改
timings列时,会重新创建字符串实例,打破了原有的共享引用,因此避免了错误
解决方案
方案1:使用框架内置的复制方法
直接调用DataFrame的copy()方法,框架会正确处理内存引用,避免手动复制的问题:
var secondFrame = dataframe.copy()
方案2:复制独立的列实例
如果需要手动构建第二个DataFrame,直接复制原DataFrame的列(调用列的copy()方法),而不是逐行赋值:
var secondFrame = DataFrame() if let userCol = dataframe["user"]?.copy() { secondFrame.append(column: userCol) } if let filenameCol = dataframe["filename"]?.copy() { secondFrame.append(column: filenameCol) } if let syllableCol = dataframe["syllable count"]?.copy() { secondFrame.append(column: syllableCol) } if let timingsCol = dataframe["timings"]?.copy() { secondFrame.append(column: timingsCol) }
方案3:显式更新字符串列
如果必须保留逐行复制的逻辑,在删除行前对timings列做一次转换,确保每个字符串都是独立实例:
// 对timings列做一次映射,生成新的字符串实例 secondFrame["timings"] = secondFrame["timings"]!.map { $0 } // 再执行删除操作 secondFrame.removeRow(at: 0)
内容的提问来源于stack exchange,提问作者Joshua Rapp
相关产品推荐
相关产品推荐

