上篇分析了NSString的copy和retain的区别,并且引出对oc中的copy原理探究的欲望,参考了很多资料,这里做讨论。
1、实现了NSCopy/NSMutableCopying的框架类
我们都知道oc框架里面的例如NSString、NSArray等很多类的对象在需要复制的时候都可以直接调用[obj copy/mutablecopy]方法。调用copy方法时,会向NSCopying的协议方法copywithzone发消息的。作为根类的NSObject并没有实现NSCopying协议,所以他们都默认实现了NSCopying和NSMutableCopying协议,实现copywithzone和mutablecopywithzone方法。
@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>
@interface NSArray : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
@interface NSDictionary : NSObject <NSCopying, NSMutableCopying, NSSecureCoding, NSFastEnumeration>
2、自定义的对象的copy/mutablecopy
我们自定义的对象要想调用copy/mutablecopy方法,也必须手动实现NSCopying协议,实现两个方法。如果没有实现,调用copy方法会抛出异常。
定义一个person类
@interface Person : NSObject
@property (strong,nonatomic) NSString* name;
@property (assign,nonatomic) NSInteger age;
@property (strong,nonatomic) NSArray* sons;
@end
//**************
@implementation Person
@end
使用:
1 Person* p = [[Person alloc] init];
2 p.name = @"张三";
3 p.age = 50;
4 p.sons = @[@"张小一",@"张小二"];
5 Person* copy = [p copy];
到5的地方抛出异常:
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[Person copyWithZone:]: unrecognized select
or sent to instance 0x8b240d0'
无法识别的方法消息,这里因为person没有实现NSCopying协议和coypwithzone方法。接下来我为person类添加协议和方法。
@interface Person : NSObject<NSCopying,NSMutableCopying> ... ... ... @end //**************************************
@implementation Person
-(id)copyWithZone:(NSZone *)zone
{
Person* copy = [[self class] allocWithZone:zone]; //分配新的内存空间 对于self本身来说,属于深拷贝了。
copy.name = [self.name copy]; //NSString的copy,对于name,属于浅拷贝,因为都属于不可变
copy.age = self.age;
copy.sons = [self.sons copy]; //同样浅拷贝
return self;
}
-(id)mutableCopyWithZone:(NSZone *)zone
{
Person* copy = [[self class] allocWithZone:zone];//分配新的内存空间
copy.name = [self.name mutableCopy]; //深拷贝 不可变拷贝可变
copy.age = self.age;
copy.sons = [self.sons mutableCopy]; //同样深拷贝
return copy;
}
@end2.1使用copy:
Person* p = [[Person alloc] init]; p.name = @"张三"; p.age = 50; p.sons = @[@"张小一",@"张小二"]; Person* copy = [p copy];
打印地址:NSLog(@"p addr = %p",p); NSLog(@"copy addr = %p",copy);
输出内容:
p addr = 0x8b30140 copy addr = 0x8d34750
我们看到,地址不同,copy的内存空间是新开辟的。这里要说一下,因为一般我们copy出来的Person对象,副本变动不希望影响到另一个。所以在copywithzone里面如上面那样操作,新开辟内存空间,逐个成员变量赋值;如果copy的对象不考虑互相影响的后果,则可以直接:不过这样貌似没有什么意义,不如直接用:-(id)copyWithZone:(NSZone *)zone { return self; }
Person* copy = p;
Person的name和sons都属于不可变,并且调用的是copy,所以对象p和copy的这两个成员的地址应该是一样的。 我们打印出name和sons的值和地址来看一下:
输出:NSLog(@"p.name addr = %p && copy.name addr = %p",p.name,copy.name); NSLog(@"p.sons addr = %p && copy.sons addr = %p",p.sons,copy.sons); NSLog(@"p's son addr = %p && copy's son addr = %p",[p.sons objectAtIndex:0],[copy.sons objectAtIndex:0]);
这个结果没有悬念,遵循不可变对象copy属于浅拷贝。p.name addr = 0x4848 && copy.name addr = 0x4848 p.sons addr = 0x8d46410 && copy.sons addr = 0x8d46410 p's son addr = 0x5858 && copy's son addr = 0x5858
这里注意到,我专门打印出p.sons 和copy.sons的第一个元素地址来做对比,他们是一样的。NSArray是容器类型,里面的元素是在copy的时候是什么样的情况,后面详细讨论。
2.2使用mutablecopy
可以看到,copywithzone和mutablecopywithzone都创建了新的内存空间,不同之处在于对成员变量的处理上。
打印结果:Person* copy = [p mutableCopy];
结果是name和sons都被深拷贝,sons里面元素仍然是浅拷贝,看来NSArray的mutablecopy不会令其中的元素深拷贝。p.name addr = 0x5848 && copy.name addr = 0x8a3db30 p.sons addr = 0x8a3f8f0 && copy.sons addr = 0x8a3f020 p's son addr = 0x5858 && copy's son addr = 0x5858
3、容器类对象的拷贝 参考:http://www.cnblogs.com/ydhliphonedev/archive/2012/04/27/2473927.html
对于类似NSArray、NSDictionary之类的容器元素,要想实现深拷贝,可以参考一下两种方法:
1 NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"first"],[NSStringstringWithString:@"b"],@"c",nil]; 2 NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES]; 3 NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData: 4 [NSKeyedArchiver archivedDataWithRootObject: array]];
trueDeepCopyArray是完全意义上的深拷贝,而deepCopyArray则不是,对于deepCopyArray内的不可变元素其还是指针复制。或者我们自己实现深拷贝的方法。因为如果容器的某一元素是不可变的,那你复制完后该对象仍旧是不能改变的,因此只需要指针复制即可。除非你对容器内的元素重新赋值,否则指针复制即已足够。举个例子,[[array objectAtIndex:0]appendstring:@”sd”]后其他的容器内对象并不会受影响。[[array objectAtIndex:1]和[[deepCopyArray objectAtIndex:0]尽管是指向同一块内存,但是我们没有办法对其进行修改——因为它是不可改变的。所以指针复制已经足够。所以这并不是完全意义上的深拷贝,但是apple的官方文档将其列为deep copy了,并添加了copy和mutablity的关系说明,故在此做一说明