美文网首页
iOS 深拷贝和浅拷贝

iOS 深拷贝和浅拷贝

作者: Good_Citizen | 来源:发表于2019-12-04 14:41 被阅读0次

    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)也完全深拷贝了!

    相关文章

      网友评论

          本文标题:iOS 深拷贝和浅拷贝

          本文链接:https://www.haomeiwen.com/subject/wbjkgctx.html