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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 00:45:13