字符串属性

日常实践中,我们经常要声明一些字符串属性,包括NSStringNSMutableString,而字符串最常用的修饰符就要数strongcopy了。这两个修饰符对字符串有什么影响呢?

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 赋值
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 的指针
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 类型的oriStrstrongcopy修饰的字符串对象赋值时,两个对象内保存的是字符串oriStr值对象A的地址指针。因为oriStr是不可变字符串,所以只能修改oriStr的指针,使其指向新的值B时,此时strongcopy的对象内保存的指针并没变,还是指向值原值对象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 赋值
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 的值(注意,不是改变指针)
[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 类型的oriStrstrong类型的字符串赋值时,_aStrongStroriStr的值对象A的地址进行了指针拷贝,二者值相等。当oriStr值对象的指针未变但值变成B时,_aStrongStr中值对象指针跟oriStr一样也没变,所以其值也会变成B

当使用 NSMutableString 类型的oriStrcopy类型的字符串赋值时,_aCopyStroriStr的值对象进行了深拷贝,二者指向了不同的对象。当oriStr的值变化时,_aCopyStr的值并未跟着变化而是保持不变。

3.小结

1、当源字符串为NSString类型,给strongcopy修饰的属性赋值时,效果一样,都是浅拷贝,得到的两个对象都与源字符串的值相同。同时,由于源字符串不可变,如果想修改这两个对象的值,可以将二者指向新的字符串对象,或直接用字符串字面量赋值。

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 赋值
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效果~


字符串属性
https://davidlii.cn/2018/06/15/string-prop.html
作者
Davidli
发布于
2018年6月15日
许可协议