翻译连接https://developer.apple.com/library/content/documentation/Cocoa/Conceptual/BinaryData/BinaryData.html#//apple_ref/doc/uid/10000037-SW1
简介
在Foundation框架和Core Foundation框架可以将二进制封装为对象并对其进行操作。因为数据对象是桥接对象,所以开发者可以交替使用Core Foundation数据对象和Foundation对象。数据对象可以自动管理字节缓冲区的内存开辟和内存释放。数据对象可以被存储在集合中,吸入属性列表,存储在文件中,以及在通讯端口之间进行传输。
本文档的结构
下面的文章介绍了数据对象是如何运作的
《数据对象》描述了数据对象是如何对缓冲字节进行封装的
下面的文章介绍了一般的任务
《二进制数据的使用》解释了如何创建和使用二进制数据对象
《可变二进制数据的使用》解释了如何修改二进制数据对象中的字节缓冲
数据对象
数据对象就是对缓冲字节进行面向对象化的封装。在这些数据对象中,简单开辟的缓存区(也就是不包含嵌套指针)具有其他对象的一些行为—也就是说,它们既能够包含数据,又能提供对这些数据的操作方法。数据对象通常被用来存储数据。数据对象在互联网以及内部网络的应用中非常有用,因为数据对象中被包含的数据可以在应用之间进行传输。
一个NSData或者NSMutableData对象所能够封装的数据大小取决于对应的平台限制,相关细节请参照NSData文档。当数据大小略大于几个内存页的时候,目标对象就会使用虚拟内存管理机制。数据对象还可以对已有数据进行封装,而不用考虑这些数据是如何被创建的。数据对象不会保存和缓冲数据有关的其他信息(比如数据的类型);如何使用数据取决于客户端。尤其要注意的是,数据对象并不负责在大小端设备之间的相关转换。取而代之的方案是,针对相关类型对象使用NSValue进行管理。
数据对象提供了一种与操作系统无关的方式来实现写时拷贝。所谓的写时拷贝技术就是说,当数据从虚拟内存进行拷贝的时候,只有操作方试图对目标数据进行修改的时候,才会真正地执行拷贝操作。
一种典型操作就是当创建一个数据对象的时候,可以指定缓存区的大小。开发者还可以提取指定范围的缓存数据,对两个数据对象的数据进行比较,以及将数据写入到对应的URL中。当创建了对应的对象之后,如果还有修改对象的需求,那么就应该使用可修改对象。在可修改对象中,开发者可以对数据进行截断、扩展、添加,以及替换。
使用二进制数据
本节包含NSData以及NSMutableData的相关代码示例。因为Foundation中的类簇机制,数据对象并不是NSData或者NSMutableData的实例,而是某个对应的私有子类的实例。尽管一个数据对象的类是私有的,但是接口是公开的,这些接口在父类NSData和NSMutableData中进行了声明。
基于原始字节创建数据对象
通常,开发者可以在NSData或者NSMutableData中通过那些以data...开头的方法来基于原始字节创建数据对象。这些方法可以返回包含开发者指定原始字节的数据对象。
通常初始化方法(诸如dataWithBytes:length:)会对传入的缓冲区字节进行拷贝。在这种情况下,被拷贝的
字节拥有者就是数据对象,由数据对象来负责对相关内存的释放。而原有缓冲字节的内存释放则由原有对象负责。
但是,如果开发者使用了带有NoCopy相关字样的初始化函数,那么对应的缓冲字节就不会被拷贝。这种情况下的数据对象会持有通过参数传入的缓冲字节,然后在对象释放的时候, 缓冲字节对应的内存可会被释放掉。(NSMutableData同样也有这些方法,但是缓冲字节会被拷贝,对应的缓冲区可会被立刻释放掉)。基于上述原因,通过NoCopy方法传入的数据必须是通过malloc创建的。
如果开发者倾向于不拷贝数据或者当对象释放的时候,缓存字节不被释放,那么就可以使用dataWithBytesNoCopy:length:freeWhenDone:以及initWithBytsNoCopy:length:freeWhenDone,然后通过对freeWhenDone:参数传入NO来达到目的。
基于文件或者URL创建数据对象
开发者可以使用dataWithContentsOfFile:以及dataWithContentsOfURL:这两个方法来创建数据对象。下面是实例代码,注意,路径必须是绝对路径。
NSString *thePath = @"/u/smith/myFile.txt";
NSData *myData = [NSData dataWithContentsOfFile:thePath];
访问和比较缓冲字节
NSData中的两个基础方法bytes和length为这个类的其他方法提供了基础操作。bytes方法返回一个指向缓冲字节数据的指针。length方法则返回了缓冲字节的长度。
NSData提供了从数据对象中拷贝到指定缓冲区的数据访问方法。getBytes:length:方法将字节拷贝到指定缓冲区去。比如说,下面的代码段就是初始化一个数据对象myData,参数是myString,然后使用了getByts:length:方法来将myData的数据拷贝到aBuffer中。
unsigned char aBuffer[20];
NSString *myString = @"Test string.";
const char *utfString = [myString UTF8String];
NSData *myData = [NSData dataWithBytes: utfString length: strlen(utfString)];
[myData getBytes:aBuffer length:20];
getBytes:range:方法将指定范围内的字节进行拷贝。
为了提取某个数据对象的字节数据子集,开发者可以使用subDataWithRange:方法。举例来说,下面的代码段会初始化一个数据对象data2来包含data1的数据子集。
NSString *myString = @"ABCDEFG";
const char *utfString = [myString UTF8String];
NSRange range = {2, 4};
NSData *data1, *data2;
data1 = [NSData dataWithBytes:utfString length:strlen(utfString)];
data2 = [data1 subdataWithRange:range];
如果想要比较两个数据对象是否相同,可以调用isEqualToData:方法,这个方法提供逐个字节的比较。
拷贝数据对象
开发者可以以拷贝的方式来创建一个只读的或者可修改的数据对象。NSData和NSMutableData实现了NSCopying协议以及NSMutableCopying协议,这就确保了可以在支队对象和可修改对象之间进行方便的转换。开发者可以通过调用copy方法来创建只读的拷贝,也可以通过mutableCopy来创建可修改的拷贝。
存储数据对象
开发者可以将字节数据存储在本地文件或者互联网中。writeToFile:atomically:方法以及writeToURL:atomically:方法可以让开发者将字节数据保存在本地文件中。
使用可变二进制数据
本节包含了一些和可变数据对象(也就是NSMutableData)有关的代码示例。开发者可以通过直接修改字节数组、添加新数据以及替换指定范围内的数据这些方式来改变可修改数据对象。
修改字节
NSMutableData的两个方法mutableBytes以及setLength:为本类的其他方法提供了基础。其中,mutableBytes返回的是一个指向可修改字节数据的指针。而setLength:方法允许开发者对数据对象中的缓冲字节进行范围扩展或者截断。increaseLengthBy:方法同样可以让开发者改变可修改数据对象中的数据长度。
在下面代码段中,mutableBytes可以返回一个指向data2的指针。然后使用data1的数据覆盖data2的数据。
代码段1,修改字节
NSMutableData *data1, *data2;
NSString *myString = @"string for data1";
NSString *yourString = @"string for data2";
const char *utfMyString = [myString UTF8String];
const char *utfYourString = [yourString UTF8String];
unsigned char *firstBuffer, secondBuffer[20];
/* initialize data1, data2, and secondBuffer... */
data1 = [NSMutableData dataWithBytes:utfMyString length:strlen(utfMyString)+1];
data2 = [NSMutableData dataWithBytes:utfYourString length:strlen(utfYourString)+1];
[data2 getBytes:secondBuffer length:20];
NSLog(@"data2 before: \"%s\"\n", (char *)secondBuffer);
firstBuffer = [data2 mutableBytes];
[data1 getBytes:firstBuffer length:[data2 length]];
NSLog(@"data1: \"%s\"\n", (char *)firstBuffer);
[data2 getBytes:secondBuffer length:20];
NSLog(@"data2 after: \"%s\"\n", (char *)secondBuffer);
下面是代码段1的输出:
Oct 3 15:59:51 [1113] data2 before: "string for data2"
Oct 3 15:59:51 [1113] data1: "string for data1"
Oct 3 15:59:51 [1113] data2 after: "string for data1"
添加字节
appendBytes:length:方法以及appendData:方法可以让开发者在可修改数据对象中进行数据添加。比如说,代码段2就是将data2中的数据拷贝到aBuffer中,然后将aBuffer中的数据添加到data1中
NSMutableData *data1, *data2;
NSString *firstString = @"ABCD";
NSString *secondString = @"EFGH";
const char *utfFirstString = [firstString UTF8String];
const char *utfSecondString = [secondString UTF8String];
unsigned char *aBuffer;
unsigned len;
data1 = [NSMutableData dataWithBytes:utfFirstString length:strlen(utfFirstString)];
data2 = [NSMutableData dataWithBytes:utfSecondString length:strlen(utfSecondString)];
len = [data2 length];
aBuffer = malloc(len);
[data2 getBytes:aBuffer length:[data2 length]];
[data1 appendBytes:aBuffer length:len];
程序的输出为ASCII的字符数组“ABCDEFGH”
替换字节
开发者可以通过方法resetBytesInRange:或者使用replaceBytesInRange:withBytes方法来将可修改数据对象中的指定范围的字节重置为0。在代码段3中,data1中的指定范围的数据被data2中的数据替代了,然后data1中的数据就从”Liz and John”变成了”Liz and Larry”。
NSMutableData *data1, *data2;
NSString *myString = @"Liz and John";
NSString *yourString = @"Larry";
const char *utfMyString = [myString UTF8String];
const char *utfYourString = [yourString UTF8String];
unsigned len;
unsigned char *aBuffer;
NSRange range = {8, strlen(utfYourString)};
data1 = [NSMutableData dataWithBytes:utfMyString length:strlen(utfMyString)];
data2 = [NSMutableData dataWithBytes:utfYourString length:strlen(utfYourString)];
len = [data2 length];
aBuffer = malloc(len);
[data2 getBytes:aBuffer length:len];
[data1 replaceBytesInRange:range withBytes:aBuffer];