细说@property(四)
copy和strong
copy和strong的区别是面试中出现频率最高的,我们一般都知道,不可变对象属性修饰符使用copy,可变对象属性修饰符使用strong。
可变对象和不可变对象
- Objective-C中存在可变对象和不可变对象的概念。像NSArray、NSDictionary、NSString这些都是不可变对象,像NSMutableArray、NSMutableDictionary、NSMutableString这些是可变对象。可变对象和不可变对象的区别是,不可变对象的值一旦确定就不能再修改。
- (void)testNotChange
{
NSString *str = @"123";
NSLog(@"str = %p",str);
str = @"234";
NSLog(@"after str = %p",str);
NSMutableString *mutStr = [NSMutableString stringWithString:@"abc"];
NSLog(@"mutStr = %p",mutStr);
[mutStr appendString:@"def"];
NSLog(@"after mutStr = %p",mutStr);
}
NSString是不可变对象,虽然在程序中修改了str的值,但是此处的修改实际上是系统重新分配了空间,定义了字符串,然后str重新指向了一个新的地址,这也是为何修改之后地址不一致的原因。而NSMutableString是可变对象,程序中改变了mutStr的值,且修改前后mutStr的地址一致
2019-01-31 18:02:41.350812+0800 TestClock[884:17969] str = 0x106ec1290
2019-01-31 18:02:41.350919+0800 TestClock[884:17969] after str = 0x106ec12d0
2019-01-31 18:02:41.457179+0800 TestClock[1000:21900] mutStr = 0x600002100540
2019-01-31 18:02:41.457261+0800 TestClock[1000:21900] after mutStr = 0x600002100540
不可变对象用strong会怎样?
上面说了,可变对象使用strong,不可变对象使用copy。那么,如果不可变对象使用strong来修饰,会有什么问题呢?
@property (nonatomic, strong) NSString *strongStr;
- (void)testStrongStr
{
NSString *tempStr = @"123";
NSMutableString *mutString = [NSMutableString stringWithString:tempStr];
self.strongStr = mutString; // 子类初始化父类
NSLog(@"self str = %p mutStr = %p",self.strongStr,mutString); // 两者指向的地址是一样的
[mutString insertString:@"456" atIndex:0];
NSLog(@"self str = %@ mutStr = %@",self.strongStr,mutString); // 两者的值都会改变,不可变对象的值被改变
}
首先明确一点,既然类型是NSString,那么则代表我们不希望testStr被改变,否则直接使用可变对象NSMutableString就可以了。另外需要提醒的一点是,NSMutableString是NSString的子类,对继承了解的应该都知道,子类是可以用来初始化父类的。
注意:我们定义的不可变对象strongStr,在开发者无感知的情况下被篡改了。所谓无感知,是因为开发者没有显示的修改strongStr的值,而是再修改其他变量的值时,strongStr被意外的改变。这显然不是我们想得到的,而且也是危险的。项目中出现类似的bug时,通常都很难定位。这就是不可变对象使用strong修饰所带来的风险。
可变对象用copy会怎样?
这里还是强调一下,既然属性类型是可变类型,说明我们期望再程序中能够改变mutString的值,否则直接使用NSString了。
@property (nonatomic, copy) NSMutableString *mutString;
- (void)testStrCopy
{
NSString *str = @"123";
self.mutString = [NSMutableString stringWithString:str];
NSLog(@"str = %p self.mutString = %p",str,self.mutString); // 两者的地址不一样
[self.mutString appendString:@"456"]; // 会崩溃,因为此时self.mutArray是NSString类型,是不可变对象
}
执行程序后,会崩溃,崩溃原因是:
[NSTaggedPointerString appendString:]: unrecognized selector sent to instance 0xed877425eeef9883
self.mutString没有appendString方法。self.mutString是NSMutableString类型,为何没有appendString方法呢?这就是使用copy造成的。错误原因就在下面这行代码
self.mutString = [NSMutableString stringWithString:str];
这行代码到底发生了什么。这行代码实际上完成了两件事:
// 首先声明一个临时变量
NSMutableString *tempString = [NSMutableString stringWithString:str];
// 将该临时变量copy,赋值给self.mutString
self.mutString = [tempString copy];
注意,通过[tempString copy]得到的self.mutString是一个不可变对象,不可变对象自然没有appendString方法,这也是为何会崩溃的原因。