属性修饰符是什么?有什么作用?
属性修饰符,顾名思义就是对属性进行修饰的符号。不同修饰符修饰的属性会表现出不一样的属性特性,其表现形式并不是那么显而易见。
那它的作用是什么呢?从上面的定义上面我们就能发现,被不同修饰符修饰的属性会有不同的特性,根据这些特性,我们能更好的优化自己的代码逻辑,更简单的实现效果。
接下来我们就逐个详细分析一下。
属性修饰符有哪些?
这里我把常用的属性修饰符分为三大类
- 线程安全类
nonatomic/atomic
- 读写权限类
readwrite/readonly
- 内存管理类
assign/copy/strong/weak
还有其他的例如
-
class
类属性 -
setter=/getter=
自定义setter/getter方法名 -
retain
MRC下内存管理
现在我们来详细说一下常用的三大类属性修饰符。
1、线程安全类 nonatomic/atomic
- nonatomic 非原子属性。它的特点是多线程并发访问性能高,但是访问不安全;与之相对的就是atomic,特点就是安全但是是以耗费系统资源为代价,所以一般在工程开发中用nonatomic的时候比较多。
- 系统默认的是atomic,为setter方法加锁,而nonatomic 不为setter方法加锁。
- 如1所述,使用nonatomic要注意多线程间通信的线程安全。
根据上述描述,项目中我们基本上只使用nonatomic
,因为他的访问性能高,对于多线程处理的时候,也建议自己去实现加锁的处理。一是因为atomic
的加锁占用系统资源量大,二是因为atomic
只是在setter/getter方法中进行了加锁处理,在其他操作中是没有的,这里可能会出现遗漏。
2、读写权限类 readwrite/readonly
这个就是访问权限的控制,决定该属性是否可读和可写,默认是readwrite
,所以我们定义属性的时候,一般不需要这个修饰。只有只读属性才需要加上readonly
的修饰。
readonly
来控制读写权限的方式就是只生成getter方法,不生成setter方法。
3、内存管理类 assign/copy/strong/weak
这一类的的修饰符是该文档中最重要的部分了,这也是我们项目编码中,经常会混淆,理解错误的地方。不知道这些修饰符都要什么时候时候使用。
assign
该修饰符是给那些不需要进行内存管理的变量使用的,包括所有的基础类型变量,例如int float double long BOOL NSInteger
等。还有的是由栈区,全局区,常量区管理的变量也可以使用assign来修饰,因为他们的内存已经被系统自动管理了,无需手动额外管理。copy
该修饰符修饰的属性在赋值的时候会多一个执行copy方法的操作,对于copy的方法实现完全按照对应的对象的copy实现,如NSString的copy是不变的。
对于那些属性赋值需要进行浅拷贝的,也就是当前类中处理该属性的值不影响传入变量的情况下,可以使用该类。但是要谨慎使用,很容易会产生问题,例如可变类型不能使用copy来进行修饰,否则赋值的变量都会变成不可变类型的属性,与原先定义的属性类型不一致。在编写代码过程中可能会调用不属与该类的方法。strong
该修饰符相当于是MRC中的retain
修饰符。它是一种强引用,被它修饰的属性都会进行内存管理,也就是引用计数的管理。weak
弱引用,和strong
相对应。常用于解决循环引用问题。被修饰的属性在其他持有者都被释放之后,该属性会自动指向nil
,也就是说,作用和assign一样,但是更加安全!
总结:可以说属性修饰符就是对setter/getter方法的一些功能性扩展,使其定义简单的同时能够满足更多的功能要求。
栈区、常量区、全局区用
assign
堆区持有用strong
、copy
,不持有用weak
什么叫不持有?
就是一个对象别其他变量持有了,那该变量就指向该对象,如果别的变量都不持有该对象了,那该变量也不需要指向该对象了,这就叫不持有。
注意点
接下来我们说明一下在实际使用过程中比较常见的一些误区和知识点。
-
NSString
为什么要用copy
?为什么不能用strong
?
如果是简单的就是NSString
类型的字符串,它们都是存放在常量区里面 的,就不需要进行内存管理,assign,copy,strong
都是可以修饰的,它们实际上都不会去改变任何东西。
@property (nonatomic, assign) NSString *str1;
@property (nonatomic, copy) NSString *str2;
@property (nonatomic, strong) NSString *str3;
self.str1 = @"str1";
self.str2 = @"str2";
self.str3 = @"str3";
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"str1:%@ %p", self.str1, self.str1);
NSLog(@"str2:%@ %p", self.str2, self.str2);
NSLog(@"str3:%@ %p", self.str3, self.str3);
});
2020-07-01 14:37:16.465702+0800 Test_UI[76089:3507003] str1:str1 0x10c1d5150
2020-07-01 14:37:16.465822+0800 Test_UI[76089:3507003] str2:str2 0x10c1d5170
2020-07-01 14:37:16.465888+0800 Test_UI[76089:3507003] str3:str3 0x10c1d5190
但是为什么只能用 copy
呢?因为 NSString
的子类 NSMutableString
,他是存放在堆区的。子类也是可以用父类来接收的,这中情况下 assign
就不能使用了,它无法持有 NSMutableString
会导致被提前释放。不使用 strong
而使用 copy
是为了在赋值的时候去除可变的特性,优化内存管理,让存放在堆区的 NSMutableString
对象及时得到释放。
@property (nonatomic, assign) NSString *str1;
@property (nonatomic, copy) NSString *str2;
@property (nonatomic, strong) NSString *str3;
self.str1 = [@"str1" mutableCopy];
self.str2 = [@"str2" mutableCopy];
self.str3 = [@"str3" mutableCopy];
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"str1:%@ %p", self.str1, self.str1);
NSLog(@"str2:%@ %p", self.str2, self.str2);
NSLog(@"str3:%@ %p", self.str3, self.str3);
NSLog(@"str class: %@", [@"str123" class]);
NSLog(@"mutable str copy class : %@", [[[@"str123" mutableCopy] copy] class]);
});
2020-07-01 14:38:10.159028+0800 Test_UI[76124:3508299] str1:str3 0x6000012d9e00
2020-07-01 14:38:10.159150+0800 Test_UI[76124:3508299] str2:str2 0xaffe863aa45e1a58
2020-07-01 14:38:10.159220+0800 Test_UI[76124:3508299] str3:str3 0x6000012d9e00
2020-07-01 14:38:10.159310+0800 Test_UI[76124:3508299] str class: __NSCFConstantString
2020-07-01 14:38:10.159393+0800 Test_UI[76124:3508299] mutable str copy class : NSTaggedPointerString
我们来分析一下上面输出的情况可以看到 str1
和 str3
的地址变成一样的了,str1
并没有变成野指针崩溃,然后我同时也打印了一下内容,发现 str1
的内容变成了 str3
的内容。这是为什么呢?
因为我们使用 assign
修饰的 str1
,在赋值之后没有持有导致马上就被释放掉了,而该属性指向的内容也没有了,而在之后 str3
创建的对象刚好也是从这个地址开始创建的,所以就造成了上面的那种现象。这里我们把后面 str2,str3
的代码删除之后运行会发现, str1
的内容就变成空了。
2020-07-01 15:13:42.852822+0800 Test_UI[92383:3561873] str1:<__NSMallocBlock__: 0x60000089e220> 0x60000089e220
接下来我们发现 str2
的地址不是在常量区的,这是为什么呢?
最后我们也打印了一下两种字符串的类型,发现它们是不一样的,为什么呢?同样都是不可变字符串,还都是 NSString
。
这是因为 NSString
是另一个类蔟,类蔟的定义和表现我们之后再讲。__NSCFConstantString
该类型的会直接存放在常量区,这是代码编译时就能够决定的,然后直接存放在常量区。NSTaggedPointerString
这种类型的可以说是动态生成的,编译的时候无法判断,所以在他产生的时候 动态添加到栈区里面。
可变类型不能使用
copy
。
可变类型的属性如果使用copy
来修饰,在赋值的时候,该值会进行一次copy
操作,导致属性指向的是一个不可变的对象,如果用该属性去调用可变对象的方法,会产生崩溃。Block到底使用什么来修饰?
block有一个特性,当它访问了外部局部变量(注意:这里是外部的局部变量,全局变量不受影响),就会存放在堆区。
@property (nonatomic, assign) void(^block1)(void);
@property (nonatomic, copy) void(^block2)(void);
@property (nonatomic, strong) void(^block3)(void);
void(^block1)(void) = ^() {
NSLog(@"1");
};
void(^block2)(void) = ^() {
NSLog(@"2");
};
void(^block3)(void) = ^() {
NSLog(@"3");
};
NSLog(@"block1: %p", block1);
NSLog(@"block2: %p", block2);
NSLog(@"block3: %p", block3);
self.block1 = block1;
self.block2 = block2;
self.block3 = block3;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"block1: %p", self.block1);
NSLog(@"block2: %p", self.block2);
NSLog(@"block3: %p", self.block3);
});
2020-07-01 16:39:06.271597+0800 Test_UI[24869:3706043] block1: 0x10a984108
2020-07-01 16:39:06.271695+0800 Test_UI[24869:3706043] block2: 0x10a984128
2020-07-01 16:39:06.271759+0800 Test_UI[24869:3706043] block3: 0x10a984148
2020-07-01 16:39:06.294224+0800 Test_UI[24869:3706043] block1: 0x10a984108
2020-07-01 16:39:06.294314+0800 Test_UI[24869:3706043] block2: 0x10a984128
2020-07-01 16:39:06.294391+0800 Test_UI[24869:3706043] block3: 0x10a984148
所以这样的block所有修饰符都可以修饰。
@property (nonatomic, assign) void(^block1)(void);
@property (nonatomic, copy) void(^block2)(void);
@property (nonatomic, strong) void(^block3)(void);
int num = 2;
void(^block1)(void) = ^() {
NSLog(@"%d", num);
};
void(^block2)(void) = ^() {
NSLog(@"%d", num);
};
void(^block3)(void) = ^() {
NSLog(@"%d", num);
};
NSLog(@"block1: %p", block1);
NSLog(@"block2: %p", block2);
NSLog(@"block3: %p", block3);
self.block1 = block1;
self.block2 = block2;
self.block3 = block3;
dispatch_async(dispatch_get_main_queue(), ^{
NSLog(@"block1: %p", self.block1);
NSLog(@"block2: %p", self.block2);
NSLog(@"block3: %p", self.block3);
});
2020-07-01 17:08:27.093104+0800 Test_UI[25619:3736522] block1: 0x600003fb1950
2020-07-01 17:08:27.093214+0800 Test_UI[25619:3736522] block2: 0x600003fb12f0
2020-07-01 17:08:27.093287+0800 Test_UI[25619:3736522] block3: 0x600003fb08d0
2020-07-01 17:08:27.102584+0800 Test_UI[25619:3736522] block1: 0x600003fb1950
2020-07-01 17:08:27.102696+0800 Test_UI[25619:3736522] block2: 0x600003fb12f0
2020-07-01 17:08:27.102785+0800 Test_UI[25619:3736522] block3: 0x600003fb08d0
这种block就不能使用 assign
来修饰,如果及时释放的话,访问 self.block1
的时候会产生崩溃。
而同样的我们也可以看到,不管是 copy
还是 strong
,block的地址都没有变,所以它们是等价的,而使用 strong
更加直接,性能会更好,而同样的,对已经自动管理的block类型而言,我们所有修饰符都可以使用,所以为了通用,我们在ARC下使用 strong
来修饰所有的block,当然也可以用 copy
,可以说 copy
的修饰是从MRC中继承过来的。