关于copy, mutableCopy, 浅拷贝,深拷贝

时间:2021-05-02 19:51:12

随便写一个类, 继承自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]];