copy修饰NSArray strong修饰NSMutableArray

假装很认真的样子

阴差阳错,前两天和一个小伙伴在一起聊天。聊到关于 copystrong 的问题。这个在ARC[Automatic Reference Counting)]下慢慢淡化的一个东东。交流之中让我受益匪浅,原来 copystrong 还可以这么玩。


以下内容在 demo 中均有体现

1.首先我们先看一下到底出现了什么问题

使用 copy 修饰这个可变数组
@property (copy, nonatomic) NSMutableArray *copAry;

// 直接崩溃测试
- (void)testCash {
    NSMutableArray *arr = [NSMutableArray arrayWithObjects:@1, @2, @3, nil];
    self.copAry = arr;
    [self.copAry removeObject:@1]; // 直接崩溃
    NSLog(@"self.copyAry = %@", self.copAry);
}
报错reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'

那么为什么使用 strong 来修饰就不会有这个问题呢。
我们来看一个例子:

  • 定义一个CJPerson类,.m 中先不实现 setterr 方法

    CJPerson.h

  • 在控制器看一下打印结果

    NSMutableArray *names = [@[@"zhangsan"] mutableCopy];
    CJPerson *person = [[CJPerson alloc] init];
    person.sAry = names;// strong
    person.cAry = names;// copy
    
    [names addObject:@"lisi"];
    NSLog(@"sAry = %@, cAry = %@", person.sAry, person.cAry);
    /* 输出结果:
     sAry = (
         zhangsan,
         lisi
     ), cAry = (
         zhangsan
     )
     */
  • 归根结底之所以出现这样问题,那是因为 ARC 情况下 strongcopy 对属性 setter 方法重写的区别。
    strong: setter 时调用了 [sAry retain] 方法,实现了指针拷贝,也就是浅拷贝。
    copy: setter 时调用了 [cAry copy] 方法,实现了内容拷贝,也就是深拷贝。
    也就是在 .m 文件中系统默认帮我们实现的是:
    CJPerson.m
  • 具体可以看内存地址图详解:(简单粗糙,欢迎大神指正)


    内存地址分析.png

2.那么这个与 copy 修饰 NSMutableArray 导致崩溃有什么关系呢?

原来不管是集合类对象(NSArray,NSDictionary,NSSet...),还是非集合类对象(NSString),接收到copy或者mutableCopy消息时,都需遵循以下准则:

  • copy 返回的都是不可变对象,所以如果对 copy 返回值去调用可变对象的接口就会 crash
  • mutableCopy 返回的都是可变对象
    所以在 - (void)testCash 方法中执行到 self.copAry = arr; ARC 环境下setter 方法执行了 copy 方法,导致原本 NSMutableArray类型数组变成 NSArray 类型,在调用removeObject:方法时,自然会出现这个错误reason: '-[__NSArrayI removeObject:]: unrecognized selector sent to instance 0x6000000487c0'

3.那么 NSArray 类型为什么需要使用 copy 来修饰

  • 我们继续使用 copystrong 来定义变量
@property (strong, nonatomic) NSArray *arr1;
@property (copy, nonatomic) NSArray *arr2;
// 为什么 NSArray 需要使用 copy
- (void)testUserCopyWithAry {
    NSMutableArray *arr = [NSMutableArray arrayWithObjects:@(1), @(2), @(3), nil];
    self.arr1 = arr;
    self.arr2 = arr;
    
    [arr addObject:@(4)];
    NSLog(@"arr1 = %@, arr2 = %@", self.arr1, self.arr2);
    
    /* 输出结果:
     arr1 = (
         1,
         2,
         3,
         4
     ), arr2 = (
         1,
         2,
         3
     )
     */
}

简直白的说就是:如果定义一个数组,使用 strong 来修饰的话,如果这个数组在外界被修改的话,那么这个用 strong 修改的数组变量的值也会跟着变化。为什么?还是因为 strong 进行了指针拷贝。在内存中,两个变量指向的是同一块内存地址。所以为了避免值在外接发生改变而影响自身值的变化,我们通常选择使用 copy 进行修饰。

4.接着再上两个测试例子,对比看输出结果

- (void)test01 {
    NSArray *array = @[@1,@2,@3,@4];
    
    NSArray *copyArr = [array copy];
    NSArray *mCopyArr = [array mutableCopy];
    
    NSMutableArray *mcArr = [array copy];
    NSMutableArray *mmCopyArr = [array mutableCopy];
    
    NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
    
    /*  输出结果
     array:     0x60000024ce10
     copyArr:   0x60000024ce10
     mCopyArr:  0x60000024cd80
     mcArr:     0x60000024ce10
     mmCopyArr: 0x60000024ce70
     */
    
}

- (void)test02 {
    NSArray *tarray = @[@1,@2,@3,@4];
    NSMutableArray *array = [[NSMutableArray alloc] init];
    [array addObjectsFromArray:tarray];
    
    NSArray *copyArr = [array copy];
    NSArray *mCopyArr = [array mutableCopy];
    
    NSMutableArray *mcArr = [array copy];
    NSMutableArray *mmCopyArr = [array mutableCopy];
    
    NSLog(@"array:%p--copyArr:%p--mCopyArr:%p--mcArr:%p---mmCopyArr:%p",array,copyArr,mCopyArr,mcArr,mmCopyArr);
    /* 输出结果
     array:     0x60000024cd20
     copyArr:   0x60000024cc90
     mCopyArr:  0x60000024ce40
     mcArr:     0x60000024cde0
     mmCopyArr: 0x60000024d020
     */
}

小结一下:

  • NSArray的copy ---->指针拷贝---->浅拷贝
  • NSArray的mutableCopy,NSMutableArray的copy, NSMutableArray的mutableCopy 均为深拷贝,即内容拷贝。
  • 关于NSString(非集合类对象),NSDictionary及其对应的可变类型都可以此类推。

5.浅拷贝、深拷贝

全篇在讲浅拷贝、深拷贝,追究他们追究到底是什么。
段子手的理解:浅复制好比你和你的影子,你完蛋,你的影子也完蛋。深复制好比你和你的克隆人,你完蛋,你的克隆人还活着。
浅拷贝

  • 浅拷贝就是对内存地址的复制,目标对象和原对象指向同一片内存地址。
    注意
    多个对象共用一块地址时,当内存销毁的时候,指向这片内存地址的几个指针需要重新定义才可以使用,否则出现野指针现象。
    iOS的浅拷贝 中,通常会使用 retain 关键字进行引用计数。因为他既可以让几个指针共同指向同一内存地址,也可以在 release 的时候 由于计数的存在,不会让内存销毁,从而出现野指针的现象。

深拷贝

  • 深拷贝也就是内容拷贝。目标对象虽然和原对象的值一样,但是所指向的内存地址不一样。可以说深拷贝把原对象地址也拷贝了,而内存地址是自主分配的。因内存地址不一样,两个对象也就互不影响、互不干涉了。
    iOS的深拷贝 中,通常会使用 copymutableCopy 方法
// 深拷贝
- (void)testDeepCopy {
    NSString *str = @"abcdefg";
    NSString *cStr = [str copy];
    NSMutableString *mStr = [str mutableCopy];
    [mStr appendString:@"!!"];
    NSLog(@"\n str = %@ = %p,\n cStr = %@ = %p,\n mStr = %@ = %p", str, str, cStr, cStr, mStr, mStr);
}
/* 输出结果:
 str = abcdefg = 0x109dd8090,
 cStr = abcdefg = 0x109dd8090,
 mStr = abcdefg!! = 0x604000057e50
*/

再次验证了第 4 个模块


番外篇
浅拷贝的 retain 和 深拷贝中提到的 copy 有什么区别呢
可以观看这篇文章:copy 和 retain 到底有啥区别

6.拷贝构造

当然在 iOS 中并不是所有的对象都支持copymutableCopy,遵守 NSCopying 协议的类可以发送 copy 消息,遵守NSMutableCopying 协议的类才可以发送 mutableCopy 消息。
假如发送了一个没有遵守上诉两协议而发送 copy 或者 mutableCopy, 那么就会发生异常。但是默认的 iOS 类并没有遵守这两个协议。如果想自定义一下 copy 那么就必须遵守 NSCopying, 并且实现 copyWithZone: 方法,如果想自定义一下mutableCopy 那么就必须遵守 NSMutableCopying, 并且实现 mutableCopyWithZone: 方法。

  • 自定义 model,遵守 NSCopyingNSMutableCopying 协议
@interface CJObj : NSObject<NSCopying, NSMutableCopying>
@property (copy, nonatomic) NSString *name;
@property (copy, nonatomic) NSString *icon;
@end
  • 实现:


    CJObj.m
  • ViewController 中打印结果

// 拷贝构造
- (void)copyConstruct {
    CJObj *obj = [[CJObj alloc] init];
    obj.name = @"zhangsan";
    obj.icon = @"icon";
    
    NSMutableArray *arr = [NSMutableArray array];
    NSMutableArray *copyAry = [NSMutableArray array];
    [arr addObject:obj];
    
    [copyAry addObject:[arr[0] copy]];
    //[copyAry addObject:[arr[0] mutableCopy]];

    CJObj *obj2 = arr[0];
    obj2.name = @"lisi";
    obj2.icon = @"obj2_icon";
    
    NSLog(@"arr.name = %@, arr.icon = %@", ((CJObj *)arr[0]).name, ((CJObj *)arr[0]).icon);
    NSLog(@"copyArr.name = %@, copyArr.icon = %@", ((CJObj *)[copyAry objectAtIndex:0]).name, ((CJObj *)[copyAry objectAtIndex:0]).icon);
    /* 输出结果:
         arr.name = lisi, arr.icon = obj2_icon
         copyArr.name = zhangsan, copyArr.icon = icon
     */
}
  • 内存地址图详解:(简单粗糙,欢迎大神指正)


    拷贝构造

以上内容在 demo 中均有体现

感谢:

相关阅读:

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

推荐阅读更多精彩内容

  • 本文为转载: 作者:zyydeveloper 链接:http://www.jianshu.com/p/5f776a...
    Buddha_like阅读 824评论 0 2
  • 307、setValue:forKey和setObject:forKey的区别是什么? 答:1, setObjec...
    AlanGe阅读 1,481评论 0 1
  • 前言 不敢说覆盖OC中所有copy的知识点,但最起码是目前最全的最新的一篇关于 copy的技术文档了。后续发现有新...
    zyydeveloper阅读 3,252评论 4 35
  • 一 记得是去年冬天,高升桥路大修的那段时间,我家门前的那条小路也居然没了通行能力。且不说每日上班路上的跌宕起伏,就...
    木登阅读 182评论 0 0
  • 二十天前,我查到了第二次考六级的分数:459分(425过线),比第一次325整整高了130多分。 但是呢,这个分数...
    靳小凡阅读 12,989评论 130 614