说一下深拷贝和浅拷贝的基本概念:a指针指向地址A1, 浅拷贝是创建了一个b指针指向地址A1;深拷贝是创建了一个c指针指向地址A2,A1和A2的地址不同。
我们看到NSObject接口里面是已经声明了copy和mutableCopy方法,也就是说任何的Objective-c的对象都可以调用这两个方法。
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
........
- (id)copy;
- (id)mutableCopy;
.......
@end
我们真的可以什么都不做就可以直接调用这两个方法吗?我们来做一个测试:
//test NSObject copy
NSObject *originObject = [[NSObject alloc] init];
NSObject *copiedObject = [originObject copy];
NSObject *mutableCopiedObject = [originObject mutableCopy];
NSLog(@"%@, %@, %@", originObject, copiedObject, mutableCopiedObject);
当代码执行到[originObject copy]时crash了,提示NSObject并没有实现NSCopying协议的方法,
-- ::56.680 NSLockTest[:] -[NSObject copyWithZone:]: unrecognized selector sent to instance 0x79f909b0
我们看看apple的解释:
Return Value
The object returned by the NSCopying protocol method copyWithZone:,. Discussion
This is a convenience method for classes that adopt the NSCopying protocol. An exception is raised if there is no implementation for copyWithZone:. NSObject does not itself support the NSCopying protocol. Subclasses must support the protocol and implement the copyWithZone: method. A subclass version of the copyWithZone: method should send the message to super first, to incorporate its implementation, unless the subclass descends directly from NSObject.
看出来了吧,copy和mutableCopy方法是给子类调用的,子类是要相应实现NSCopying和NSMutableCopy协议的。
@protocol NSCopying
- (id)copyWithZone:(NSZone *)zone;
@end
@protocol NSMutableCopying
- (id)mutableCopyWithZone:(NSZone *)zone;
@end
有人会经常疑惑深拷贝和浅拷贝的问题,其实不需要有什么疑惑,如果是自定义的类,深拷贝还是浅拷贝是我们实现协议时自己控制的,比如
- (id)copyWithZone:(NSZone *)zone{
return [self deepCopy:zone];
} - (id)shallowCopy:(NSZone *)zone{
return self;
} - (id)deepCopy:(NSZone *)zone{
CopiedObject *copy = [[[self class] allocWithZone:zone] init];
copy.name = [self.name copyWithZone:zone];
return copy;
}
那只是对于系统实现NSCopying的类,我们可能不是很清楚到底是深拷贝还是浅拷贝,可以很简单的说,
- 对一个不可变对象,copy是浅拷贝;而mutableCopy则是对象深拷贝;
- 对可变对象复制,都是深拷贝,但copy返回的对象是不可变的
如果非要说一些特殊的话,对于容器类,通过copy操作后,并没有真正对容器内的各对象进行copy,容器内存储的指针和指针指向的每个对象都和拷贝前相同,如果想实现容器类中各对象的深拷贝有什么办法吗?
1)直接点的办法,不需要对容器内的对象做任何操作,把当前的容器写到本地(比如NSKeyedArchive, 文件存储)然后再重新从本地读取就可以实现深拷贝;
2)使用系统提供的方法,拿NSArray来举例,系统提供了这个方法:
- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;
当flag为YES时,会NSArray中的每个对象发送copy消息,如果这个对象实现了NSCopy协议(显然如果这个对象没有实现NSCopying协议,就会crash),并且是深拷贝,新创建的这个数组就会是一个完全深拷贝的类(容器是新的,容器内的每个对象也是新的,对他们做任何修改都不会影响原来的容器)。
当flag为NO时,NSArray只会将原来存在数组里面的指针拿过来存一遍,不会对容器内的对象做任何的操作。当容器内的数据属性发生改变时,对应原容器内的数据属性也会跟着改变,因为他们就是一个对象。
最后补充两点:
1)copy和mutableCopy后的对象很明显的区别是,copy后是一个不可变的对象,mutbaleCopy后是可变的对象,copy前后可以拿NSdictionay和NSMutableDictionary、NSArray和NSMutableArray、NSString和NSMutbaleString等来对比。
2)copy和mutbaleCopy返回的对象在MRR中是需要我们手动release/autorelease的,因为要么是对原有的对象进行了retain操作,要么是重新创建了一个新的对象;换句话说,在实现协议的时候,我们并不需要对返回的对象发送autorelease消息。
参考资料:
1)https://developer.apple.com/library/ios/documentation/Cocoa/Reference/Foundation/Protocols/NSCopying_Protocol/