关于ios的copy,你应该知道这些

什么是copy?什么是iOS的copy?

copy主要用于copy一份新的数据并与原数据相互独立存在。

对于基本数据类型(非对象)而言,一般会叫赋值操作,即将非对象的值赋予另外一个非对象。

对于对象而言,iOS中是用指针来操作的。因此,对于其赋值操作相当于copy了指针,使得源指针和新指针都指向同一个对象。对于copy操作,分为浅拷贝和深拷贝两种类型,下面会详述。

在 iOS 中并不是所有的对象都支持 copy/mutableCopy,遵守 NSCopying 协议的类可以发送 copy 消息,遵守 NSMutableCopying 协议的类可以发送 mutableCopy 消息。假如对一个没有遵守上述两协议的对象发送了 copy 或者 mutableCopy,那么就会发生异常。因此对于自定义对象,需要做相应的处理,下面会详述。

iOS中属性的copy修饰符

举例如下

@property (nonatomic, copy) NSString *s;

其setter方法默认为:

 - (void)setS:(NSString *)s {
    _s = [s copy];
}

要想设置该对象的特性为copy,该对象必须遵守NSCopying协议,Foundation类默认实现了NSCopying协议,所以只需要为自定义的类实现该协议即可。此外即使定义实例变量时使用了可变类型,但是要使用了copy修饰符,实例变量实际得到的值也是不可变的。

不可变拷贝(copy)还是可变拷贝(mutableCopy)?

对象分不可变对象和可变对象,复制方法有 copy 和 mutableCopy。
copy 返回的是不可变对象(immutableObject),mutableCopy 返回的是可变对象(mutableObject)

浅拷贝(Shallow Copy)还是深拷贝(Deep Copy)?

无论浅拷贝还是深拷贝,被拷贝的对象都会被复制一份,有新的对象产生,而浅拷贝只是复制指针,源对象的引用计数器 +1,其实相当于做了一次 retain 操作。而深拷贝会复制指针指向的内容,源对象引用计数器不变,赋值对象的引用计数器设置为 1。举例来描述一下:

对普通对象 ObjectA 进行 copy,无论浅拷贝还是深拷贝,都会复制出一个新的对象 ObjectB,只是浅拷贝时 ObjectA 与 ObjectB 中的指针还指向同一个对象,而深拷贝时 ObjectA 和 ObjectB 中的 指针分别指向各自的对象(对象内容被复制了一份)。

一句话总结:浅拷贝是指针拷贝,深拷贝是内容拷贝。

浅拷贝和深拷贝的区别来源于copyWithZone:方法的实现。浅拷贝拷贝出来的对象与源对象地址一致,修改拷贝对象的值会直接影响源对象的值。深拷贝出来的对象与源对象地址不一致,修改拷贝对象的值对源对象的值没有任何影响。深拷贝是直接拷贝整个对象内容到另一块内存中。


浅拷贝和深拷贝.png

原生对象的拷贝

先抛结论:
只有不可变对象创建不可变副本(copy)才是浅拷贝,其它都是深拷贝。
大家带着结论阅读下面的详细阐述:

系统的非容器类(非集合类)对象的copy与mutableCopy

系统的非容器类对象指的是 NSString,NSNumber 这些不能包含其他对象的对象。在非集合类对象中,对不可变对象进行copy操作,是浅拷贝,mutableCopy操作是深拷贝。对可变对象进行copy和mutableCopy都是深拷贝,但是 copy 返回的对象是不可变的。
(Foundation对于不可变复制对象而言,copy方法做了优化,不会开辟新的内存,是浅拷贝,相当于retain)

用代码简单表示如下:

NSString *str = @"hello world!";
NSString *strCopy = [str copy] // 浅拷贝,strCopy与str的地址一样
NSMutableString *strMCopy = [str mutableCopy] // 深拷贝,strMCopy与str的地址不一样

NSMutableString *mutableStr = [NSMutableString stringWithString: @"hello world!"];
NSString *strCopy = [mutableStr copy] // 深拷贝,但strCopy不可变
NSMutableString *strMCopy = [mutableStr mutableCopy] // 深拷贝
系统非容器类对象的拷贝.png

系统的容器类(集合类)对象

系统的容器类对象指 NSArray,NSDictionary 等可以容纳其他对象的对象。对于容器类本身,上面讨论的结论也是适用的,需要探讨的是复制后容器内对象的变化。集合对象的深拷贝仅限于对象本身,对集合内的对象元素仍然是浅拷贝,即单层内容复制。默认只拷贝容器对象本身,不复制其中的数据。这样做的目的是,容器内的对象未必都能拷贝,而且调用者也未必想在拷贝容器时一并拷贝其中的某个对象)。

举例描述为:

NSArray *arr = @[@[@"a", @"b"], @[@"c", @"d"];
NSArray *copyArr = [arr copy]; // 浅拷贝
NSMutableArray *mCopyArr = [arr mutableCopy]; //深拷贝,但单层内容复制

NSMutableArray *array = [NSMutableArray arrayWithObjects:[NSMutableString stringWithString:@"a"],@"b",@"c",nil];
NSArray *copyArr = [mutableArr copy]; // 深拷贝,单层内容复制
NSMutableArray *mCopyArr = [mutableArr mutableCopy]; // 深拷贝,单层内容复制
系统的容器类对象的拷贝.png

一般来说,完全深复制的实现复杂度会大一些,尤其是当该对象包含大量的指针类型的实例变量时,如果某些实例变量里再次包含指针类型的实例变量,那么实现完全深复制会更加复杂。那如果确实需要进行深拷贝,那有什么方法吗?当然是有的:

  • 集合的单层深复制 (one-level-deep copy)
    可以用 initWithArray:copyItems: 将第二个参数设置为YES即可实现单层深复制,如
    NSMutableArray *copyArray = [[NSMutableArray alloc] initWithArray:array copyItems:YES];

如果你用这种方法深复制,集合里的每个对象都会收到 copyWithZone: 消息。同时集合里的对象需遵循 NSCopying 协议,否则会崩溃。

  • A true deep copy
    使用归档来实现:
// 使用归档/反归档拷贝
NSMutableArray *trueDeepCopyArray = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject: array]];

结合我们常用的stringWithString:和arrayWithArray:方法再来与拷贝对照:

NSString *t1 = @"hello world";
NSString *t2 = [NSString stringWithString:t1];
NSLog(@"[test]%p,%p",t1,t2);//[test]0x10a403e40,0x10a403e40,浅拷贝
NSMutableString *t3 = [NSMutableString stringWithString:t2];
NSLog(@"[test]%p,%p",t2,t3);//[test]0x10332fe40,0x6000029f6280,深拷贝

NSArray *a1 = @[t1,t2,t3];
NSArray *a2 = [NSArray arrayWithArray:a1];//[test]0x600001b9ed60,0x600001b9edf0,深拷贝
NSLog(@"[test]%p,%p",a1,a2);
NSArray *a3 = [a1 copy];//[test]0x600001c2e280,0x600001c2e280,浅拷贝
NSLog(@"[test]%p,%p",a1,a3);
NSMutableArray *a4 = [NSMutableArray arrayWithArray:a1];//[test]0x600002122fd0,0x6000021564c0,对象本身深拷贝
NSLog(@"[test]%p,%p",a1,a4);
NSLog(@"[test]%p,%p",a1.firstObject,a4.firstObject);//[test]0x10d38fe40,0x10d38fe40,里层对象浅拷贝
NSMutableString *t4 = [NSMutableString stringWithString:t1];
[a4 replaceObjectAtIndex:0 withObject:t4];
NSMutableArray *a5 = [[NSMutableArray alloc] initWithArray:a4 copyItems:YES];//[test]0x600003b34a80,0x600003b473c0,深拷贝
NSLog(@"[test]%p,%p",a4,a5);
NSLog(@"[test]%p,%p",a4.firstObject,a5.firstObject);//[test]0x600003b472d0,0x6000035695e0,单层深拷贝

自定义对象的拷贝

iOS中自定义对象的类并没有默认遵守copy的协议。如果想自定义对象支持copy,就必须遵守 NSCopying,并且实现 copyWithZone: 方法,同理mutableCopy 需遵守 NSMutableCopying,并且实现 mutableCopyWithZone: 方法。

在实现 copyWithZone: 方法时需要注意:copyWithZone: 相当于新创建一个对象,并将当前对象的值复制到新创建的对象中。

  • 直接从 NSObject 继承的类,应使用 [[[self class] allocWithZone:zone] init],使得在创建新对象时能够使用正确的类。
  • 父类中已经实现了 copyWithZone: 时,应先调用父类的方法,让父类创建对应的对象(self class 能保证创建对象是正确的),并拷贝父类中定义的成员变量。


    copyWithZone.png

与copy相关的面试可能遇到的问题

  • 讲一讲iOS中的copy和retain
    copy创建的新对象也是强引用,在iOS中分为浅拷贝和深拷贝。浅拷贝是指针复制,与源对象指向同一内存地址,源对象的引用计数器会+1。深拷贝是内容拷贝,两个对象内容相同,新的对象 retain 为 1 ,与旧有对象的引用计数无关,旧有对象没有变化。copy 会减少对象对上下文的依赖。
    retain 属性表示两个对象地址相同,内容也相同,这个对象的引用计数器+1。

  • 为什么NSString类型的成员变量的修饰属性用copy而不是strong呢?
    因为有时候赋给该成员变量的值是NSMutableString类型的,这时候如果修饰符是strong,那成员变量的值就会随着被赋值对象的值的变化而变化。若是用copy修饰,则对NSMutableString类型的值进行了一次深拷贝,成员变量的值就不会随着被赋值对象的值的改变而改变。

  • 对NSMutableString用copy修饰会有什么问题?
    答:对NSMutableString用copy修饰得到的是不可变类型NSString对象,如果再对这个对象进行改变会crash。
    NSString与NSMutableString的区别主要是:NSMutableString对象所指向内存地址中的内容可以被修改,而NSString对象所指向内存地址中内容不能被修改,但NSString对象不是常量,可以通过为NSString对象重新分配一块内存来改变其指向的内容。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,482评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,377评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,762评论 0 342
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,273评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,289评论 5 373
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,046评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,351评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,988评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,476评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,948评论 2 324
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,064评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,712评论 4 323
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,261评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,264评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,486评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,511评论 2 354
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,802评论 2 345