帮助中心 > 新闻资讯 >iOS - copy和mutableCopy你真的会用么?

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

发布时间:2019-01-14

前言

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;
}

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

相关推荐