Swift修改kMDItemDateAdded元数据失败:setattrlist返回-1求修复
修改文件kMDItemDateAdded元数据的Swift实现问题修复
问题背景
macOS/iOS中,无法通过FileManager的setAttributes方法修改文件的kMDItemDateAdded(即"Date added")属性,因为FileAttributeKey没有对应的dateAdded键。示例代码如下:
var attrs: [FileAttributeKey: Date] = [:] // 没有对应的FileAttributeKey.dateAdded可设置 attrs[FileAttributeKey.modificationDate] = Date.now try FileManager.default.setAttributes(attrs, ofItemAtPath: file.path)
通过C语言的setattrlist系统调用可以实现该功能,但转译为Swift后,setattrlist始终返回-1,无法生效。
原C语言实现参考
以下是可正常工作的C语言代码:
#include <stdlib.h> #include <string.h> #include <sys/attr.h> #include <unistd.h> /* * 设置文件的kMDItemDateAdded属性 * 返回值: * • 0 成功 * • 1 系统调用失败,可检查errno */ int set_date_added(const char* path, struct timespec in) { attrgroup_t request_attrs = ATTR_CMN_ADDEDTIME; struct attrlist request; memset(&request, 0, sizeof(request)); request.bitmapcount = ATTR_BIT_MAP_COUNT; request.commonattr = request_attrs; typedef struct { struct timespec added; } __attribute__((aligned(4), packed)) request_buf_t; request_buf_t request_buf; request_buf.added.tv_sec = in.tv_sec; request_buf.added.tv_nsec = in.tv_nsec; int err = setattrlist(path, &request, &request_buf, sizeof(request_buf), 0); if (err != 0) { return 1; } return 0; }
Swift转译的问题排查
转译后的Swift代码失败的核心原因有以下几点:
- 路径传递错误:直接将Swift
String传入setattrlist,没有正确转换为C风格字符串,可能导致路径解析失败。 - 时间值转换不精确:将
Date.timeIntervalSince1970(Double类型)直接转为Int,丢失了小数部分的纳秒值,且tv_sec的类型应为Int64(对应C的time_t)而非Int。 - 结构体内存对齐不匹配:C代码中对
request_buf_t使用了packed属性确保内存紧凑,Swift默认结构体对齐规则与C不一致,导致setattrlist无法正确解析缓冲区数据。 - 错误排查缺失:未获取
errno值定位具体失败原因,比如权限问题、路径无效等。
修复后的Swift实现
import Darwin func setDateAdded(path: String, toDate date: Date) -> Bool { guard FileManager.default.fileExists(atPath: path) else { print("文件不存在") return false } // 1. 初始化attrlist并清零 var attrList = attrlist() memset(&attrList, 0, MemoryLayout<attrlist>.size) attrList.bitmapcount = UInt16(ATTR_BIT_MAP_COUNT) attrList.commonattr = attrgroup_t(ATTR_CMN_ADDEDTIME) // 2. 定义与C代码内存布局一致的缓冲区结构体(packed属性确保紧凑对齐) @_packed struct AttrBuffer { var added: timespec } // 3. 精确转换Date到timespec let timeInterval = date.timeIntervalSince1970 let seconds = Int64(floor(timeInterval)) let nanoseconds = Int((timeInterval - floor(timeInterval)) * 1_000_000_000) var attrBuffer = AttrBuffer(added: timespec(tv_sec: seconds, tv_nsec: nanoseconds)) let bufferSize = MemoryLayout<AttrBuffer>.size var result: Int32 = -1 // 4. 正确传递C风格路径 path.withCString { cPath in result = setattrlist(cPath, &attrList, &attrBuffer, bufferSize, 0) } // 5. 打印错误信息便于排查 if result != 0 { let errorStr = String(cString: strerror(errno)) print("设置失败:\(errorStr) (errno: \(errno))") return false } return true }
关键修复点说明
- 路径处理:使用
String.withCString确保传递给setattrlist的是有效的C风格字符串,避免内存管理问题。 - 结构体对齐:给
AttrBuffer添加@_packed属性,匹配C代码中的__attribute__((packed)),保证内存布局一致。 - 时间转换:精确拆分秒和纳秒部分,且
tv_sec使用Int64类型,适配64位系统的time_t定义。 - 错误排查:通过
errno和strerror获取具体失败原因,便于定位权限、路径等问题。
内容的提问来源于stack exchange,提问作者Andrew
相关产品推荐
相关产品推荐

