随便写一个类, 继承自NSObject,
.h文件
@interface YIOHOn :NSObject
@property (nonatomic,strong)NSString *myName;
@end
.m文件:
@implementation YIOHOn
@end
使用时:
YIOHOn *onObj = [[YIOHOnalloc]init];
onObj.myName =@"Jacky";
YIOHOn *cop1 = [onObjcopy];
YIOHOn *cop2 = [onObjmutableCopy];
NSLog(@"cop1=%@, cop2=%@", cop1, cop2);
运行情况是, 运行到copy时就会崩溃,崩溃显示的信息如下:2013-10-24 12:31:49.405 YOnsgongs[20395:a0b] -[YIOHOn copyWithZone:]: unrecognized selector sent to instance 0xa109450
2013-10-24 12:31:49.435 YOnsgongs[20395:a0b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[YIOHOn copyWithZone:]: unrecognized selector sent to instance 0xa109450'
*** First throw call stack:
分析发现, 这里崩溃是在YIOHOn类方法copyWithZone中崩溃的。可是我们在程序中只调用了copy, 哪里有copyWithZone:呢?
1, 我们知道咱们的YIOHOn类是继续自NSObject的, 我们要以看到NSObject.h文件中, 如下:
@interface NSObject <NSObject> {
Class isa OBJC_ISA_AVAILABILITY;
}
+ (void)load;
+ (id)new;
+ (id)allocWithZone:(struct_NSZone *)zone;
- (id)copy;
- (id)mutableCopy;
+ (id)copyWithZone:(struct_NSZone *)zoneOBJC_ARC_UNAVAILABLE;
+ (id)mutableCopyWithZone:(struct_NSZone *)zoneOBJC_ARC_UNAVAILABLE;
很明显,我们能执行 [onObj copy ]是因为NSObject有对象方法, -(id)copy; 。 然后我们到帮助文档中看copy解释
This is a convenience method for classes that adopt the NSCopying
protocol. An exception is raised if there is no implementation forcopyWithZone:
.
说这个方法是为那些类,这些类实现了NSCopying的协议的类,如果这个类没有实现copyWithZone:方法为这个协议,那么就会抛出异常崩溃。
因为我们调用了copy方法,而copy方法最终会要求调用类方法copyWithZone:, 而NSObject本身并没有实现这个类方法, 这个类方法是放在NSCopying协议中的, 虽然在上面的NSObject.h文件中, 有提到NSObject有类方法copyWithZone:, 其实这是一个误导, 对NSObject, 这里虽然写了一个+(id)copyWithZone:但NSObject类本身却并没有实现这个类方法, 它是要求子类去实现的, 子类如果要调用copy方法, 那么子类就去遵循NSCopying协议, 然后就能正常调用了。
NSMutableCopying协议同理
讨论一下不可变拷贝与可变拷贝
先上代码
NSString *originString =@"China";
NSString *muString = [originStringmutableCopy];
[(NSMutableString *)muStringappendString:@"Love"];
NSLog(@"after append, muString=%@", muString);
执行结果,
after append, muString=ChinaLove
分析之, 可以得出结论,对执行mutableCopy后,不管原始值是可变的还是不可变后, 执行后生成的对象一定是可变的。再上代码:
NSMutableString *oriMutString = [NSMutableStringstringWithFormat:@"You"];
NSString *genString = [oriMutStringcopy];
[(NSMutableString *)genStringappendFormat:@"LikeMe"];
NSLog(@"genString=%@", genString);
运行会发现崩溃,崩溃信息为:*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'Attempt to mutate immutable object with appendFormat:'
分析之, 得出结论,执行copy后,不管原始值是可变的还是不可变的, 生成的对象一定是不可变的。深拷贝与浅拷贝
- (id)copyWithZone:(NSZone *)zone {
YYCar *copiedCar = [[YYCarallocWithZone:zone]init];
copiedCar.bandName = [self.bandNamecopyWithZone:zone];
copiedCar.soldPrice = self.soldPrice;
return copiedCar;
}
深拷贝与浅拷贝与copy和mutableCopy无关。copy和mutableCopy中的实现方法有关。上述方法中, copiedCar.bandName = [self.bandName copyWithZone:zone];就是一个深拷贝,因为生成的bandName与原始的self.bandName内存地址变了。
而如果写成copiedCar.bandName =self.bandName;则是浅拷贝,因为他们的内存地址是相同的。
结论:在拷贝时, 如果内容本来是不可变的, 如上面这个bandName,是NSString类型的, 那么建议使用浅拷贝的方式,这样的话, 不用太重新创建对象。 因为在创建对象的时候会花费较多的执行时间,而创建出的对象因为是不可变的, 其实就是一个重复的对象,意义并不大。而对于NSMutableString来说,尽量采用创建新对象的方式来生成, 以防止以前的对象与拷贝后的对象修改时影响对方的值。
最后再说一下嵌套深层拷贝的问题
先上代码:
NSDictionary *dic = [NSDictionarydictionaryWithObjectsAndKeys:@"Jacky",@"Name",
[NSArrayarrayWithObjects:@"1",@"2",nil],@"CheckDic",
[NSArrayarrayWithObjects:@"First",@"Second",nil],@"TheArr", nil];
NSMutableDictionary *mutDic = [dicmutableCopy];
//改直接下面的键值
[mutDicsetObject:@"Zouzou"forKey:@"Name"];
//改直接下面的键值对数组
[mutDic setObject:[NSArrayarrayWithObjects:@"11",@"22",nil]forKey:@"CheckDic"];
NSLog(@"before dic=%@, \n mutDic=%@", dic, mutDic);
// 改内置的Arr
NSMutableArray *mutArr = (NSMutableArray *)[mutDicobjectForKey:@"TheArr"];
[mutArraddObject:@"Third"];
NSLog(@"after dic=%@, \n mutDic=%@", dic, mutDic);
运行时,会发现输入为:before dic={
CheckDic = (
1,
2
);
Name = Jacky;
TheArr = (
First,
Second
);
},
mutDic={
CheckDic = (
11,
22
);
Name = Zouzou;
TheArr = (
First,
Second
);
}
2013-10-24 15:07:37.842 YOnsgongs[20953:a0b] -[__NSArrayI addObject:]: unrecognized selector sent to instance 0xa8550d0
2013-10-24 15:07:37.844 YOnsgongs[20953:a0b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[__NSArrayI addObject:]: unrecognized selector sent to instance 0xa8550d0'
*** First throw call stack:
然后就崩溃了, 崩溃的原因我们可以想到, 这里的mutDic确实是可变的, 但是它下面的键值对应的数据却不会是可变的。 所以我们往其内嵌的数组中添加数据,则会引发崩溃。 简单地说, 就是上面只执行了一层的可变拷贝, 更深层的则没进行进行可变拷贝。 那么如何能做到更深层次的可变拷贝呢。 使用下面这个类别, 就可做到。 .h文件如下@interface NSDictionary(MutableDeepCopy)
// NSDictionary深拷贝方法
- (NSMutableDictionary *)mutableDeepCopyOfDictionary;
@end
@interface NSArray(MutableDeepCopy)
// NSArray深拷贝方法
- (NSMutableArray *)mutableDeepCopyOfArray;
@end
实现文件
#import "MutableDeepCopy.h"
@implementation NSDictionary(MutableDeepCopy)
- (NSMutableDictionary *) mutableDeepCopyOfDictionary {
NSMutableDictionary *newDict = [NSMutableDictionarydictionaryWithCapacity:[selfcount]];
NSArray *keys = [selfallKeys];
for (id keyin keys) {
id oneValue = [selfvalueForKey:key];
id oneCopy =nil;
if ([oneValuerespondsToSelector:@selector(mutableDeepCopyOfDictionary)]) {
oneCopy = [[oneValuemutableDeepCopyOfDictionary]retain];
}
elseif ([oneValuerespondsToSelector:@selector(mutableDeepCopyOfArray)]) {
oneCopy = [[oneValuemutableDeepCopyOfArray]retain];
}
elseif ([oneValueconformsToProtocol:@protocol(NSMutableCopying)]) {
oneCopy = [oneValuemutableCopy];
}
if (oneCopy ==nil) {
oneCopy = [oneValuecopy];
}
[newDictsetObject:oneCopyforKey:key];
[oneCopyrelease];
}
return newDict;
}
@end
@implementation NSArray(MutableDeepCopy)
- (NSMutableArray *)mutableDeepCopyOfArray {
NSMutableArray *newArray = [NSMutableArrayarrayWithCapacity:[selfcount]];
for (int i = 0; i < [selfcount]; i++) {
id oneValue = [selfobjectAtIndex:i];
id oneCopy =nil;
if ([oneValuerespondsToSelector:@selector(mutableDeepCopyOfArray)]) {
oneCopy = [[oneValuemutableDeepCopyOfArray]retain];
}
elseif ([oneValuerespondsToSelector:@selector(mutableDeepCopyOfDictionary)]) {
oneCopy = [[oneValuemutableDeepCopyOfDictionary]retain];
}
elseif ([oneValueconformsToProtocol:@protocol(NSMutableCopying)]) {
oneCopy = [oneValuemutableCopy];
}
if (oneCopy ==nil) {
oneCopy = [oneValuecopy];
}
[newArrayaddObject:oneCopy];
[oneCopyrelease];
}
return newArray;
}
@end
// 使用时需要注意, 上面这个类别使用的是非ARC的。
通过上面这种全深次的拷贝, 出来的东东就是彻底的深拷贝, 所有的对象,都是mutable的
输出如下:
after dic={
CheckDic = (
1,
2
);
Name = Jacky;
TheArr = (
First,
Second
);
},
mutDic={
CheckDic = (
11,
22
);
Name = Zouzou;
TheArr = (
First,
Second,
Third
);
}
heqin补充:
被充一个深拷贝的方式,使用存档和取档的方式进行,相当于是把当前的内存转成NSData封存,然后再使用解档的方式从NSData生成对象,则生成的对象与原对象完全无关。真正意义上的深层次拷贝。
NSArray *array = [NSArray arrayWithObjects:[NSMutableString stringWithString:@"first"],[NSStringstringWithString:@"b"],@"c",nil];
NSArray *deepCopyArray=[[NSArray alloc] initWithArray: array copyItems: YES];
NSArray* trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array]];