iOS - copy和mutableCopy你真的会用么?

前言

1.深浅拷贝

2.copy 和 mutableCopy 介绍和用法。

3.为什么修饰block用copy?

4.声明NSArray 和 NSMutableArray变量时,哪个更适合用copy修饰?

5.总结

一、深浅拷贝

1.什么是浅拷贝、什么是深拷贝

深拷贝 : 拷贝出来的对象与源对象地址不一致! 这意味着我修改拷贝对象的值对源对象的值没有任何影响.
浅拷贝 : 拷贝的是指针地址,拷贝出来的对象与源对象地址一致! 这意味着我修改拷贝对象的值会直接影响到源对象.

2.网上有一些错误的观点:
copy就是浅拷贝, mutableCopy就是深拷贝
事实上copy也可以是深拷贝。可变对象进行copy,会产生新的对象地址,而不是新的指针地址。,mutableCopy也未必是深复制。我会在下面的例子中说明。

3.针对NSArray、NSDictionary、NSSet容器类型的对象,深拷贝可分为:"不完全深拷贝""完全深拷贝"。
不完全深拷贝:拷贝出来的容器是新的对象,但是容器里面的对象还是原来对象。
完全深拷贝:拷贝出来的容器是新的对象,容器里面的对象也是新对象。

二、copy 和 mutableCopy介绍和用法

1.先看官档是怎么说明copy的

copy (是NSCopying协议的方法)

"Returns the object returned by copyWithZone:"
翻译:返回的对象是通过调用 copyWithZone: 这个方法返回的。

看了对copy的解释,就可以知道调用copy实际上就是调用copyWithZone:这个方法。在我没有贴出来的官档里也说了copy就是copyWithZone:简写,为了更方便调用。为了知道copy的用法,
那我们就要知道copyWithZone:的用法。这个方法的官档如下:

copyWithZone:

"Returns a new instance that’s a copy of the receiver."
翻译:返回一个新的实例,这个实例是接收器的副本。

再看看官方文档对这个方法的讨论
"The returned object is implicitly retained by the sender, who is responsible for releasing it. The copy returned is immutable if the consideration “immutable vs. mutable” applies to the receiving object; otherwise the exact nature of the copy is determined by the class."

大意:发送者隐式的保留这个返回的对象,同时也负责这个返回对象的释放工作。无论接受器的对象是"可变的"或则"不可变的",使用这个方法,返回的对象都是不可变的。否则,拷贝对象的确切的特性由被拷贝的对象的类决定。

在通俗的解释下,一般情况下,使用copy拷贝的对象都是不可变的,无论是对可变对象拷贝还是对不可变对象拷贝。最后一句话说copy的具体特性由被拷贝的对象决定,就是说有可能copy的对象是可变。下面会有栗子。

2.再看看mutableCopy的官方文档说明

mutableCopy(是NSMutableCopying协议的方法)

"A protocol that mutable objects adopt to provide functional copies of themselves."
可变对象采用的协议,用于提供自身的功能副本。

mutableCopy和copy很相似。mutableCopy是mutableCopyWithZone:简写形式。所以我们再看看mutableCopyWithZone:官方文档是怎样描述的。

mutableCopyWithZone:

Returns a new instance that’s a mutable copy of the receiver.
返回一个新的实例,这是一个可变的接收器的副本。
再看看官方文档对这个方法的讨论
"The returned object is implicitly retained by the sender, which is responsible for releasing it. The copy returned is mutable whether the original is mutable or not."
大意:发送者隐式的保留这个返回的对象,同时也负责这个返回对象的释放工作。无论接受器的对象是"可变的"或则"不可变的",使用这个方法,返回的对象都是可变的。

只有定义“不可变与可变”区别的类才应采用此协议(NSMutableCopying协议)也即只有定义不可变与可变区别的类,才可以使用mutableCopy。

举例说明用法:

  1. NSString 、NSMutableString 的copy和mutableCopy
- (void)copyFunction {
    //不可变字符串
    NSString *imutableString = @"这是一个不可变字符串";
    id imutableString_copy = [imutableString copy];
    id imutableString_mutableCopy = [imutableString mutableCopy];
    
    //可变字符串
    NSMutableString *mutableStr = [[NSMutableString alloc] initWithString:@"这是一个可变字符串"];
    id mutableStr_copy = [mutableStr copy];
    id mutableStr_mutableCopy = [mutableStr mutableCopy];
}

控制台系统输出如下:


不可变字符串的copy和mutableCopy

可变字符串的copy和mutabelCopy

从控制台的输出信息,可以看出对于不可变和可变字符串的copy和mutableCopy的规律:

NSString 类型的字符串:string

[string copy]------------------------------->NSString类型的 (浅拷贝
[string mutableCopy]-------------------->NSMutableString类型 (深拷贝

NSMutableString类型的字符串:mString

[mString copy]------------------------------->NSMutableString类型的 ( 深拷贝
[mString mutableCopy]-------------------->NSMutableString类型 (深拷贝

2.容器类型以数组举例:NSArray 、NSMutableArray 的copy和mutableCopy
先来看看数组中的元素是OC系统定义的类

- (void)copyArray
{
    NSDate *a1 = [NSDate date];
    NSDate *a2 = [NSDate date];
    //不可变数组
    NSArray *arr = [NSArray arrayWithObjects:a1,a2, nil];
    id arr_copy = [arr copy];
    id arr_mutableCopy = [arr mutableCopy];
    //可变数组
    NSMutableArray *mArr = [NSMutableArray arrayWithObjects:a1,a2, nil];
    id mArr_copy = [mArr copy];
    id mArr_mutableCopy = [mArr mutableCopy];
}
不可变数组的copy和mutableCopy

可变数组的copy和mutableCopy

从控制台的输出信息,可以看出对于不可变和可变数组的copy和mutableCopy的规律:

NSArray类型的数组:arr

[arr copy]------------------------------->NSArray类型 (浅拷贝
[arr mutableCopy]-------------------->NSMutableArray类型 (深拷贝

NSMutableArray类型的数组:mArr

[mArr copy]------------------------------->NSArray类型 ( 深拷贝
[mArr mutableCopy]-------------------->NSMutableArray类型 (深拷贝
通过字符串和数组的事例,可以看出copy和mutableCopy的用法

非容器类总结
对象类型 不可变对象 可变对象
copy 浅拷贝 深拷贝
mutableCopy 深拷贝 深拷贝
容器类型总结
对象类型 不可变对象 可变对象
copy 浅拷贝 深拷贝
mutableCopy 深拷贝 深拷贝

对于容器类型的深拷贝可细分为:不完全深拷贝和完全深拷贝
这里大家一定要注意,不同对象调用copy得到结果不一样。正如官方文档所述那样:"否则,拷贝对象的确切的特性由被拷贝的对象的类决定。"

总之,大家要记住一句话,copy一般情况下是浅拷贝,但是在一些情况下,copy又是深拷贝。

下面又是一个例子证明:
这次数组里面的元素是自定义类型的User对象

- (void)copyArray
{
    User *u1 = [[User alloc] init];
    u1.name = @"小明";
    u1.professional = @"教授";
    u1.age = @(32);
    u1.hobbies = @"看书";
    
    User *u2 = [[User alloc] init];
    u2.name = @"张三";
    u2.professional = @"歌手";
    u2.age = @(23);
    u2.hobbies = @"唱歌";
    //不可变数组
    NSArray *arr = [NSArray arrayWithObjects:u1,u1, nil];
    id arr_copy = [arr copy];
    id arr_mutableCopy = [arr mutableCopy];
    //可变数组
    NSMutableArray *mArr = [NSMutableArray arrayWithObjects:u1,u2, nil];
    id mArr_copy = [mArr copy];
    id mArr_mutableCopy = [mArr mutableCopy];
}

不可变数组元素是自定义类型的copy和mutableCopy

可变数组元素是自定义类型的copy和mutableCopy

从上图可以看到无论是不可变数组还是可变数组的copy,copy产生的对象都是新的对象,而且是不可变类型的,并且是完全深拷贝。数组里面的元素对象都是新的。


三、为什么block使用copy?

block是一个对象, 所以block理论上是可以retain/release的. 但是block在创建的时候它的内存是默认是分配在栈(stack)上, 而不是堆(heap)上的. 所以它的作用域仅限创建时候的当前上下文(函数, 方法...), 当你在该作用域外调用该block时, 程序就会崩溃.
其实block使用copy是MRC时代留下来的传统。 在MRC下, 在方法中的block创建在栈区, 使用copy就能把他放到堆区, 这样在作用域外调用该block程序就不会崩溃. 但在ARC下, 使用copy与strong其实都一样, 因为block的retain就是用copy来实现的。之所以大家都习惯用copy就是MRC时代留下的习惯。


四.声明NSArray 和 NSMutableArray变量时,哪个更适合用copy修饰?

NSArray和NSMutableArray用strong和copy修饰区别:

- (void)copyArray
{
    User *u1 = [[User alloc] init];
    u1.name = @"小明";
    u1.professional = @"教授";
    u1.age = @(32);
    u1.hobbies = @"看书";
    
    User *u2 = [[User alloc] init];
    u2.name = @"张三";
    u2.professional = @"歌手";
    u2.age = @(23);
    u2.hobbies = @"唱歌";
    //可变数组
    NSMutableArray *mArr = [NSMutableArray arrayWithObjects:u1,u2, nil];
    //一个可变数组赋值给分别用strong和copy修饰的不可变数组。
    //@property (nonatomic, strong) NSArray *arr_strong;
    //@property (nonatomic, copy) NSArray *arr_copy;
    self.arr_strong = mArr;
    self.arr_copy = mArr;
    [mArr addObject:@"嗯哼~"];
    
    //@property (nonatomic, strong) NSMutableArray *mArray_strong;
    //@property (nonatomic, copy) NSMutableArray *mArr_copy;
    NSMutableArray *mArr1 = [NSMutableArray arrayWithObjects:u1,u2, nil];
    self.mArray_strong = mArr1;
    self.mArr_copy = mArr1;
    [mArr1 addObject:@"天亮啦~"];
}

下面是对应的结果

(lldb) po self.arr_strong
<__NSArrayM 0x6040004449b0>(
<User: 0x604000444710>,
<User: 0x604000444ad0>,
嗯哼~
)

(lldb) po self.arr_copy
<__NSArrayI 0x60400022cc60>(
<User: 0x604000444710>,
<User: 0x604000444ad0>
)

(lldb) po self.mArray_strong
<__NSArrayM 0x6040004447d0>(
<User: 0x604000444710>,
<User: 0x604000444ad0>,
天亮啦~
)

(lldb) po self.mArr_copy
<__NSArrayI 0x60400022cd40>(
<User: 0x604000444710>,
<User: 0x604000444ad0>
)

1.没有对比,就不知道真相。通过对比可以看出,如果使用strong来修饰NSArray类型的数组,当array的数组被赋值了可变数组对象时,当可变数组改变时,NSArray数组里的对象也会跟着改变,这是我们不想要的结果。使用copy修饰,在被赋值可变数组时,会生成一个新的不可变数组对象,这样可变数组之后怎样变化,都不会影响NSArray类型的数组对象。

2.再看看使用strong来修饰NSMutableArray类型的数组,当mArray的数组被赋值了可变数组对象时,当可变数组改变时,NSMutableArray数组里的对象也会跟着改变,这是符合我们预期的。当使用copy修饰后,被赋值后,会生成一个新的不可变数组对象。这样我们还以为它是可变类型的数组,然后使用增删改查,就会crash,也谈不上可以改变数组对象了。

3.综上,用property声明NSArray数组时,最好使用copy。用property声明NSMutableArray数组时,最好使用strong。如果使用copy,又self.mArr来赋值,后面增删改查,程序肯定会crash的。


五、总结

1.copy的用法,不能一概而论。不同类型的类使用copy,结果可能都不一样。需要自行实验得出结论。
2. copy可能是浅拷贝,也可能是深拷贝。mutableCopy都是深拷贝。
3.用property声明NSArray数组或者NSMutableArray数组时,注意修饰的关键词,使用strong还是copy。
4.对于自定义的对象,要使用copy。需要重写copyWithZone:这个方法。

如下栗子:

- (nonnull id)copyWithZone:(nullable NSZone *)zone {
    
    User *user = [[User allocWithZone:zone] init];
    user.name = self.name;
    user.professional = self.professional;
    user.age = self.age;
    user.hobbies = self.hobbies;
    return user;
}

如有不正确的地方,还请大家指出来。欢迎大家交流学习

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

推荐阅读更多精彩内容

  • 本文为转载: 作者:zyydeveloper 链接:http://www.jianshu.com/p/5f776a...
    Buddha_like阅读 823评论 0 2
  • Swift1> Swift和OC的区别1.1> Swift没有地址/指针的概念1.2> 泛型1.3> 类型严谨 对...
    cosWriter阅读 11,034评论 1 32
  • 浅拷贝与深拷贝 浅拷贝:指针拷贝,不产生新的对象,源对象的引用计数器+1 深拷贝:对象拷贝,会产生新的对象,源对象...
    SkyMing一C阅读 612评论 0 6
  • 【叶圣陶】现在有些青年以为写东西只要提笔写下去就成,不很顾到所写的明白不明白,有条理没有条理。我要爽直的说,这是一...
    梅花绽放阅读 101评论 0 0
  • 文 / 竹叶潇潇水迢迢 NO.30 图片来自网络 昨天晚上,跟一个朋友一块吃饭,聊了国庆回家的一些事情。 朋友说,...
    竹叶潇潇水迢迢阅读 431评论 0 1