日常实践中,我们经常要声明一些字符串属性,包括NSString
和NSMutableString
,而字符串最常用的修饰符就要数strong
和copy
了。这两个修饰符对字符串有什么影响呢?
1.不可变源字符串
#示例1:用 NSString 赋值
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
| @interface AppDelegate() @property (nonatomic, strong) NSString *aStrongStr; @property (nonatomic, copy) NSString *aCopyStr; @end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSString *oriStr = [NSString stringWithFormat:@"A"]; self.aStrongStr = oriStr; self.aCopyStr = oriStr;
NSLog(@"++oriStr: 值地址:%p, 对象地址:%p, 值:%@",oriStr,&oriStr,oriStr); NSLog(@"++Strong: 值地址:%p, 对象地址:%p, 值:%@",_aStrongStr,&_aStrongStr,_aStrongStr); NSLog(@"++Copy: 值地址:%p, 对象地址:%p, 值:%@",_aCopyStr,&_aCopyStr,_aCopyStr);
oriStr = @"B";
NSLog(@"++oriStr1: 值地址:%p, 对象地址:%p, 值:%@",oriStr,&oriStr,oriStr); NSLog(@"++Strong1: 值地址:%p, 对象地址:%p, 值:%@",_aStrongStr,&_aStrongStr,_aStrongStr); NSLog(@"++Copy1: 值地址:%p, 对象地址:%p, 值:%@",_aCopyStr,&_aCopyStr,_aCopyStr);
return YES; }
|
输出日志:
1 2 3 4 5 6
| ++oriStr: 值地址:0xcee763de400efcf2, 对象地址:0x7ffeea6357e8, 值:A ++Strong: 值地址:0xcee763de400efcf2, 对象地址:0x600003c7ff28, 值:A ++Copy: 值地址:0xcee763de400efcf2, 对象地址:0x600003c7ff30, 值:A ++oriStr1: 值地址:0x1055d67d0, 对象地址:0x7ffeea6357e8, 值:B ++Strong1: 值地址:0xcee763de400efcf2, 对象地址:0x600003c7ff28, 值:A ++Copy1: 值地址:0xcee763de400efcf2, 对象地址:0x600003c7ff30, 值:A
|
当使用 NSString 类型的oriStr
给strong
和copy
修饰的字符串对象赋值时,两个对象内保存的是字符串oriStr
值对象A
的地址指针。因为oriStr
是不可变字符串,所以只能修改oriStr
的指针,使其指向新的值B
时,此时strong
与copy
的对象内保存的指针并没变,还是指向值原值对象A
的地址。所以oriStr
指针的新变化不会影响前两者内的指针。
2.可变源字符串
#示例2:用 NSMutableString 赋值
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
| @interface AppDelegate() @property (nonatomic, strong) NSString *aStrongStr; @property (nonatomic, copy) NSString *aCopyStr; @end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSMutableString *oriStr = [NSMutableString stringWithFormat:@"A"]; self.aStrongStr = oriStr; self.aCopyStr = oriStr; NSLog(@"++oriStr: 值地址:%p, 对象地址:%p, 值:%@",oriStr,&oriStr,oriStr); NSLog(@"++Strong: 值地址:%p, 对象地址:%p, 值:%@",_aStrongStr,&_aStrongStr,_aStrongStr); NSLog(@"++Copy: 值地址:%p, 对象地址:%p, 值:%@",_aCopyStr,&_aCopyStr,_aCopyStr); [oriStr setString:@"B"];
NSLog(@"++oriStr1: 值地址:%p, 对象地址:%p, 值:%@",oriStr,&oriStr,oriStr); NSLog(@"++Strong1: 值地址:%p, 对象地址:%p, 值:%@",_aStrongStr,&_aStrongStr,_aStrongStr); NSLog(@"++Copy1: 值地址:%p, 对象地址:%p, 值:%@",_aCopyStr,&_aCopyStr,_aCopyStr);
return YES; }
|
输出日志:
1 2 3 4 5 6
| ++oriStr: 值地址:0x600002b960a0, 对象地址:0x7ffee292d7e8, 值:A ++Strong: 值地址:0x600002b960a0, 对象地址:0x6000025f6c08, 值:A ++Copy: 值地址:0xfac5ab7f38f70e39, 对象地址:0x6000025f6c10, 值:A ++oriStr1: 值地址:0x600002b960a0, 对象地址:0x7ffee292d7e8, 值:B ++Strong1: 值地址:0x600002b960a0, 对象地址:0x6000025f6c08, 值:B ++Copy1: 值地址:0xfac5ab7f38f70e39, 对象地址:0x6000025f6c10, 值:A
|
当使用 NSMutableString 类型的oriStr
给strong
类型的字符串赋值时,_aStrongStr
对oriStr
的值对象A
的地址进行了指针拷贝,二者值相等。当oriStr
值对象的指针未变但值变成B
时,_aStrongStr
中值对象指针跟oriStr
一样也没变,所以其值也会变成B
。
当使用 NSMutableString 类型的oriStr
给copy
类型的字符串赋值时,_aCopyStr
对oriStr
的值对象进行了深拷贝,二者指向了不同的对象。当oriStr
的值变化时,_aCopyStr
的值并未跟着变化而是保持不变。
3.小结
1、当源字符串为NSString
类型,给strong
和copy
修饰的属性赋值时,效果一样,都是浅拷贝,得到的两个对象都与源字符串的值相同。同时,由于源字符串不可变,如果想修改这两个对象的值,可以将二者指向新的字符串对象,或直接用字符串字面量赋值。
2、当源字符串为NSMutableString
类型,给strong
修饰的属性赋值时,也是浅拷贝,但要注意,由于源字符串是可变的,所以源字符串的变化会影响到strong
修饰的属性及其对应的成员变量。
3、当源字符串为NSMutableString
类型,使用copy
修饰的属性赋值时,是深拷贝,新对象的值与源字符串的值相同,但二者的指针不同,不会相互影响。
通常情况下,我们声明一个字符串类型的属性并给其赋值时,并不希望源字符串后续的修改会影响到我们的字符串属性,所以,综合起来还是使用copy
稳妥。这样,如果源字符串为NSString
类型,其内容不可变,所以不存在后续影响;如果源字符串为NSMutableString
类型,因为copy
会做深拷贝,所以也不存在后续影响的问题。
4.给属性成员变量的赋值
上面的示例中,在给属性赋值时,使用的都是self.属性 = xx
格式,而直接给属性的成员变量赋值时,即_属性名 = xx
,需要特别注意:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| @interface AppDelegate() @property (nonatomic, strong) NSString *aStrongStr; @property (nonatomic, copy) NSString *aCopyStr; @end
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions { NSMutableString *oriStr = [NSMutableString stringWithFormat:@"A"]; _aStrongStr = oriStr; _aCopyStr = oriStr; [oriStr setString:@"B"]; NSLog(@"++oriStr: 值地址:%p, 对象地址:%p, 值:%@",oriStr,&oriStr,oriStr); NSLog(@"++Strong: 值地址:%p, 对象地址:%p, 值:%@",_aStrongStr,&_aStrongStr,_aStrongStr); NSLog(@"++Copy: 值地址:%p, 对象地址:%p, 值:%@",_aCopyStr,&_aCopyStr,_aCopyStr); return YES; }
|
输出日志:
1 2 3
| ++oriStr: 值地址:0x60000101a760, 对象地址:0x7ffee854ed68, 值:B ++Strong: 值地址:0x60000101a760, 对象地址:0x60000101e148, 值:B ++Copy: 值地址:0x60000101a760, 对象地址:0x60000101e150, 值:B
|
示例中是给属性对应的成员变量
赋值的!虽然源字符串还是NSMutableString
类型,但从打印的日志来看,copy
修饰的属性并未做深拷贝,且它的值始终受到源字符串变化的影响。这是因为,以下划线开头的成员变量是ARC环境下,编译器自动帮我们添加的,给其赋值时,并不会触发属性的 setter,也就没有默认的copy
操作,因此也就不会有copy
效果~