Unix文件系统中对单个文件提供了很多属性,比如创建时间、文件大小、修改时间等,这些信息都保存在文件系统中的inode节点中,获取这些属性并不需要读取文件,所以获取速度是很快的。在开发过程中常常会使用这些属性,但更多的时候我们需要一个自定义的属性,来保存我们需要的一些值。比如我们给文件定义一个有效期,或者给文件加一个特殊标记等。
解决方法
我们可以建立一个数据库,每个文件为一条数据,再自定义一些字段,然而这样做会有些同步问题,比如文件不小心被删除了,或者文件名字改了,这会导致数据库操作过于复杂;或者我们在文件中添加一些自定义内容,加入文件的特定部分,但这样每次获取这些属性时都要读取这个文件,开销代价很高。
其实Unix系统中提供了文件扩展属性的功能,可以非常方便的实现为文件添加自定义属性,这些属性会保存到文件节点数据中。如果文件删除了,这些数据也同时删除;如果文件改名了,也不会影响节点数据,因此这些属性依然会和改名后的文件关联;同时获取这些属性也不用去读取文件。设置一个自定义属性需要用到:
1 2
| int setxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); int fsetxattr(int fd, const char *name, void *value, size_t size, u_int32_t position, int options);
|
这两个函数都是用来设置一个文件的扩展属性,唯一的区别是其中setxattr第一个参数是文件路径,而fsetxattr是已打开的文件描述符(由open/creat函数返回)。name是指定的属性的名字,是一个c字符串,为了保证名字的唯一性,建议使用反域名格式的名字,如“com.example.propertyname”。第三个参数value是值的地址,第四个参数size是值的大小,position设置为0。options可以有以下几个:
- XATTR_NOFOLLOW 不跟随符号链接,如果文件是符号链接的话,设置这个option后会设置符号链接本身的属性,而不是符号链接所指向的文件的。
- XATTR_CREATE 只能创建标志,如果文件已经有同名属性,则返回失败。
- XATTR_REPLACE 只能替换标志,如果文件没有同名属性,则返回失败。
成功后这两个函数会返回0;否则返回-1,同时设置了系统的errno。
同时Unix还提供了相应的获取已设置扩展属性的方法:
1 2
| ssize_t getxattr(const char *path, const char *name, void *value, size_t size, u_int32_t position, int options); ssize_t fgetxattr(int fd, const char *name, void *value, size_t size, u_int32_t position, int options);
|
参数和设置方法的对应,只是传入的size是value的内存空间,返回的是值实际的大小。
具体用法
设置文件的过期日期
1 2 3 4 5
| NSDate *expireDate = ...; const char* cfilePath = [filePath fileSystemRepresentation]; const char* attrExpireDate = "com.xcodev.file.expireDate"; double attrDate = [expireDate timeIntervalSince1970]; setxattr(cfilePath, attrExpireDate, &attrDate, sizeof(attrDate), 0, 0);
|
获取文件过期日期,如果过期了就删除。
1 2 3 4 5 6 7 8 9
| const char* cfilePath = [path fileSystemRepresentation]; const char* attrExpireDate = "com.xcodev.file.expireDate"; double expireTimestamp = 0; if (getxattr(cfilePath, attrExpireDate, &expireTimestamp, sizeof(expireTimestamp), 0, 0)>0) { NSDate *expireDate = [NSDate dateWithTimeIntervalSince1970:expireTimestamp]; if ([expireDate compare:[NSDate date]]==NSOrderedAscending) { [[NSFileManager defaultManager] removeItemAtPath:path error:NULL]; } }
|
Do not Backup
文件扩展属性的另一个在iOS中的具体应用就是设置不让iCould备份标记。
1 2 3 4
| const char* cfilePath = [filePath fileSystemRepresentation]; const char* attrBackup = "com.apple.MobileBackup"; u_int8_t attrValue = 1; setxattr(cfilePath, attrBackup, &attrValue, sizeof(attrValue), 0, 0);
|
把“com.apple.MobileBackup”的值设置为1后,iCloud就不再去备份这个文件了。