iOS--拷贝

首先说明两个概念

容器类对象:系统的容器类对象,指NSArray,NSDictionary等

非容器类对象:系统的非容器类对象 这里指的是NSString,NSNumber等一类的对象。


拷贝实际上分为三种:

* 浅拷贝(shallow copy):指针拷贝,对于被拷贝对象的每一层都是指针拷贝,拷贝前后的指针指向同一块内存地址,不产生新的对象,源对象的引用计数器+1。

* 深拷贝(one-level-deep copy):内存块拷贝,拷贝后的指针指向拷贝后的内存块。但是这里有一个坑,就是深拷贝只是深拷贝对象自身这一层,是单层深拷贝,对于容器类对象,容器内各层元素对象依然是浅拷贝。

* 完全深拷贝(real-deep copy): 在完全深拷贝操作时,对于被拷贝对象的每一层都是深拷贝,容器本身的地址和容器内元素的地址都不一样。

不管是容器类对象还是非容器类对象,copy返回的都是不可变对象,mutableCopy返回的是可变对象。 一个NSObject的对象要想使用这两个函数,那么类必须实现NSCopying协议和NSMutableCopying协议,包括系统容器类对象中包含的元素。 对于NSCopying,实现- (id)copyWithZone:(NSZone*)zone方法。 对于NSMutableCopying,实现- (id)mutableCopyWithZone:(NSZone *)zone方法。系统提供的NSString,NSArray,NSDictionary等都已实现,我们自定义的类需要手动实现。

接下来我们看几个例子来看一下copy和mutableCopy的区别:

示例一:非容器类的copy和mutableCopy,以NSString为例

//不可变字符串的浅拷贝和深拷贝

    NSString *originStr = [[NSString alloc] initWithFormat:@"这是一个原始字符串"];

    NSLog(@"originStr:%p---%p---%@---%ld", &originStr, originStr, originStr,CFGetRetainCount((__bridge  CFTypeRef)(originStr)));

    NSString*cpStr = [originStrcopy];

    NSLog(@"cpStr    :%p---%p---%@---%ld---%ld", &cpStr, cpStr, cpStr,CFGetRetainCount((__bridge  CFTypeRef)(originStr)),CFGetRetainCount((__bridge  CFTypeRef)(cpStr)));

    NSString*mcyStr = [originStrmutableCopy];

    NSLog(@"mcyStr  :%p---%p---%@---%ld---%ld---%ld", &mcyStr, mcyStr, mcyStr,CFGetRetainCount((__bridge  CFTypeRef)(originStr)),CFGetRetainCount((__bridge  CFTypeRef)(cpStr)),CFGetRetainCount((__bridge  CFTypeRef)(mcyStr)));

    NSLog(@"—————————————————————————————————");

    //不可变字符串的浅拷贝和深拷贝

    NSMutableString *moriStr = [[NSMutableString alloc] initWithString:@"这是一个原始字符串"];

    NSLog(@"moriStr  :%p---%p---%@---%ld", &moriStr, moriStr, moriStr,CFGetRetainCount((__bridge  CFTypeRef)(moriStr)));

    NSMutableString*mcpStr = [moriStrcopy];

    NSLog(@"mcpStr    :%p---%p---%@---%ld---%ld", &mcpStr, mcpStr, mcpStr,CFGetRetainCount((__bridge  CFTypeRef)(moriStr)),CFGetRetainCount((__bridge  CFTypeRef)(mcpStr)));

    NSMutableString*mmcyStr = [moriStrmutableCopy];

    NSLog(@"mmcyStr  :%p---%p---%@---%ld---%ld---%ld", &mmcyStr, mmcyStr, mmcyStr,CFGetRetainCount((__bridge  CFTypeRef)(moriStr)),CFGetRetainCount((__bridge  CFTypeRef)(mcpStr)),CFGetRetainCount((__bridge  CFTypeRef)(mmcyStr)));

运行看一下输出:从左往右依次输出拷贝操作后指针地址,对象内存地址,对象,引用计数

 originStr:0x7ffeefbe41b8---0x60000334e790---这是一个原始字符串---1

  cpStr    :0x7ffeefbe41b0---0x60000334e790---这是一个原始字符串---2---2

 mcyStr  :0x7ffeefbe41a8---0x60000337a4c0---这是一个原始字符串---2---2---1

2020-07-08 13:43:48.023203+0800 iOS_copy[4153:3626248] —————————

moriStr  :0x7ffeefbe41a0---0x60000337c9f0---这是一个原始字符串---1

mcpStr    :0x7ffeefbe4198---0x60000337cae0---这是一个原始字符串---1---1

mmcyStr  :0x7ffeefbe4190---0x60000337cc00---这是一个原始字符串---1---1---1


        先看分割线上面不可变字符串的copy和mutableCopy。copy操作生成新的指针指向当前对象地址,引用计数在copy操作后变成了2,说明copy操作对不可变对象会引起源对象引用计数变化。mutableCopy指针地址和对象内存地址都是新生成的,mutableCopy后的对象是引用计数为1的新对象,源对象引用计数不受影响。

再看分割线下面可变字符串的copy和mutableCopy。可见无论是copy还是mutableCopy操作,都是生成新的指针指向新开辟的内存,源对象和新对象引用计数都为 1 ,引用计数不受影响。


示例二:元素为不可变的不可变容器的copy和mutableCopy

NSArray*originArray =@[@"北京市",@[@"昌平区",@[@"天通苑"]]];

    NSLog(@"originArray:%@---%p---%p---%p---%ld", [originArrayclass], &originArray, originArray, originArray[0],CFGetRetainCount((__bridge  CFTypeRef)(originArray)));

    NSArray*cyArray = [originArraycopy];

    NSLog(@"cyArray    :%@---%p---%p---%p---%ld---%ld", [cyArrayclass], &cyArray, cyArray, cyArray[0],CFGetRetainCount((__bridge  CFTypeRef)(originArray)),CFGetRetainCount((__bridge  CFTypeRef)(cyArray)));

    NSArray*mcyArray = [originArraymutableCopy];

          NSLog(@"mcyArray  :%@---%p---%p---%p---%ld---%ld", [mcyArrayclass], &mcyArray, mcyArray, mcyArray[0],CFGetRetainCount((__bridge  CFTypeRef)(originArray)),CFGetRetainCount((__bridge  CFTypeRef)(mcyArray)));

运行结果:数组的类型---指向对象的指针的地址---对象内存地址---首元素内存地址----引用计数

originArray:__NSArrayI---0x7ffeee273188---0x600000cf89a0---0x10198d060---1 cyArray   :__NSArrayI---0x7ffeee273180---0x600000cf89a0---0x10198d060---2---2  mcyArray  :__NSArrayM---0x7ffeee273178---0x6000002c33c0---0x10198d060---2---1

__NSArrayI <-->  NSArray          __NSArrayM <-->  NSMutableArray

可以看到对originArray进行copy操作后的cyArray是NSArray不可变类型,生成了新的指针,指向了和originArray同一块内存地址,数组内元素地址没有变化。同时cyArray和originArray的引用计数为 2,说明对不可变对象的copy操作会引起对象引用计数加 1 ,与上面例子中相印证。

对originArray进行了mutableCopy操作后的mcyArray是NSMutableArray可变数组,生成了新的指针,同时开辟新的内存地址,但是数组内的元素地址没有变化。源对象originArray引用计数仍然为 2,mcyArray是引用计数为 1 的新对象。


示例二:元素可变的不可变容器的copy和mutableCopy

NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"TianTongyuan", @"Huilongguan", nil];         

NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];   

NSArray *originArray = @[@"Beijing", disticts];   

NSLog(@"originArray:%@---%p---%p---%p---%ld", [originArray class], &originArray, originArray, originArray[0], CFGetRetainCount((__bridge  CFTypeRef)(originArray)));    

NSArray *cyArray = [originArray copy];   

NSLog(@"cyArray    :%@---%p---%p---%p---%ld---%ld", [cyArray class], &cyArray, cyArray, cyArray[0], CFGetRetainCount((__bridge  CFTypeRef)(originArray)), CFGetRetainCount((__bridge  CFTypeRef)(cyArray)));   

NSArray *mcyArray = [originArray mutableCopy];   

NSLog(@"mcyArray   :%@---%p---%p---%p---%ld---%ld", [mcyArray class], &mcyArray, mcyArray, mcyArray[0], CFGetRetainCount((__bridge  CFTypeRef)(originArray)), CFGetRetainCount((__bridge  CFTypeRef)(mcyArray)));    


NSLog(@"%@---\n%@---\n%@", originArray[1], cyArray[1], mcyArray[1]);   

[disticts addObject:@"Haidian"]; 

NSLog(@"%@---\n%@---\n%@", originArray[1], cyArray[1], mcyArray[1]);

运行结果

originArray:__NSArrayI---0x7ffedfe5c190---0x60000355f480---0x10fda40c0---1

cyArray    :__NSArrayI---0x7ffedfe5c188---0x60000355f480---0x10fda40c0---2---2

mcyArray  :__NSArrayM---0x7ffedfe5c180---0x600003b14b10---0x10fda40c0---2---1

2020-07-08 14:52:25.644210+0800 iOS_copy[6633:3675474] (

    Changping,

        (

        TianTongyuan,

        Huilongguan

    )

)---originArray[1]

(

    Changping,

        (

        TianTongyuan,

        Huilongguan

    )

)---cyArray[1]

(

    Changping,

        (

        TianTongyuan,

        Huilongguan

    )

)---mcyArray[1]

2020-07-08 14:52:25.644368+0800 iOS_copy[6633:3675474] (

    Changping,

        (

        TianTongyuan,

        Huilongguan

    ),

    Haidian

)---originArray[1]

(

    Changping,

        (

        TianTongyuan,

        Huilongguan

    ),

    Haidian

)---cyArray[1]

(

    Changping,

        (

        TianTongyuan,

        Huilongguan

    ),

    Haidian

)---mcyArray[1]

对于copy和mutableCopy前后原对象和新对象指针和内存地址以及引用计数参考上面示例二。

与示例二区别的是后面 disticts 数组改变前后的输出,在向disticts内添加@“Haidian”元素后,三个数组中对应disticts的元素originArray[1],cyArray[1],mcyArray[1],都相应变化。因为无论是copy还是mutableCopy,都没有对数组内元素深拷贝,只是生成新的元素指针指向同一个对应的内存地址,当这个元素是可变数组且发生变化是,拷贝操作前后的数组内对应元素都会相应变化。

示例三   包含可变元素的可变容器的拷贝

NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"TianTongyuan", @"Huilongguan", nil];

    NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];

    NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];

    NSLog(@"originArray:%@---%p---%p---%p---%ld", [originArrayclass], &originArray, originArray, originArray[1],CFGetRetainCount((__bridge  CFTypeRef)(originArray)));

    NSArray*cyArray = [originArraycopy];

    NSLog(@"cyArray    :%@---%p---%p---%p---%ld---%ld", [cyArrayclass], &cyArray, cyArray, cyArray[1],CFGetRetainCount((__bridge  CFTypeRef)(originArray)),CFGetRetainCount((__bridge  CFTypeRef)(cyArray)));

    NSArray*mcyArray = [originArraymutableCopy];

    NSLog(@"mcyArray  :%@---%p---%p---%p---%ld---%ld", [mcyArrayclass], &mcyArray, mcyArray, mcyArray[1],CFGetRetainCount((__bridge  CFTypeRef)(originArray)),CFGetRetainCount((__bridge  CFTypeRef)(mcyArray)));


    NSLog(@"%@---originArray\n%@---cyArray\n%@---mcyArray", originArray, cyArray, mcyArray);

    [originArrayaddObject:@"Tianjin"];

    [distictsaddObject:@"Haidian"];

    NSLog(@"%@---originArray\n%@---cyArray\n%@---mcyArray", originArray, cyArray, mcyArray);

运行结果

originArray:__NSArrayM---0x7ffee84181a8---0x60000245e730---0x60000245ea00---1 cyArray    :__NSArrayI---0x7ffee84181a0---0x600002a2c540---0x60000245ea00---1---1

mcyArray   :__NSArrayM---0x7ffee8418198---0x60000243dc50---0x60000245ea00---1---1

2020-07-08 15:15:30.426782+0800 iOS_copy[7384:3690010] (

    Beijing,

        (

        Changping,

                (

            TianTongyuan,

            Huilongguan

        )

    )

)---originArray

(

    Beijing,

        (

        Changping,

                (

            TianTongyuan,

            Huilongguan

        )

    )

)---cyArray

(

    Beijing,

        (

        Changping,

                (

            TianTongyuan,

            Huilongguan

        )

    )

)---mcyArray

2020-07-08 15:15:30.427023+0800 iOS_copy[7384:3690010] (

    Beijing,

        (

        Changping,

                (

            TianTongyuan,

            Huilongguan

        ),

        Haidian

    ),

    Tianjin

)---originArray

(

    Beijing,

        (

        Changping,

                (

            TianTongyuan,

            Huilongguan

        ),

        Haidian

    )

)---cyArray

(

    Beijing,

        (

        Changping,

                (

            TianTongyuan,

            Huilongguan

        ),

        Haidian

    )

)---mcyArray

执行了copy后得到的cyArray 是不可变数组,  mutableCopy后得到的 mcyArray是可变,两个数组都是生成新的指针指向新开辟的内存块,因此,源数组内直接添加元素@"Tianjin"并不会引起拷贝后数组的变化。我们还看到拷贝前后数组内元素地址没变化,因为数组内元素不变,所以我们再向disticts内添加@“Haidian”后,三个数组对应元素的值都发生了变化。


小结

1、对于系统的非容器类对象(NSString,NSNumber等),如果对一不可变对象进行拷贝,copy是指针拷贝(浅拷贝),而mutableCopy就是内存块拷贝(深拷贝)。如果是对一可变对象拷贝,都是深拷贝,但是copy返回的对象是不可变的。

2、对于系统的容器类对象(NSArray,NSDictionary等),如果对一不可变对象进行拷贝,包括对象指针和对象内元素指针,copy是指针拷贝(浅拷贝),而mutableCopy只是对对象本身是内存块拷贝(深拷贝),对象内元素依然是指针拷贝(浅拷贝)。如果是对一可变对象拷贝,都是对对象本身是内存块拷贝(深拷贝),对象内元素依然是指针拷贝(浅拷贝),但是copy返回的对象是不可变的。

3、对于容器类对象中包含的可变元素,当这个元素改变(增,删,改)时,copy和mutableCopy后的对象中对应元素也会跟随变化,这个需要注意。


示例四 多层深拷贝

上面在对容器类进行深拷贝是只是深拷贝了容器这一层,对于容器内元素对象依然是浅拷贝,怎样才能对元素对象也进行深拷贝呢?NSArray有一个- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag;函数,我们可以先用一下。

当flag为 NO 时

NSMutableArray *estates = [[NSMutableArray alloc] initWithObjects:@"Longtengyuan", nil];

    NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"Huilongguan", estates, nil];

    NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];

    NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];


    NSMutableArray *array1 = [[NSMutableArray alloc] initWithArray:originArray copyItems:NO];

    NSArray *array2 = [[NSArray alloc] initWithArray:originArray copyItems:NO];

    NSLog(@"%@-%p-%p----%@-%p-%p", [array1class], array1, array1[1], [array2class], array2, array2[1]);


    NSArray*originArray1 = [[NSArrayalloc]initWithObjects:@"Beijing", disticts,nil];

    NSMutableArray *array3 = [[NSMutableArray alloc] initWithArray:originArray1 copyItems:NO];

    NSArray *array4 = [[NSArray alloc] initWithArray:originArray1 copyItems:NO];

    NSLog(@"%@-%p-%p----%@-%p-%p", [array3class], array3, array3[1], [array4class], array4, array4[1]);

运行结果

__NSArrayM-0x600001115050-0x600001114ff0----__NSArrayI-0x600001f64940-0x600001114ff0

__NSArrayM-0x60000112def0-0x600001114ff0----__NSArrayI-0x600001f722a0-0x600001114ff0

可以看出flag为 NO 时,这个函数都是对源对象进行了深拷贝,但是仅限于数组自身单层深拷贝,数组内各层元素对象依然是浅拷贝,返回是否可变对象取决于是否使用可变类创建。

flag为YES时:

NSMutableArray *estates = [[NSMutableArray alloc] initWithObjects:@"Longtengyuan", nil];

 NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"Huilongguan", estates, nil];

    NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];

    NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];


    NSMutableArray *array1 = [[NSMutableArray alloc] initWithArray:originArray copyItems:YES];

    NSArray *array2 = [[NSArray alloc] initWithArray:originArray copyItems:YES];

    NSLog(@"%p--%p--%p--%@", array1, array1[1], array1[1][1], [array1[1][1]class]);

    NSLog(@"%p--%p--%p--%@",array2, array2[1], array2[1][1], [array2[1][1]class]);


    [estatesaddObject:@"Longhuayuan"];

    NSLog(@"%@", array1[1][1]);

    NSLog(@"%@", array2[1][1]);

运行结果

0x600003086a60--0x600003ea2000--0x600003086730--__NSArrayM

0x600003ea1f80--0x600003ea1f60--0x600003086730--__NSArrayM

2020-07-08 16:49:30.747854+0800 iOS_copy[10520:3756490] (

    Huilongguan,

        (

        Longtengyuan,

        Longhuayuan

    )

)

2020-07-08 16:49:30.748034+0800 iOS_copy[10520:3756490] (

    Huilongguan,

        (

        Longtengyuan,

        Longhuayuan

    )

)

flag为YES,我们看到数组内第一层元素地址也发生了变化,但是第二层的元素地址没变化。

这说明- (instancetype)initWithArray:(NSArray *)array copyItems:(BOOL)flag函数在flag为YES时也只是对源对象本身和第一层的元素进行了深拷贝,更深的元素依然是浅拷贝。这依然不是我们想要的的完全深拷贝。 虽然写了多层深拷贝,实质也就是双重深拷贝。

示例五 真正的完全深拷贝

NSMutableArray *estates = [[NSMutableArray alloc] initWithObjects:@"Longtengyuan", @"Longboyuan", nil];

    NSMutableArray *towns = [[NSMutableArray alloc] initWithObjects:@"Huilongguan", estates, nil];

    NSMutableArray *disticts = [[NSMutableArray alloc] initWithObjects:@"Changping", towns, nil];

    NSMutableArray *originArray = [[NSMutableArray alloc] initWithObjects:@"Beijing", disticts, nil];

//    if (@available(iOS 12.0, *)) {

//        NSArray *deepCpArray = [NSKeyedUnarchiver unarchivedObjectOfClass:[originArray class] fromData:[NSKeyedArchiver archivedDataWithRootObject:originArray requiringSecureCoding:NO error:nil] error:nil];

//    }

    NSArray *deepCpArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:originArray]];

    NSLog(@"%p--%p--%p--%@", originArray, originArray[1], originArray[1][1], [originArray[1][1]class]);

    NSLog(@"%p--%p--%p--%@", deepCpArray, deepCpArray[1], deepCpArray[1][1], [deepCpArray[1][1]class]);

  [estatesaddObject:@"Longhuayuan"];

    NSLog(@"%@", originArray[1][1]);

    NSLog(@"%@", deepCpArray[1][1]);

运行Log:

0x600003f902d0--0x600003f90330--0x600003f90210--__NSArrayM

0x600003f90510--0x600003f90150--0x600003f901e0--__NSArrayM

2020-07-08 17:23:54.148555+0800 iOS_copy[11623:3775644] (

    Huilongguan,

        (

        Longtengyuan,

        Longboyuan,

        Longhuayuan

    )

)

2020-07-08 17:23:54.148683+0800 iOS_copy[11623:3775644] (

    Huilongguan,

        (

        Longtengyuan,

        Longboyuan

    )

)

如偿所愿,使用归档解档的方法,实现了数组和数组内的每层元素的深拷贝。需要注意的是,数组内的元素需要实现NSCoding协议。

你可能感兴趣的:(iOS--拷贝)