iOS 深拷贝和浅拷贝

2019-12-04  本文已影响0人  Good_Citizen

iOS中拷贝的目的在于和源数据互不影响,修改源数据不会影响拷贝出来的对象,同样修改拷贝出来的对象也不会影响到源数据,记住这几条原则!

copy和mutableCopy

copy是拷贝出一个不可变数据

mutableCopy是拷贝出一个可变数据

下面看下例子,对不可变数据进行copy操作(浅拷贝,指向同一块内存区域)

#import <Foundation/Foundation.h>

int main(int argc,const char* argv[]) {

    @autoreleasepool {

        NSString*str1 =@"123";

        NSString*str2 = [str1copy];

        NSLog(@"%@,%@",str1,str2);

        NSLog(@"%p,%p",str1,str2);

    }

    return 0;

}

打印结果:

 iOS深拷贝和浅拷贝[1558:20300] 123,123

 iOS深拷贝和浅拷贝[1558:20300] 0x100001038,0x100001038

可以看出,str1源数据是不可变数据,对不可变数据进行copy操作也会生成一个不可变数据,所以它们的值是一样的,但它们的地址也是一样的,也就是指向了同一块内存区域,感觉和之前说的互不影响冲突了!原因是由于不可变数据本身是不能被修改的,所以不存在哪一方被修改了会影响到另一方的说法,而且就算再重新分配一个内存来存放copy出来的数据,效果和这个也是一样,都是不能被修改的,所以编译器为了节省内存,让它们指向同一块内存

对不可变数据进行mutableCopy操作(深拷贝,指向不同的内存区域)

int main(int argc,const char* argv[]) {

    @autoreleasepool {

        NSString*str1 =@"123";

        NSMutableString*str2 = [str1 mutableCopy];

        [str2 appendString:@"456"];

        NSLog(@"%@,%@",str1,str2);

        NSLog(@"%p,%p",str1,str2);

    }

    return 0;

}

打印结果:

iOS深拷贝和浅拷贝[1886:25351] 123,123456

iOS深拷贝和浅拷贝[1886:25351] 0x100001040,0x10075e790

可以看出,对源数据为不可变数据进行mutableCopy操作后,会生成一个全新的可变数据,两个对象指向的是不同的内存区域,而且修改可变数据不会对源数据造成影响

对可变数据进行copy操作(深拷贝,指向不同的内存区域)

        NSMutableString *str1 = [NSMutableString stringWithString:@"123"];

        NSString*str2 = [str1copy];

        NSLog(@"%@,%@",str1,str2);

        NSLog(@"%p,%p",str1,str2);

打印结果:

iOS深拷贝和浅拷贝[1966:26578] 123,123

iOS深拷贝和浅拷贝[1966:26578] 0x1007577d0,0x3ee35425603786ad

可以看出对可变数据进行copy操作会生成一个全新的不可变数据,指向的是不同的内存区域

对可变数据进行mutableCopy操作(深拷贝,指向不同的内存区域)

        NSMutableString *str1 = [NSMutableString stringWithString:@"123"];

        NSMutableString*str2 = [str1mutableCopy];

        [str2appendString:@"789"];

        NSLog(@"%@,%@",str1,str2);

        NSLog(@"%p,%p",str1,str2);

打印结果:

iOS深拷贝和浅拷贝[2053:28097] 123,123789

iOS深拷贝和浅拷贝[2053:28097] 0x10186d370,0x10186d430

可以看出对可变数据进行mutableCopy操作会生成一个全新的可变数据,指向的是不同的内存区域,而且修改全新的数据不会对源数据造成影响

对其他可变不可变数据(NSDictionary,NSMutableDictionary,NSArray,NSMutableArray,NSSet,NSMutableSet)进行操作也是一样的效果,可以自己尝试下!

结论:

对不可变数据进行copy操作就是浅拷贝,对不可变数据进行mutableCopy操作就是深拷贝!

对可变数据进行copy操作就是深拷贝,对可变数据进行mutableCopy操作也是深拷贝!

再看下自定义对象的copy操作

上面说的都是系统自带的类,而且系统自带的类都是遵守了NSCoping、NSMutableCopying协议

@interface NSString : NSObject <NSCopying, NSMutableCopying, NSSecureCoding>

但是自定义对象却没有,需要我们手动添加,如果直接使用自定义对象调用copy,会报一个经典错误unrecognized selector

 iOS深拷贝和浅拷贝[2452:35056] -[WPPerson copyWithZone:]: unrecognized selector sent to instance 0x10051cc00

 iOS深拷贝和浅拷贝[2452:35056] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[WPPerson copyWithZone:]: unrecognized selector sent to instance 0x10051cc00'

*** First throw call stack:

(

0  CoreFoundation                      0x00007fff2fe9ccfd __exceptionPreprocess + 256

1  libobjc.A.dylib                    0x00007fff5a546a17 objc_exception_throw + 48

2  CoreFoundation                      0x00007fff2ff16b06 -[NSObject(NSObject) __retain_OA] + 0

3  CoreFoundation                      0x00007fff2fe3eb8f ___forwarding___ + 1485

4  CoreFoundation                      0x00007fff2fe3e538 _CF_forwarding_prep_0 + 120

6  libdyld.dylib                      0x00007fff5bd143d5 start + 1

7  ???                                0x0000000000000001 0x0 + 1

)

libc++abi.dylib: terminating with uncaught exception of type NSException

原因是由于我们没有实现copyWithZone方法,查看源码可以看到copy方法其实是调用了copyWithZone方法

- (id)copy {

    return [(id)self copyWithZone:nil];

}

先看下重写copyWithZone直接返回self,会发生什么情况

#import "WPPerson.h"

@interface WPPerson()<NSCopying>

@end

@implementation WPPerson

-(id)copyWithZone:(NSZone*)zone{

    return self;

}

@end

        WPPerson *p1 = [[WPPerson alloc] init];

        WPPerson*p2 = [p1 copy];

        NSLog(@"%@,%@",p1,p2);

打印结果:

iOS深拷贝和浅拷贝[2932:43861] <WPPerson: 0x10057f8d0>,<WPPerson: 0x10057f8d0>

可以看到p1和p2是指向的同一块内存地址,也就是浅拷贝,修改p1会对p2造成影响,所以copyWithZone方法里面可以随意控制你想要的copy机制,如果想要进行深拷贝,可以这样写

-(id)copyWithZone:(NSZone*)zone{

    WPPerson*p = [WPPerson allocWithZone:zone];

    return p;

}


        WPPerson *p1 = [[WPPerson alloc] init];

        WPPerson*p2 = [p1copy];

        NSLog(@"%@,%@",p1,p2);

打印结果:

iOS深拷贝和浅拷贝[3118:46781] <WPPerson: 0x10180b090>,<WPPerson: 0x10180b7d0>

可以看出p1和p2指向的是不同的内存地址,但我想要将p1对象里面的属性值也拷贝到p2中,又该怎么做呢?

-(id)copyWithZone:(NSZone*)zone{

    WPPerson*p = [WPPerson allocWithZone:zone];

    p.age=self.age;

    p.name=self.name;

    return p;

}

-(NSString*)description{

    return [NSString stringWithFormat:@"name == %@,age == %d,address == %p",self.name,self.age,self];

}


WPPerson *p1 = [[WPPerson alloc] init];

        p1.name=@"jerry";

        p1.age=15;

        WPPerson*p2 = [p1 copy];

        NSLog(@"%@,%@",p1,p2);

        p2.name=@"tom";

        NSLog(@"%@,%@",p1,p2);


打印结果:

iOS深拷贝和浅拷贝[3244:49034] name == jerry,age == 15,address == 0x10078aa20,name == jerry,age == 15,address == 0x10078b1f0

iOS深拷贝和浅拷贝[3244:49034] name == jerry,age == 15,address == 0x10078aa20,name == tom,age == 15,address == 0x10078b1f0

可以在copyWithZone方法中直接进行属性赋值,而且修改p1,p2是不会受到影响

如果p1里面有集合这种数据属性,copy之后想做到完全深拷贝,又该如何做呢?如果按照之前的简单赋值,看下会发生什么情况

-(id)copyWithZone:(NSZone*)zone{

    WPPerson*p = [WPPerson allocWithZone:zone];

    p.age=self.age;

    p.name=self.name;

    p.arrData = self.arrData;

    returnp;

}

-(NSString*)description{

    return [NSString stringWithFormat:@"name == %@,age == %d,arrData == %@,address == %p",self.name,self.age,self.arrData,self];

}

-(NSMutableArray *)arrData{

    if(_arrData==nil) {

        _arrData= [NSMutableArray array];

    }

    return _arrData;

}


WPPerson *p1 = [[WPPerson alloc] init];

        p1.name=@"jerry";

        p1.age=15;

        [p1.arrData addObject:@"111"];

        WPPerson*p2 = [p1 copy];

        NSLog(@"%@,%@",p1,p2);

        p2.name=@"tom";

        [p2.arrData addObject:@"222"];

        NSLog(@"%@,%@",p1,p2);


打印结果:

 name == jerry,age == 15,arrData == (

    111

),address == 0x100704870,

name == jerry,age == 15,arrData == (

    111

),address == 0x100705100

name == jerry,age == 15,arrData == (

    111,

    222

),address == 0x100704870,name == tom,age == 15,arrData == (

    111,

    222

),address == 0x100705100

可以看出当改变p2中的集合数据时,p1中的数据也跟着发生改变,因此自定义对象中如果有集合这种数据类型时,使用简单的赋值并不能做到完全深拷贝,所以只需要在赋值操作时通过mutableCopy拷贝出一个全新的集合对象,这样就达到了完全深拷贝!

-(id)copyWithZone:(NSZone*)zone{

    WPPerson*p = [WPPerson allocWithZone:zone];

    p.age=self.age;

    p.name=self.name;

    p.arrData= [self.arrData mutableCopy];

    return p;

}


打印结果:
name == jerry,age == 15,arrData == (

    111

),address == 0x100786df0,name == jerry,age == 15,arrData == (

    111

),address == 0x100787680

name == jerry,age == 15,arrData == (

    111

),address == 0x100786df0,name == tom,age == 15,arrData == (

    111,

    222

),address == 0x100787680

最后再看一种特殊的情况,自定义对象中有一个集合属性,而且集合中装的都是另一个自定义对象,看代码

WPCar.h文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface WPCar : NSObject

@property(nonatomic,assign)int weight;

@end

NS_ASSUME_NONNULL_END

WPCar.m文件

#import "WPCar.h"

@implementation WPCar

-(NSString *)description{

    return[NSString stringWithFormat:@"weight == %d,address == %p",self.weight,self];

}

@end

WPPerson.h文件

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interfaceWPPerson :NSObject

@property(nonatomic,strong)NSMutableArray *arrData;

@end

NS_ASSUME_NONNULL_END

WPPerson.m文件

#import "WPPerson.h"

@interface WPPerson()<NSCopying>

@end

@implementation WPPerson

-(id)copyWithZone:(NSZone*)zone{

    WPPerson*p = [WPPerson allocWithZone:zone];

    p.arrData= [self.arrData mutableCopy];

    returnp;

}

-(NSString*)description{

    return [NSString stringWithFormat:@"arrData == %@,address == %p",self.arrData,self];

}

-(NSMutableArray *)arrData{

    if(_arrData==nil) {

        _arrData= [NSMutableArray array];

    }

    return _arrData;

}

@end

再看下外部使用情况

        WPCar*car1 = [[WPCar alloc]init];

        car1.weight=11;

        WPPerson*p1 = [[WPPerson alloc]init];

        [p1.arrData addObject:car1];

        WPPerson*p2 = [p1copy];

        NSLog(@"%@,%@",p1,p2);

        WPCar*car2 = [[WPCar alloc]init];

        [p2.arrData addObject:car2];

        WPCar*carTmp = p2.arrData[0];

        carTmp.weight=22;

        NSLog(@"%@,%@",p1,p2);

打印结果:

arrData == (

    "weight == 11,car_address == 0x100624af0"

),person_address == 0x100624900,

arrData == (

    "weight == 11,car_address == 0x100624af0"

),person_address == 0x100604590

arrData == (

    "weight == 22,car_address == 0x100624af0"

),person_address == 0x100624900,

arrData == (

    "weight == 22,car_address == 0x100624af0",

    "weight == 0,car_address == 0x10054e860"

),person_address == 0x100604590

可以看出p1和p2是不同的两个对象,而且p1和p2中的数组也是指向的不同内存区域,但是数组里面装的car对象却是指向的同一块内存区域,当我们修改p2中arrData数组中的car对象的weight属性时,p1中的weight也发生了变化,这样也达不到我们想要的需求,这时就需要用到[[NSMutableArray alloc] initWithArray:self.arrData copyItems:YES];方法来替代之前的mutableCopy了,该方法中的copyItems如果传入的是YES,那么表示该数组中的每个元素都会调用copy,所以需要WPCar对象也遵循NSCoping协议以及重写copyWithZone方法

#import "WPCar.h"

@interfaceWPCar()

@end

@implementation WPCar

-(id)copyWithZone:(NSZone *)zone{

    WPCar *p = [WPCar allocWithZone:zone];

    returnp;

}

#import "WPPerson.h"

@interface WPPerson()<NSCopying>

@end

@implementation WPPerson

-(id)copyWithZone:(NSZone*)zone{

    WPPerson*p = [WPPerson allocWithZone:zone];

    p.arrData = [[NSMutableArray alloc] initWithArray:self.arrData copyItems:YES];

//    p.arrData = [self.arrData mutableCopy];

    returnp;

}

打印结果:

arrData == (

    "weight == 11,address == 0x10058eba0"

),address == 0x10058e9b0,

arrData == (

    "weight == 0,address == 0x10057f8a0"

),address == 0x100580e70

arrData == (

    "weight == 11,address == 0x10058eba0"

),address == 0x10058e9b0,

arrData == (

    "weight == 22,address == 0x10057f8a0",

    "weight == 0,address == 0x100609630"

),address == 0x100580e70

这样就可以做到对自定义对象WPPerson中的集合对象(包含其他自定义对象WPCar)也完全深拷贝了!

上一篇 下一篇

猜你喜欢

热点阅读