最近在用NSArray 数组对数据进行操作时,将该数组进行 MutableCopy 到一个NSMutableArray数组中,然后再在这个NSMutableArray数组中对数据进行操作的时候,发现原来NSArray 数组中的数据也被修改了。仔细研究,发现copy和MutableCopy并没有我想像的那么简单。
首先关于copy和mutableCopy的行为:不管是NSString这种元素类、还是NSArray这样的容器类、还是Mutable和非Mutable类,copy和mutableCopy调用后表现的行为到底是什么样的完成取决于类本身NSCopying和NSMutableCopying协议是如何实现的。并且,如果尝试使用自定义类(例如,人类(person类)、书类(book类))中的copy方法等类似的操作,将会收到一条异常出错的消息,它可能如下所示:
-[Fraction copyWithZone:]: unrecognized selector sent to instance 0x7fabb8414380
这种错误,是对于自定义类,要实现使用自己的类进行复制,必须根据<NSCopying>协议实现其中的一两个方法。实现<NSCopying>协议时,类必须实现copyWithZone:方法来响应copy消息。如果想要区分可变副本和不可变副本,那么copyWithZone:应该返回不可变副本,而mutableCopyWithZone:应该返回可变副本。产生对象的可变副本并不要求被复制的对象本身也是可变的(反之亦然),想要产生不可变副本的可变副本是很合理的(例如,字符串对象)。
以下对于copy和mutableCopy的详细分析引用 http://blog.csdn.net/omegayy/article/details/7311839
1. 元素数据的copy和mutableCopy。
常用的字符串NSString类,示例代码如下:
NSString* string = @”hello world”; NSString* stringCopy = [string copy];// stringCopy与string地址相同,retainCount+ 1 NSMutableString* stringMCopy = [string mutablecopy];// stringMCopy与string地址不同 NSMutableString* stringM1 = [stringMCopy copy];//地址与stringMCopy不同,且为不可修改 NSMutableString* stringM2 = [stringMCopy mutablecopy];//地址与stringMCopy不同,可修改
可以基本推出NSString和NSMutableString中两个协议的实现
NSString: - (id)copywithZone:(NSZone*)zone { return self; } - (id)mutableCopywithZone:(NSZone*)zone { NSMutableString* copy =[[NSMutableString alloc] initxxxxxx]; .... return copy; } NSMutableString: - (id)copywithZone:(NSZone*)zone { NSString* copy = [[NSStringalloc] initxxxxxx]; .... return copy;//所以不可修改 } - (id)mutableCopywithZone:(NSZone*)zone { NSMutableString* copy =[[NSMutableString alloc] initxxxxxx]; .... return copy; }
2. 容器类的copy和mutableCopy。
常用类NSArray和NSMutableArray,看如下示例代码:
Class1* obj1= ....;//正常初始化 NSArray* array = [[NSArray alloc] initWithObjects:obj1, nil]; NSArray* arrayCopy = [array copy];//地址不变,retaincount+1 NSMutableArray* arrayMCopy = [array mutableCopy];//地址改变,但是数组中成员指针和obj1相同,浅拷贝 NSMutableArray* arrayM1 = [arrayMCopy Copy];//地址改变,但是数组中成员指针和obj1相同,浅拷贝。arrayM1为NSArray不可修改 NSMutableArray* arrayM2 = [arrayMCopy mutableCopy];//地址改变,但是数组中成员指针和obj1相同,浅拷贝
推断
NSArray: - (id)copywithZone:(NSZone*)zone { //伪码 return [self retain]; } - (id)mutableCopywithZone:(NSZone*)zone { NSMutableArray* copy = [[NSMutableString alloc] initxxxxxx]; for (id element in self) { [copy addObject:element];//element retian count + 1 .... } return copy; } NSMutableArray: - (id)copywithZone:(NSZone*)zone { NSArray* copy = [[NSArray alloc] initXXX]; /*把每个element加入到copy数组,retainCount+1*/ .... return copy; } - (id)mutableCopywithZone:(NSZone*)zone { NSMutableArray* copy = [[NSMutableString alloc] initxxxxxx]; for (id element in self) { [copy addObject:element];//element retian count + 1 .... } return copy; }
3. 深拷贝
官方文档中介绍两种实现深拷贝的方法:
a. 用Array的initWithArray: copyItems函数,如下:
NSArray *deepCopyArray=[[NSArray alloc] initWithArray: someArraycopyItems: YES];
调用后,会对原NSArray中的每个元素调用其copy函数,并把返回的id加入到新的数组中。所以这是依赖于Obj对象类实现的深拷贝,如果- (id)copywithZone:(NSZone*)zone是重新分配一块内存赋值后返回,那么就是真正的深拷贝。如果直接返回自身,那么它只是浅拷贝。
b. 用archiver方式:
NSArray* trueDeepCopyArray = [NSKeyedUnarchiverunarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject:oldArray]];
这是真正意义上的深拷贝,不依赖于实际类Copying协议的实现。
最后需要注意的是:
如果你的类可以产生子类,那么copyWithZone:方法将被继承。在这种情况下,该方法中的程序行:
//假如父类为Book类 无子类是可以这样实现 Book *newbook = [[Book allocWithZone:zone] init];
//有子类时,需要这样实现 Book *newBook = [[[self class] allocWithZone:zone] init];这样,可以从该类分配一个新的对象,而这个类的copy的接收着(例如,如果它产生了一个名为NewFraction 的子类,那么应该确保在继承的方法中分配了新的NewFraction对象,而不是Fraction对象)。
如果编写一个类的copyWithZone:方法,而该类的超类也实现了<NSCopying>协议,那么应该先调用超类的copy方法以复制继承来的实例变量,然后加入自己的代码以复制想要添加到该类中的任何附加的实例变量(如果有的话)。