strong、copy和mutableCopy详解

写在前面

关于copymutableCopystrong,一直想好好整理整理,但是因为各种原因搁置了。今天上班时发现老代码中因为这个问题出现了一个特别恶心的大坑,让我下定决心写下这篇博文。如果你认为自己没掌握copy相关知识,建议看看此文,这里会有你需要的;如果你认为自己掌握了copy相关知识,也建议看看此文,也许这里有你感兴趣的东西。

首先和大家回顾一下有关copy的两个概念——浅拷贝(Shallow Copy)深拷贝(Deep Copy)

浅拷贝:只拷贝指针,不拷贝内容(不用为内容分配新的内存空间);
深拷贝:同时拷贝指针和内容,即分配一块新的内存,将原内容复制一份到该内存中,并生成指向该内存的新指针。(这里谢谢@灿烂天空的提醒,原来的解释可能不太准确,特此修正)
通俗点解释就是,已知一条路,通往罗马,浅拷贝就是新建一条通往这个罗马的路。而深拷贝则是选一个地方,按照原来的罗马新建一个(罗马内部包含的所有东西全部照旧的新建,比如家居、文化等等),并修一条通往新罗马的路,这样原来罗马怎么样就跟新罗马没关系了。

In the case of collection objects, a shallow copy means that a new collection object is created, but the contents of the original collection are not duplicated—only the object references are copied to the new container.

A deep copy duplicates the compound object as well as the contents of all of its contained objects.

四种copy情况

不可变非集合类对象的copy

直接上代码:

NSString *immutableStr = @"KFA_test1";
NSString *copyImmutableStr = [immutableStr copy];
NSString *mutableCopyImmutableStr = [immutableStr mutableCopy];
NSLog(@"\\nimmutableStr:%p-%p\\ncopyImmutableStr:%p-%p\\nmutableImmutableStr:%p-%p",immutableStr,&immutableStr,copyImmutableStr,&copyImmutableStr,mutableCopyImmutableStr,&mutableCopyImmutableStr);

运行结果

immutableStr:0x103426098-0x7fff5c7dba08
copyImmutableStr:0x103426098-0x7fff5c7dba00
mutableImmutableStr:0x7ffd99d34ce0-0x7fff5c7db9f8

可以发现,对不可变非集合类对象进行copy时,只拷贝了指针,内容不拷贝。对不可变非集合类对象进行mutableCopy时,同时拷贝了指针和内容。

可变非集合类对象的copy

照例上代码:

NSMutableString *mutableStr = [@"KFA_test2" mutableCopy];
NSString *copyImmutableStr = [mutableStr copy];
NSString *mutableCopyImmutableStr = [mutableStr mutableCopy];
NSLog(@"\\nmutableStr:%p-%p\\ncopyImmutableStr:%p-%p\\nmutableImmutableStr:%p-%p",mutableStr,&mutableStr,copyImmutableStr,&copyImmutableStr,mutableCopyImmutableStr,&mutableCopyImmutableStr);

运行结果:

mutableStr:0x7feb8bc1c940-0x7fff5610fa08
copyImmutableStr:0xa34cf2e0400c1289-0x7fff5610fa00
mutableImmutableStr:0x7feb8bc1f320-0x7fff5610f9f8

从结果可以发现,对可变非集合类对象进行copymutableCopy,都同时拷贝指针和内容。

不可变集合类对象的copy

这里以数组为例:

NSArray *immutableArr = @[@"KFA_test3"];
NSString *immu = immutableArr.firstObject;
NSArray *copyImmutableArr = [immutableArr copy];
NSString *copyImmu = copyImmutableArr.firstObject;
NSArray *mutableCopyImmutableArr = [immutableArr mutableCopy];
NSString *mutableCopyImu = mutableCopyImmutableArr.firstObject;
NSLog(@"\\nimmutableArr:%p-%p\\ncopyImmutableArr:%p-%p\\nmutableCopyImmutableArr:%p-%p\\nimmu:%p-%p\\ncopyImmu:%p-%p\\nmutableCopyImu:%p-%p",immutableArr,&immutableArr,copyImmutableArr,&copyImmutableArr,mutableCopyImmutableArr,&mutableCopyImmutableArr,immu,&immu,copyImmu,&copyImmu,mutableCopyImu,&mutableCopyImu);

运行结果:

immutableArr:0x7fa44a7087d0-0x7fff576619d0
copyImmutableArr:0x7fa44a7087d0-0x7fff576619c0
mutableCopyImmutableArr:0x7fa44a703370-0x7fff576619b0
immu:0x1085a0118-0x7fff576619c8
copyImmu:0x1085a0118-0x7fff576619b8
mutableCopyImu:0x1085a0118-0x7fff576619a8

不难发现,对不可变集合类对象进行copy时,只拷贝指针,不拷贝内容。对不可变集合类对象进行mutableCopy时,同时拷贝指针和内容,但是这里的内容只针对集合对象本身,对于集合内的元素只拷贝指针,不拷贝内容。

可变集合类对象的copy

还是以数组为例:

NSArray *mutableArr = [@[@"KFA_test3"] mutableCopy];
NSString *mu = mutableArr.firstObject;
NSArray *copyImmutableArr = [mutableArr copy];
NSString *copyImmu = copyImmutableArr.firstObject;
NSArray *mutableCopyImmutableArr = [mutableArr mutableCopy];
NSString *mutableCopyImu = mutableCopyImmutableArr.firstObject;
NSLog(@"\\nmutableArr:%p-%p\\ncopyImmutableArr:%p-%p\\nmutableCopyImmutableArr:%p-%p\\nmu:%p-%p\\ncopyImmu:%p-%p\\nmutableCopyImu:%p-%p",mutableArr,&mutableArr,copyImmutableArr,&copyImmutableArr,mutableCopyImmutableArr,&mutableCopyImmutableArr,mu,&mu,copyImmu,&copyImmu,mutableCopyImu,&mutableCopyImu);

运行结果:

mutableArr:0x7feacbd0d250-0x7fff521409d0
copyImmutableArr:0x7feacbd06ef0-0x7fff521409c0
mutableCopyImmutableArr:0x7feacbd0c2a0-0x7fff521409b0
mu:0x10dac1118-0x7fff521409c8
copyImmu:0x10dac1118-0x7fff521409b8
mutableCopyImu:0x10dac1118-0x7fff521409a8

我们发现,对可变集合类对象进行copymutableCopy时,都同时拷贝指针和内容。这里的内容跟上文一样只针对集合对象本身,对于集合内部的元素只拷贝指针,不拷贝内容。

strong和copy

strong和copy下的字符串

先添加两个字符串ivar,分别用strong和copy修饰:

@property (nonatomic, strong) NSString *testStrongStr;
@property (nonatomic, copy) NSString *testCopyStr;

然后写个测试方法:

- (void)testCopyAndStrongForString {
    
    NSString *immutableStr = @"KFA_test4";
    self.testStrongStr = immutableStr;
    self.testCopyStr = immutableStr;
    NSLog(@"\\nimmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",immutableStr,self.testStrongStr,self.testCopyStr);
    immutableStr = @"KFA_test4_change";
    NSLog(@"\\nimmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",immutableStr,self.testStrongStr,self.testCopyStr);
    
    NSMutableString *mutableStr = [@"KFA_test5" mutableCopy];
    self.testStrongStr = mutableStr;
    self.testCopyStr = mutableStr;
    NSLog(@"\\nmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",mutableStr,self.testStrongStr,self.testCopyStr);
    [mutableStr appendString:@"_change"];
    NSLog(@"\\nmutableStr:%@\\nself.testStrongStr:%@\\nself.testCopyStr:%@",mutableStr,self.testStrongStr,self.testCopyStr);
}

运行结果:

immutableStr:KFA_test4
self.testStrongStr:KFA_test4
self.testCopyStr:KFA_test4 
immutableStr:KFA_test4_change
self.testStrongStr:KFA_test4
self.testCopyStr:KFA_test4 
mutableStr:KFA_test5
self.testStrongStr:KFA_test5
self.testCopyStr:KFA_test5 
mutableStr:KFA_test5_change
self.testStrongStr:KFA_test5_change
self.testCopyStr:KFA_test5

从结果可以看出,当赋值对象是不可变的字符串时,strongcopy的效果一样,因为当赋值对象为不可变的字符串时,copy修饰的ivar进行拷贝相当于上文中的对不可变对象进行copy,只拷贝指针,不考虑内容。但是当赋值对象为可变的字符串时,当赋值对象发生变化时,strong修饰的ivar会跟着变,copy修饰的ivar进行了拷贝(参考上文中对可变对象进行copy),所以不会变。这里可能有同学会有疑惑,为什么明明KFA_test4变成了KFA_test4_change,内容变了,而self.testStrongStrself.testCopyStr的内容没变,这是因为KFA_test4变成KFA_test4_change只是因为immutableStr指针进行了重指向指向了KFA_test4_change,而内容KFA_test4属于常量,存在栈区,本身并没有变。所以在修饰字符串时为了数据的安全,建议用copy,而不用strong

strong和copy下的数组

同样,先添加两个数组ivar,分别用strongcopy修饰:

@property (nonatomic, strong) NSArray *testStrongArr;
@property (nonatomic, copy) NSArray *testCopyArr;

另外再创建一个model类,有一个number属性:

@interface KFACopyModel : NSObject

@property (nonatomic, copy) NSString *number;

@end

再写两个函数(等会测试方法里会用到),用来同时打印三个数组(里面的元素):

void KFALog(NSArray *array1, NSArray *array2, NSArray *array3) {

    NSString *string1 = KFANSStringFromArray(array1);
    NSString *string2 = KFANSStringFromArray(array2);
    NSString *string3 = KFANSStringFromArray(array3);
    NSLog(@"\\n\\%@\\n%@\\n%@",string1,string2,string3);
}

NSString *KFANSStringFromArray(NSArray *array) {
    NSMutableString *string = nil;
    for (NSString *objc in array) {
        NSString *objcStr = nil;
        if ([objc isKindOfClass:[NSString class]]) {
            objcStr = (NSString *)objc;
        }else if ([objc isKindOfClass:[KFACopyModel class]]) {
            KFACopyModel *model = (KFACopyModel *)objc;
            objcStr = model.number;
        }
        if (string == nil) {
            string = [objcStr mutableCopy];
        }else {
            [string appendFormat:@"_%@",objcStr];
        }
    }
    return string;
}

然后就是测试方法了:

- (void)testCopyAndStrongForArray {
    
    NSArray *immutableArr = @[@"KFA_test6"];
    self.testStrongArr = immutableArr;
    self.testCopyArr = immutableArr;
    KFALog(immutableArr,self.testStrongArr,self.testCopyArr);
    immutableArr = @[@"KFA_test6_change"];
    KFALog(immutableArr,self.testStrongArr,self.testCopyArr);
    
    NSMutableArray *mutableArr = [@[@"KFA_test7"] mutableCopy];
    self.testStrongArr = mutableArr;
    self.testCopyArr = mutableArr;
    KFALog(mutableArr, self.testStrongArr, self.testCopyArr);
    [mutableArr replaceObjectAtIndex:0 withObject:@"KFA_test7_change"];
    KFALog(mutableArr, self.testStrongArr, self.testCopyArr);
    
    KFACopyModel *model1 = [[KFACopyModel alloc] init];
    model1.number = @"13789892310";
    NSArray *immutableArr_ = @[model1];
    self.testStrongArr = immutableArr_;
    self.testCopyArr = immutableArr_;
    KFALog(immutableArr_, self.testStrongArr, self.testCopyArr);
    model1.number = @"137****2310";
    KFALog(immutableArr_, self.testStrongArr, self.testCopyArr);
    
    KFACopyModel *model2 = [[KFACopyModel alloc] init];
    model2.number = @"13982650942";
    NSArray *mutableArr_ = @[model2];
    self.testStrongArr = mutableArr_;
    self.testCopyArr = mutableArr_;
    KFALog(mutableArr_, self.testStrongArr, self.testCopyArr);
    model2.number = @"139****0942";
    KFALog(mutableArr_, self.testStrongArr, self.testCopyArr);
}

运行结果:

KFA_test6
KFA_test6_change
KFA_test6
KFA_test6
KFA_test7
KFA_test7
KFA_test7 
KFA_test7_change
KFA_test7_change
KFA_test7 
13789892310
13789892310
13789892310 
137****2310
137****2310
137****2310 
13982650942
13982650942
13982650942 
139****0942
139****0942
139****0942

我们发现一个有意思的问题,当数组里元素为字符串时,结果跟咱们预料的一样的,但是当数组的元素为KFACopyModel对象时,结果跟咱们预料有点出入了。这里就得回到上文说的四种情况了。对集合类对象进行copymutableCopy时,即便拷贝内容,拷贝的也是集合对象本身,对于集合内的元素只拷贝了指针,不拷贝内容。所以当数组里的元素KFACopyModel对象的number属性发生变化时,拷贝的数组里元素也会跟着变化。

写在最后

通过上文我们可以总结两点:

  • 对于凡是具有可变子类的类,如NSString有子类NSMutableStringNSArray有子类NSMutableArray,都用copy进行修饰,不用strong。但是对可变类,如NSMutableArray,应该用strong,不用copy,不然就会造成明明定义一个可变数组却不能调用可变数组的方法的后果。
  • 给数组ivar赋值时,为了数组的安全,最好用一个临时的数组对象(此对象不会在其他地方用到),更不能用一个数组ivar来给另一个数组ivar赋值。

本文用的代码全在这里,可以点击下载。欢迎拍砖和转发。

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

推荐阅读更多精彩内容

  • 设计模式是什么? 你知道哪些设计模式,并简要叙述?设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事...
    irenb阅读 5,168评论 1 20
  • 设计模式是什么? 你知道哪些设计模式,并简要叙述? 设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的...
    不懂后悔阅读 823评论 0 53
  • 设计模式是什么? 你知道哪些设计模式,并简要叙述?设计模式是一种编码经验,就是用比较成熟的逻辑去处理某一种类型的事...
    騂跃神话阅读 842评论 0 11
  • 今天是出发的日子,我们将要从北京出发,抵达香港后转机去英国。 在飞机上看...
    A开开开开开开心阅读 142评论 0 0
  • 民中的七月,最适合择一处极为平常的景致,泡上一杯不冷不热的柠檬茶,静候在讲台的某一个角落,时而听学生书写答案的沙沙...
    棒槌草阅读 692评论 0 2