[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() 

@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() 
@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() 
@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

你可能感兴趣的:([iOS] copy和mutableCopy及相关问题)