1.@property中的copy关键字如何使用?

  • 对于NSStringNSArrayNSDictionary而言:见下面几个问题的回答。
  • 对于block而言:需要使用copy是为了在原有上下文范围外,继续追踪它捕获的状态。在使用ARC的时候不需要担心这个问题,因为是自动进行的,但是最佳实践是为property属性标记上这个必然的行为(即,添加上copy关键字)。

block copy

其他关于ARC遇上Block的问题,可以参见之后的iOS的内存管理之block一文。

2.用@property声明的NSString/NSArray/NSDictionary,常用copy关键字,为什么?如果改用strong,可能会有什么问题?

结论:使用copy是为了防止传入的属性值被意外更改。如果改为strong,一旦传入的参数是一个可变类型的对象,那么这个参数就会随着这个对象的变化而变化,而这个变化并不一定是@property所属的类内部所期望的,造成不可控的结果。

做个实验来验证:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// strong vs copy (string)
@property (nonatomic, strong) NSString *strongString;
@property (nonatomic, copy) NSString *copiedString;
NSMutableString *s = [NSMutableString stringWithFormat:@"test"];
self.strongString = s;
self.copiedString = s;
NSLog(@"original string: %p", s);
NSLog(@"strong string: %p", _strongString);
NSLog(@"copied string: %p", _copiedString);
[s appendString:@" something else"];
NSLog(@"original = %@, strong = %@, copied = %@", s, self.strongString, self.copiedString);
// Console:
2017-06-29 11:19:16.453 iOS Example[68464:37016982] original string: 0x618000263a40
2017-06-29 11:19:16.453 iOS Example[68464:37016982] strong string: 0x618000263a40
2017-06-29 11:19:16.453 iOS Example[68464:37016982] copied string: 0xa000000747365744
2017-06-29 11:19:16.453 iOS Example[68464:37016982] original = test something else, strong = test something else, copied = test

可以看到,原可变字符串的地址和strong字符串的地址相同,只进行了指针拷贝,而copy字符串的地址是另一个。因此在修改原字符串的时候,使用copy不会改变字符串,而使用strong则会随之变化。

copy&strong

3.用@property声明的NSString、NSArray、NSDictionary的可变类型NSMutableString、NSMutableArray、NSMutableDictionary,用copy是否有问题?

结论:会有问题。因为copy只是将可变类型的对象复制成为不可变的对象,也就无法进行添加、删除、修改的操作。

做个实验来验证:(顺便和strong进行对比)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// strong vs copy (array)
@property (nonatomic, strong) NSMutableArray *strongMutableArray;
@property (nonatomic, copy) NSMutableArray *copiedMutableArray;
NSMutableArray *a = [NSMutableArray arrayWithObjects:@1, @2, nil];
self.strongMutableArray = a;
self.copiedMutableArray = a;
[self.strongMutableArray removeObjectAtIndex:0];
[self.copiedMutableArray removeObjectAtIndex:0]; // <-- Crash here!!
// Console:
2017-06-29 11:19:16.453 iOS Example[68464:37016982] -[__NSArrayI removeObjectAtIndex:]: unrecognized selector sent to instance 0x60800003f3c0
(lldb) po self.copiedMutableArray
<__NSArrayI 0x60800003f3c0>(
1,
2
)
(lldb) po [self.copiedMutableArray isKindOfClass:[NSMutableArray class]]
NO
(lldb) po [self.copiedMutableArray isKindOfClass:[NSArray class]]
YES

可以看出,代码在运行到[self.copiedMutableArray removeObjectAtIndex:0];一行的时候就挂掉了,原因是给NSArray发送removeObjectAtIndex:消息导致unrecognized selector sent to instance。因为作为copy后得到的NSArray对象,并没有任何修改数组的方法。

po一下验证推断:self.copiedMutableArray的确是NSArray类型的对象。
证毕。

4.对集合类对象和非集合类对象的copymutableCopy操作

结论

非集合对象:

非集合对象 immutable object mutable object
copy 浅拷贝 深拷贝
mutableCopy 深拷贝 深拷贝

集合对象:

集合对象 immutable object mutable object
copy 浅拷贝 单层深拷贝
mutableCopy 单层深拷贝 单层深拷贝

这里的单层深拷贝是指,对于集合对象,仅复制集合对象本身,并不复制其中的元素,即集合对象是内容复制,其中的对象元素是指针复制。

做个试验来验证:

非集合对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// copy vs mutableCopy (non-collection object)
NSMutableString *mutableString = [NSMutableString stringWithString:@"immutableString"];
NSString *cpString = [mutableString copy];
NSString *mcpString = [mutableString mutableCopy];
NSString *sString = mutableString;
[mutableString appendString:@" copy”];
NSLog(@"mutable copy: \ncopy: %@(%p)\nmutable copy: %@(%p)\norginal: %@(%p)", cpString, cpString,mcpString,mcpString, sString,sString);
NSString *immutableString = @"immutableString";
NSString *imcpString = [immutableString copy];
NSString *immcpString = [immutableString mutableCopy];
NSLog(@"immutable copy: \ncopy: %p\nmutable copy: %p\norginal: %p", imcpString, immcpString, immutableString);
// Console:
2017-06-29 13:50:41.125 iOS Example[71672:37269990] mutable copy:
copy: immutableString(0x600000241620)
mutable copy: immutableString(0x60000007a600)
orginal: immutableString copy(0x60000007a640)
2017-06-29 13:50:41.126 iOS Example[71672:37269990] immutable copy:
copy: 0x105d7b620
mutable copy: 0x618000076e80
orginal: 0x105d7b620

集合对象:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// copy vs mutableCopy (collection object)
NSMutableArray *mutableArray = [NSMutableArray arrayWithObjects:@1, @2, nil];
NSArray *cpArray = [mutableArray copy];
NSArray *mcpArray = [mutableArray mutableCopy];
NSArray *sArray = mutableArray;
[mutableArray addObject:@3];
NSLog(@"mutable copy: \ncopy: %@(%p)\nmutable copy: %@(%p)\norginal: %@(%p)", cpArray, cpArray,mcpArray,mcpArray, sArray,sArray);
NSArray *immutableArray = @[@5, @6];
NSArray *imcpArray = [immutableArray copy];
NSArray *immcpArray = [immutableArray mutableCopy];
NSLog(@"immutable copy: \ncopy: %@(%p)\nmutable copy: %@(%p)\norginal: %@(%p)", imcpArray, imcpArray,immcpArray,immcpArray, immutableArray,immutableArray);
// Console:
2017-06-29 14:03:00.059 iOS Example[71958:37305362] mutable copy:
copy: (
1,
2
)(0x618000036520)
mutable copy: (
1,
2
)(0x61800005f830)
orginal: (
1,
2,
3
)(0x61800005f860)
2017-06-29 14:03:00.059 iOS Example[71958:37305362] immutable copy:
copy: (
5,
6
)(0x608000036600)
mutable copy: (
5,
6
)(0x60800005d5e0)
orginal: (
5,
6
)(0x608000036600)