[iOS] copy和mutableCopy及相关问题

copy和mutableCopy大家应该都遇到过,简单的说就是,mutableCopy返回的对象是可变的(例如NSMutableString),copy返回是不可变的。

注意哦,如果mutable array你用copy修饰就又搞成NSArray啦,于是add remove的时候是会crash的哦!

// for example
NSString *abc = @"abc";
NSMutableString *def = [abc mutableCopy];
[def setString:@"def"];

//会crash,copy返回对象不可变
NSMutableString *str1 = [NSMutableString stringWithFormat:@"1234"];
NSMutableString *str1change = [str1 copy];
[str1change setString:@"12345"];

关于copy可以参考上一篇文章:https://www.jianshu.com/p/1313aac306b1

Firstly, 什么对象可以mutableCopy

实现了NSMutableCopying协议的对象都可以,但如果一个对象并不是mutable的其实实现了也没有多大意义。

@interface  Product() <NSMutableCopying>

@end

@implementation Product

- (id)mutableCopyWithZone:(NSZone *)zone {
    return self; //just for example
}
Secondly, mutableCopy是浅拷贝么?

如果我们像上面那样让Product对象mutableCopyWithZone的时候返回self,可以预见到它的mutableCopy一定是指针拷贝。

Product *product1 = [[Product alloc] init];
Product *product2 = [product1 mutableCopy];
NSLog(@"product1的地址%p", product1);
NSLog(@"product2的地址%p", product2);

输出:
product1的地址0x600000788880
product2的地址0x600000788880 //和product1地址一致

但其实我们自己定义一个对象,他也不是mutable的,执行mutableCopy的意义不太大,主要还是看一下NSString、NSArray、NSDictionary等是如何做的拷贝。

NSString *strA = @"1234";
NSMutableString *strB = [strA mutableCopy];
NSString *strC = [strA mutableCopy];
NSLog(@"strA的地址%p", strA);
NSLog(@"strB的地址%p", strB);
NSLog(@"strC的地址%p", strC);

输出:
strA的地址0x10d02b170
strB的地址0x600003f5fbd0
strC的地址0x600003f5fa20

可以看到mutableCopy对于NSString的拷贝并不是指针拷贝,而是开辟新的内存,并把原字符串的内容拷贝到新的内存地址。

NSArray *arr7 = @[@(1)];
NSArray *arr8 = [arr7 mutableCopy];
NSLog(@"arr7的地址%p, arr8的地址%p", arr7, arr8);
NSLog(@"arr7的内容地址%p, arr8的内容地址%p", arr7[0], arr8[0]);

输出:
arr7的地址0x6000034048d0
arr8的地址0x600003871470 //两者不一样
arr7的内容地址0xcc75a17bc6b5238d
arr8的内容地址0xcc75a17bc6b5238d //两者一样

对于NSArray的拷贝也遵循了开辟新的内存空间,但是仍是将每个元素的指针拷贝过来,不会将元素真正复制的。

※其实所有mutableCopy都不是指针拷贝,而copy对于不可变的对象是指针拷贝,对于可变对象也不是指针拷贝※,这里偷一下最后文章里面的图的总结,列举了所有系统类的copy和mutableCopy的操作是神马类型的拷贝。

copy与mutableCopy.png


下面补一下之前漏掉的两个小点~

① 强制类型转换干了啥

首先,补一下上次忘记的topic,就是当我们把NSMutableString直接赋值给NSString的时候发生了什么,基于之前对为什么要使用copy的探讨,即防止NSMutableString赋值给NSString后发生改变,可以猜测出其实强转就是复制了一下指针啥也没干-_-||

NSMutableString *str1 = [NSMutableString stringWithFormat:@"1234"];
NSString *str2 = (NSString *)str1;
NSLog(@"str1的地址%p, str2的地址%p", str1, str2);
NSLog(@"str1的内容%@, str2的内容%@", str1, str2);
    
[str1 setString:@"12345"];
NSLog(@"str1的地址%p, str2的地址%p", str1, str2);
NSLog(@"str1的内容%@, str2的内容%@", str1, str2);

输出:
str1的地址0x600003117c30, st2的地址0x600003117c30
str1的内容1234, st2的内容1234

str1的地址0x600003117c30, st2的地址0x600003117c30
str1的内容12345, st2的内容12345

果然str1和str2的地址是一样的,并且当str2改变时,str1也会被改掉。

同理,对于数组也是这样,如果把NSMutableArray强转为NSArray,在原数组改变以后,NSArray也会被改掉,这也就是为什么属性要用copy修饰的原因啦。

NSMutableArray *change = [NSMutableArray arrayWithObjects:@(1), @(2), nil];
NSArray *notChange = (NSArray *)change;
NSLog(@"change的内容%@, notChange的内容%@", change, notChange);
[change addObject:@(4)];
NSLog(@"change的内容%@, notChange的内容%@", change, notChange);

输出:
change的内容(1, 2), notChange的内容(1, 2)
change的内容(1, 2, 4), notChange的内容(1, 2, 4)
② arrayWithArray是执行了copy吗
NSArray *arr1 = @[@(1)];
NSArray *arr2 = [NSArray arrayWithArray:arr1];
NSLog(@"arr1的地址%p, arr2的地址%p", arr1, arr2);
NSLog(@"arr1的内容地址%p, arr2的内容地址%p", arr1[0], arr2[0]);

输出:
arr1的地址0x600002503b10
arr2的地址0x600002503af0 //不同于arr1
arr1的内容地址0xbdef16ebabacb1f0
arr2的内容地址0xbdef16ebabacb1f0 //和arr1一致

arr1和arr2地址是不一样的,内容元素地址仍旧是一样的,所以其实arrayWithArray执行的并不是copy(copy对于NSArray只是指针复制哈),那会不会是mutableCopy后类型转换呢?

我们新建一个NSArray 设为nil然后执行mutableCopy试一下~

NSArray *arr3 = nil;
NSArray *arr4 = [arr3 mutableCopy];
NSArray *arr5 = [NSArray arrayWithArray:arr3];
NSLog(@"arr3的地址%p, arr4的地址%p, arr5的地址%p", arr3, arr4, arr5);
NSLog(@"arr3的内容%@, arr4的内容%@, arr5的地址%@", arr3, arr4, arr5);

输出:
arr3的地址0x0, arr4的地址0x0, arr5的地址0x600001bf00d0
arr3的内容(null), arr4的内容(null), arr5的地址()

※如果array == nil,copy的结果是nil,arrayWithArray结果是长度为0的NSArray对象
由于对nil的array,mutableCopy和arrayWithArray返回的结果不一样,所以其实arrayWithArray也并没有调用mutableCopy。

③ Array如何实现元素深拷贝

我本来的想法是继承NSArray然后重写copyWithZone方法,后来发现苹果并不支持我们继承NSString/NSArray之类的系统类,会莫名其妙的crash.....

方案(1) 遍历copy后添加

这个方案大概是最傻的了,让元素的对象实现NSCopying,并且在copyWithZone返回一个新对象,不要返回self;之后新建一个数组,遍历原来的数组,循环为每个元素执行copy后加入新数组。

@interface  Product() <NSCopying>
@end

@implementation Product
- (id)copyWithZone:(NSZone *)zone {
    Product *product = [[Product alloc] init];
    product.name = self.name;
    return product;
}
@end

//试验代码
Product *smile = [[Product alloc] init];
smile.name = @"smile";
NSArray *lucy = @[smile];
NSMutableArray *lucyCopy = [NSMutableArray array];
for (Product *product in lucy) {
  [lucyCopy addObject:[product copy]];
}
NSLog(@"lucy的地址%p, lucyCopy的地址%p", lucy, lucyCopy);
NSLog(@"lucy的内容地址%p, lucyCopy的内容地址%p", lucy[0], lucyCopy[0]);

输出:
lucy的地址0x6000027c47c0
lucyCopy的地址0x600002b98ed0 
lucy的内容地址0x6000027c4790
lucyCopy的内容地址0x6000027c47d0 //元素地址不一样哦
方案(2) 序列化反序列化

这个是参考了最后的文章,文章里面说的是元素的类需要实现NSCoding和NSCopying协议,我感觉其实不太用,毕竟反序列化的时候就已经是一个新的对象了,当然如果对象的copy也想深拷贝最好也实现NSCopying。这里我只实现NSCoding来试验一下啦

@interface  Product() <NSCoding>
@end

@implementation Product
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    self = [super init];
    if (self) {
        _name = [aDecoder decodeObjectForKey:@"name"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeObject:_name forKey:@"name"];
}
@end

//试验代码
Product *smile = [[Product alloc] init];
smile.name = @"smile";
NSArray *lucy = @[smile];
NSArray *lucyCopy = [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:lucy]];

NSLog(@"lucy的地址%p, lucyCopy的地址%p", lucy, lucyCopy);
NSLog(@"lucy的内容地址%p, lucyCopy的内容地址%p", lucy[0], lucyCopy[0]);

输出:
lucy的地址0x600001934560
lucyCopy的地址0x600001934420 //和lucy地址不一致
lucy的内容地址0x600001934500
lucyCopy的内容地址0x600001934670 //元素地址也不一样啦!

References:

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