iOS - 如何深拷贝Array内元素、自定义对象、及自定义对象的属性

关于深拷贝、浅拷贝,请看上篇iOS - 深拷贝、浅拷贝探索验证

关于深拷贝、浅拷贝,请看上篇iOS - 深拷贝、浅拷贝探索验证

  1. 上篇我们知道:
  • copy: 对NSArray是浅拷贝,NSMutableArray是深拷贝
  • mutableCopy: 始终是深拷贝
  • 无论深浅拷贝,集合对象内元素都是浅拷贝
  1. 本篇主要探索
  • 实现NSArray内元素的拷贝

通过本文你将知道

  • 深拷贝NSArray
  • 深拷贝NSArray内元素Person
  • 深拷贝NSArray内元素Person的属性nickname

本文代码较多,建议一步步耐心跟着代码排查,会有不少收货

探索过程

1. 系统Api

我们发现系统系统了方法- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag
其中copyItems:(BOOL)flag表示是否拷贝元素,我们先代码测试一下

    NSString *str1 = @"hello world";
    NSMutableString *str2 = [NSMutableString stringWithString:@"hello world"];
    NSArray *array1 = [NSArray arrayWithObjects: str1, str2, nil];
    NSMutableArray *array2 = [[NSMutableArray alloc] initWithArray:array1 copyItems:true];

    NSLog(@"\n array1 = %p class = %@ \n", array1, [array1 class]);
    NSLog(@"\n array2 = %p class = %@ \n", array2, [array2 class]);

    NSLog(@"\n\n======== 元素是String ======== ");
    NSLog(@"\n obj0 = %p class = %@ \n", array1[0], [array1[0] class]);
    NSLog(@"\n obj0 = %p class = %@ \n", array2[0], [array2[0] class]);

    NSLog(@"\n\n======== 元素是mutableString ========");
    NSLog(@"\n obj0 = %p class = %@ \n", array1[1], [array1[1] class]);
    NSLog(@"\n obj0 = %p class = %@ \n", array2[1], [array2[1] class]);

查看输出(为了下文对比,我对输出做了line标记)

2021-05-09 10:59:16.343163+0800 AlgorithmDemo[9904:51756] 
 array1 = 0x100607650 class = __NSArrayI  (line1)
 array2 = 0x1006074a0 class = __NSArrayM (line2)

======== 元素是String ======== 
 obj0 = 0x100008220 class = __NSCFConstantString (line3)
 obj0 = 0x100008220 class = __NSCFConstantString (line4)

======== 元素是mutableString ========
 obj0 = 0x100606330 class = __NSCFString (line5)
 obj0 = 0x100605430 class = __NSCFString (line6)

根据输出我们对得出系统initWithArray : copyItems :方法如下结论

  • 该方法对NSArray对象是深拷贝 (严格意义来说此处不能称为拷贝,是生成新对象) (line1 line2)
  • 当数组内元素为不可变对象时,该方法对元素执行浅拷贝(line3 line4)
  • 当数组内元素为可变对象时,该方法对元素执行深拷贝(line4 line5)

由此我们知道,当我们想深拷贝NSArray及其内部的元素时,用系统方法- (instancetype)initWithArray:(NSArray<ObjectType> *)array copyItems:(BOOL)flag是不行的

2. 归档

如果我们对Array执行归档再解档,结果会是什么样的呢,我们代码测试一下

    NSString *str1 = @"hello world";
    NSMutableString *str2 = [NSMutableString stringWithString:@"hello world"];
    NSArray *array1 = [NSArray arrayWithObjects: str1, str2, nil];
    NSMutableArray *array2 = [[NSMutableArray alloc] initWithArray:array1 copyItems:true];
    
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array1];
    NSMutableArray *array3 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    
    NSLog(@"\n array1 = %p class = %@ \n", array1, [array1 class]);
    NSLog(@"\n array2 = %p class = %@ \n", array2, [array2 class]);
    NSLog(@"\n array3 = %p class = %@ \n", array3, [array3 class]);
    
    NSLog(@"\n\n======== 元素是String ======== ");
    NSLog(@"\n obj0 = %p class = %@ \n", array1[0], [array1[0] class]);
    NSLog(@"\n obj0 = %p class = %@ \n", array2[0], [array2[0] class]);
    NSLog(@"\n obj0 = %p class = %@ \n", array3[0], [array3[0] class]);
    
    NSLog(@"\n\n======== 元素是mutableString ========");
    NSLog(@"\n obj0 = %p class = %@ \n", array1[1], [array1[1] class]);
    NSLog(@"\n obj0 = %p class = %@ \n", array2[1], [array2[1] class]);
    NSLog(@"\n obj0 = %p class = %@ \n", array3[1], [array3[1] class]);

查看输出

2021-05-09 11:14:50.710409+0800 AlgorithmDemo[12261:67019] 
 array1 = 0x1006b9e40 class = __NSArrayI  (line1)
 array2 = 0x1006ba180 class = __NSArrayM  (line1)
 array3 = 0x1006b7410 class = __NSArrayI  (line1)

======== 元素是String ======== 
 obj0 = 0x100008220 class = __NSCFConstantString  (line1)
 obj0 = 0x100008220 class = __NSCFConstantString  (line1)
 obj0 = 0x1006b6d20 class = __NSCFString  (line1)

======== 元素是mutableString ========
 obj0 = 0x1006b9cb0 class = __NSCFString  (line1)
 obj0 = 0x1006ba0e0 class = __NSCFString  (line1)
 obj0 = 0x1006b70e0 class = __NSCFString  (line1)

根据输出,我们很明显得出如下结论

  • 对Array归档并解档后,会生成新的Array对象
  • Array对象内的不可变元素进行了深拷贝
  • Array对象内的可变元素进行了深拷贝

由此,我们得出对如下总结

  • 只深拷贝NSArray对象,我们用mutableCopy就可以
  • 只深拷贝NSMutableArray对象,我们用Copy或mutableCopy都可以
  • 深拷贝Array对象及内部元素,我们可以通过归档的方法解决

3. 重写copyWithZone mutableCopyWithZone的方式深拷贝自定义对象

我们写一个person类测试一下

//
//  Person.h
//  AlgorithmDemo
//
//  Created by Ternence on 2021/5/9.
//

#import <Foundation/Foundation.h>

@interface Person : NSObject

@property (nonatomic, copy) NSString *nickname;

@end

测试代码如下

    Person *person = [[Person alloc] init];
    person.nickname = @"码代码的小马";
    
    NSMutableArray *array1 = [NSMutableArray arrayWithObjects:person, nil];
    
    NSArray *array2 = [array1 copy];
    NSMutableArray *array3 = [array1 mutableCopy];
    
    NSLog(@"\n array1 = %p class = %@", array1, [array1 class]);
    NSLog(@"\n array2 = %p class = %@", array2, [array2 class]);
    NSLog(@"\n array3 = %p class = %@", array3, [array3 class]);
    
    NSLog(@"\n\n======== 数组内元素 ======== ");
    NSLog(@"\n array1[0] = %p class = %@", array1[0], [array1[0] class]);
    NSLog(@"\n array2[0] = %p class = %@", array2[0], [array2[0] class]);
    NSLog(@"\n array3[0] = %p class = %@", array3[0], [array3[0] class]);

查看输出

2021-05-09 11:33:47.241685+0800 AlgorithmDemo[17319:89117] 
 array1 = 0x102a04450 class = __NSArrayM
 array2 = 0x102a04550 class = __NSSingleObjectArrayI
 array3 = 0x102a04570 class = __NSArrayM

======== 数组内元素 ======== 
 array1[0] = 0x102a042c0 class = Person
 array2[0] = 0x102a042c0 class = Person
 array3[0] = 0x102a042c0 class = Person

我们发现数组对象已经被深拷贝,但数组对象内的元素还是浅拷贝

是不是我们在定义Person类的时候没有重写方法呢? 我们把改一下Person类的代码,并对NSMutableArray内元素进行copy

! 此处的copyWithZone mutableCopyWithZone方法里,我们应该对属性也进行copy

//
//  Person.m
//  AlgorithmDemo
//
//  Created by Ternence on 2021/5/9.
//

#import "Person.h"

@interface Person ()<NSCopying, NSMutableCopying, NSCoding>

@end

@implementation Person

- (id)copyWithZone:(NSZone *)zone {
    Person *person = [Person allocWithZone:zone];
    person.nickname = [self.nickname copy];
    return person;
}

- (id)mutableCopyWithZone:(NSZone *)zone {
    Person *person = [Person allocWithZone:zone];
    person.nickname = [self.nickname mutableCopy];
    return person;
}

@end

测试代码

    Person *person = [[Person alloc] init];
    person.nickname = @"码代码的小马";
    
    NSMutableArray *array1 = [NSMutableArray arrayWithObjects:person, nil];
    
    NSArray *array2 = [array1 copy];
    NSMutableArray *array3 = [array1 mutableCopy];
    NSMutableArray *array4 = [[NSMutableArray alloc] initWithArray:array1 copyItems:true];
    NSMutableArray *array5 = [[NSMutableArray alloc] initWithObjects:[array1[0] copy], nil];
    NSMutableArray *array6 = [[NSMutableArray alloc] initWithObjects:[array1[0] mutableCopy], nil];
    
    NSLog(@"\n array1 = %p class = %@", array1, [array1 class]);
    NSLog(@"\n array2 = %p class = %@", array2, [array2 class]);
    NSLog(@"\n array3 = %p class = %@", array3, [array3 class]);
    NSLog(@"\n array4 = %p class = %@", array4, [array4 class]);
    NSLog(@"\n array5 = %p class = %@", array5, [array5 class]);
    NSLog(@"\n array6 = %p class = %@", array6, [array6 class]);
    
    NSLog(@"\n\n======== 数组内元素 ========");
    Person *orgArrayObj = (Person *)array1[0];
    Person *newArrayObj2 = (Person *)array2[0];
    Person *newArrayObj3 = (Person *)array3[0];
    Person *newArrayObj4 = (Person *)array4[0];
    Person *newArrayObj5 = (Person *)array5[0];
    Person *newArrayObj6 = (Person *)array6[0];
    NSLog(@"\n array1[0] = %p nickname = %p", orgArrayObj, orgArrayObj.nickname);
    NSLog(@"\n array2[0] = %p nickname = %p", newArrayObj2, newArrayObj2.nickname);
    NSLog(@"\n array3[0] = %p nickname = %p", newArrayObj3, newArrayObj3.nickname);
    NSLog(@"\n array4[0] = %p nickname = %p", newArrayObj4, newArrayObj4.nickname);
    NSLog(@"\n array5[0] = %p nickname = %p", newArrayObj5, newArrayObj5.nickname);
    NSLog(@"\n array6[0] = %p nickname = %p", newArrayObj6, newArrayObj6.nickname);

继续查看打印结果

2021-05-10 11:18:25.020830+0800 AlgorithmDemo[98320:429537] 
 array1 = 0x10041a790 class = __NSArrayM
 array2 = 0x100419b60 class = __NSSingleObjectArrayI
 array3 = 0x10041a880 class = __NSArrayM
 array4 = 0x10041a9f0 class = __NSArrayM
 array5 = 0x10041aa20 class = __NSArrayM
 array6 = 0x10041ac70 class = __NSArrayM

======== 数组内元素 ========
 array1[0] = 0x10041a340 nickname = 0x1000083a0 
 array2[0] = 0x10041a340 nickname = 0x1000083a0
 array3[0] = 0x10041a340 nickname = 0x1000083a0
 array4[0] = 0x100417660 nickname = 0x1000083a0 
 array5[0] = 0x100415370 nickname = 0x1000083a0
 array6[0] = 0x100412300 nickname = 0x10041ad20

我们再根据打印归纳总结一下
对于重写copyWithZone:mutableCopyWithZone:的自定义对象

  • [array copy][array mutableCopy] 只深拷贝了array对象,元素是浅拷贝
  • initWithArray:array1 copyItems:生成了新的array,且array内元素person是深拷贝,但person的属性仍然是浅拷贝(注:此处属性浅拷贝是因为:personNSString对象, copyItems 调用的是copyWithZone, [NSString copy]是浅拷贝)
  • 通过遍历array,对person分别copy, 会调用personcopyWithZone,此时NSArray是深拷贝,NSArray内元素person是深拷贝, person的属性参数是否深拷贝,取决于属性参数的类型(NSString浅拷贝, NSMutableString深拷贝)
  • 通过遍历array,对person分别mutable copy,会调用personmutableCopyWithZone,此时NSArray 是深拷贝,NSArray内元素是深拷贝,元素的属性参数也是深拷贝

此时我们知道了,要对自定义对象进行深拷贝,解决方案是重写copyWithZonemutableCopyWithZone,调用对应的方法即可

4. 用归档的方式深拷贝自定义对象

先改一下person类

//
//  Person.m
//  AlgorithmDemo
//
//  Created by Ternence on 2021/5/9.
//

#import "Person.h"

@interface Person ()<NSCopying, NSMutableCopying, NSCoding>

@end

@implementation Person

- (instancetype)initWithCoder:(NSCoder *)coder {
    self.nickname = [coder decodeObjectForKey:@"nickname"];
    return  self;
}

- (void)encodeWithCoder:(NSCoder *)coder {
    [coder encodeObject:self.nickname forKey:@"nickname"];
}

@end

再改写测试代码

    Person *person = [[Person alloc] init];
    person.nickname = @"码代码的小马";
    NSMutableArray *array1 = [NSMutableArray arrayWithObjects:person, nil];
    NSData *data = [NSKeyedArchiver archivedDataWithRootObject:array1];
    NSMutableArray *array2 = [NSKeyedUnarchiver unarchiveObjectWithData:data];
    
    NSLog(@"\n array1 = %p class = %@", array1, [array1 class]);
    NSLog(@"\n array2 = %p class = %@", array2, [array2 class]);
    
    NSLog(@"\n\n======== 数组内元素 ========");
    Person *orgArrayObj = (Person *)array1[0];
    Person *newArrayObj2 = (Person *)array2[0];
    NSLog(@"\n array1[0] = %p nickname = %p", orgArrayObj, orgArrayObj.nickname);
    NSLog(@"\n array2[0] = %p nickname = %p", newArrayObj2, newArrayObj2.nickname);

再耐心查看一下打印

2021-05-10 11:41:52.420699+0800 AlgorithmDemo[2252:455650] 
 array1 = 0x103b04e50 class = __NSArrayM
 array2 = 0x103b06f30 class = __NSArrayM

======== 数组内元素 ========
 array1[0] = 0x10061dcc0 nickname = 0x1000083a0
 array2[0] = 0x103b05860 nickname = 0x103b06120
 

很明显,array对象深拷贝了,array内的自定义对象person深拷贝了,自定义对象personnickname也深拷贝了

我们来对Array的拷贝做个年终总结:

  • copy:对NSArray对象是浅拷贝,可NSMutableArray对象是深拷贝
  • mutableCopy:始终是深拷贝
  • [Array copy]、[Array mutableCopy]因为只对Array对象本身做拷贝操作,对Array内的元素是浅拷贝
  • 要实现Array内元素的深拷贝,本质是元素全都进行深拷贝操作
  • initWithArray:array1 copyItems:生成了新的arraycopyItems:true只是调用了Array的copyWithZone方法,Array内元素是浅拷贝
  • 通过归解档的方式,可以实现对arrayarray 内元素personperson属性nickname深拷贝
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,651评论 6 501
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,468评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,931评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,218评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,234评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,198评论 1 299
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,084评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,926评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,341评论 1 311
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,563评论 2 333
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,731评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,430评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,036评论 3 326
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,676评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,829评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,743评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,629评论 2 354