先说总结:

1.对于不可变属性,推荐使用copy,能防止不可变对象变成可变对象,从而防止值发生不可控变化。
2.对于可变属性,推荐使用strong,因为用copy修饰后,会变成不可变对象,再调用可变对象的函数时会crash。

1、当修饰的属性为"不可变"时:如NSString、NSArray、NSDictionary:

首先,用copy和strong声明两个不可变属性

@property (nonatomic, strong) NSString *strongStr;
@property (nonatomic, copy  ) NSString *copyedStr;

1.1 初始化一个"不可变"对象,并将其赋值给strongStr和copyedStr:

NSString *str = @"123";
    self.strongStr = str;
    self.copyedStr = str;
    NSLog(@"%p, %p, %p    %@, %@, %@", str, _strongStr, _copyedStr, str, _strongStr, _copyedStr);

输出结果:

2021-09-15 20:37:38.980779+0800 test[96724:6373515] 0x10653d068, 0x10653d068, 0x10653d068    123, 123, 123

【可以看出:对于"不可变"对象,copy和strong指向的都是str的内存地址,copy对于不可变对象进行的是浅拷贝。】

此时,对str进行修改:

str = @"456";
    NSLog(@"%p, %p, %p    %@, %@, %@", str, _strongStr, _copyedStr, str, _strongStr, _copyedStr);

输出结果:

2021-09-15 20:37:38.981080+0800 test[96724:6373515] 0x10653d0a8, 0x10653d068, 0x10653d068    456, 123, 123

【可以看出:copy、strong指向的内存地址和内存地址中的值都没变,str重新开辟内存地址进行了修改。因为他们原本指向的内存地址的值是不可变的,str想要修改就得自立山头。】

1.2 初始化一个"可变"对象,并将其赋值给strongStr和copyedStr:

NSMutableString *mulStr = [[NSMutableString alloc] initWithString:@"PPP"];
    self.strongStr = mulStr;
    self.copyedStr = mulStr;
    NSLog(@"%p, %p, %p    %@, %@, %@", mulStr, _strongStr, _copyedStr, mulStr, _strongStr, _copyedStr);

输出结果:

2021-09-15 20:49:46.263158+0800 test[97029:6382020] 0x600000001fb0, 0x600000001fb0, 0x600000002a80    PPP, PPP, PPP

【可以看出:strongStr指向mulStr的内存地址,而且内存地址的值是个NSMutableString可变类型;而copyedStr发生深拷贝指向另一块NSString类型的内存地址】

此时,对mulStr的值进行修改:

[mulStr appendString:@"++"];
    NSLog(@"%p, %p, %p    %@, %@, %@", mulStr, _strongStr, _copyedStr, mulStr, _strongStr, _copyedStr);

输出结果:

2021-09-15 20:49:46.263429+0800 test[97029:6382020] 0x600000001fb0, 0x600000001fb0, 0x600000002a80    PPP++, PPP++, PPP

【可以看出:尽管我们并没有直接对strongStr的值进行修改,但它还是发生了变化(因为指向了mulStr的内存地址),这正是危险之处;copyedStr的值没有发生变化,因为其内存地址跟mulStr是不同的,所以不可变对象用copy修饰是安全的

// =========== 分隔符 ============

2、当修饰的属性为“可变”时:如NSMutableString、NSMutableArray、NSMutableDictionary:

首先,用copy和strong声明两个可变属性:

@property (nonatomic, strong) NSMutableString *strongMulStr;
@property (nonatomic, copy  ) NSMutableString *copyedMulStr;

2.1 初始化一个"可变"对象,并将其赋值给strongMulStr和copyedMulStr:

NSMutableString *mulStr = [[NSMutableString alloc] initWithString:@"abc"];
	self.strongMulStr = mulStr;
	self.copyedMulStr = mulStr;
	NSLog(@"%p, %p, %p    %@, %@, %@", mulStr, _strongMulStr, _copyedMulStr, mulStr, _strongMulStr, _copyedMulStr);

输出结果:

2021-09-15 21:17:03.368775+0800 test[97765:6402607] 0x6000003860d0, 0x6000003860d0, 0xcf1bace00b0cb946    abc, abc, abc

【可以看出:mulStr和strong对象指向同一块内存地址,而copy修饰的可变对象此时进行了深拷贝重新申请了一块内存地址】

此时,修改mulStr的值:

[mulStr appendFormat:@"def"];
    NSLog(@"%p, %p, %p    %@, %@, %@", mulStr, _strongMulStr, _copyedMulStr, mulStr, _strongMulStr, _copyedMulStr);

输出结果:

2021-09-15 21:17:03.369691+0800 test[97765:6402607] 0x6000003860d0, 0x6000003860d0, 0xcf1bace00b0cb946    abcdef, abcdef, abc

【可以看出:由于mulStr内存地址的值是NSMutableString可变类型,所以直接在原本内存地址进行修改,导致指向这块内存地址的_strongMulStr也跟着改变,而_copyedMulStr是另一块内存所以不受影响。】

2.2 初始化一个"不可变"对象,并将其赋值给strongMulStr和copyedMulStr:

NSString *str = @"PPP";
    self.strongMulStr = str;
    self.copyedMulStr = str;
    NSLog(@"%p, %p, %p    %@, %@, %@", str, _strongMulStr, _copyedMulStr, str, _strongMulStr, _copyedMulStr);

输出结果:

2021-09-15 21:21:06.253837+0800 test[97867:6405460] 0x10fea6068, 0x10fea6068, 0x10fea6068    PPP, PPP, PPP

【可以看出:_strongMulStr和_copyedMulStr都指向了str的内存地址,并且内存地址的值是个NSString不可变类型】

此时,修改str的值:

str = @"PPP++";
    NSLog(@"%p, %p, %p    %@, %@, %@", str, _strongMulStr, _copyedMulStr, str, _strongMulStr, _copyedMulStr);

输出结果:

2021-09-15 21:21:06.254076+0800 test[97867:6405460] 0x10fea60a8, 0x10fea6068, 0x10fea6068    PPP++, PPP, PPP

【可以看出:①str的内存地址重新申请了,因为原本的内存地址的值是NSString不可变类型,想要重新赋值就要申请新的内存;②_strongMulStr和_copyedMulStr的值没有变,因为str重新申请了一块内存进行赋值,对他们没有影响。】

再看个东西,如果对_strongMulStr和_copyedMulStr进行修改:(会crash

NSString *str = @"PPP";
    self.strongMulStr = str; //指向了str这个不可变内存地址
    self.copyedMulStr = str; //指向了str这个不可变内存地址
    [self.strongMulStr appendString:@"~~"];//crash
    [self.copyedMulStr appendString:@"~~"];//crash

输出结果:
直接crash。为什么修改NSMutableString这个可变对象会crash呢?

原因不同:
对于strongMulStr,虽然定义的是NSMutableString属性,但是由于赋值时指向了str这个不可变对象而变成了NSString,而NSString里没有appendString:这个函数,所以报错;
对于copyedMulStr,虽然定义的是NSMutableString属性,但是用copy修饰后进行了深拷贝,变成了NSString类型,也不能再调用appendString:函数。

总结:
1.对于不可变属性,推荐使用copy,能防止不可变对象变成可变对象,从而防止值发生不可控变化。
2.对于可变属性,推荐使用strong,因为用copy修饰后,会变成不可变对象,再调用可变对象的函数时会crash。