按照使用场景和作用可分为六个大类:
- 修饰器类型:@property、@synthesize、@dynamic
- 原子安全类型: nonatomic、atomic
- 计数引用类型: strong、weak、assign、copy、retain、unsafe_unretained
- 读写类型:readonly,readwrite
- 访问类型:extern、static、const
- 局部引用类型:__weak、__block、__strong
1、修饰器类型:@property、@synthesize、@dynamic
其实我也不清楚这种@修饰写法怎么称呼才好,但是我觉得这个写法很像Python里面的修饰器语法,所以我这暂且称之为修饰器类型
1.1、@property
@property (nonatomic,copy) NSString *str;
这么声明属性,相当于声明了成员_str,并默认实现了setStr和getStr方法(前提是我们没有自己写setter和getter)
1.2、@synthesize
#import "ClassA.h"
@interface ClassA()
@property NSString * str3;
@end
@implementation ClassA
// 或者写成 @synthesize str3
@synthesize str3 = str4;
@end
@synthesize要用的话,必须得声明了同名的属性,例如我已经声明了str3,再用@synthesize声明str3,那么就相当于编译器默认生成了一个str3属性和一个str3成员变量。如果再对@synthesize声明的str3赋值str4,那么就相当于生成了str4成员变量,str3的getter个setter实际上操作的都是str4.上面的代码等价于下面的代码.(成员变量和成员属性的区别可以看这里)
#import "ClassA.h"
@interface ClassA(){
NSString * str4;
}
@property (nonatomic,copy) NSString * str3;
@end
@implementation ClassA
-(NSString *)str3 {
return str4;
}
-(void)setStr3:(NSString *)str3 {
str4 = str3;
}
@end
1.3、@dynamic
@dynamic声明属性就相当于告诉编译器,该属性的getter和setter方法由我自己生成,你不要给我报错。一般可通过Category添加getter和setter,或者用runtime的class_addMethod添加方法。感觉用的很少,很少见,作了解就行。
2、原子安全类型: nonatomic、atomic
这两个类型就是根线程相关的,atomic与nonatomicd的主要区别就是系统自动生成的getter/setter方法不一样。
2.1、nonatomic
nonatomic表示非原子属性,也就是线程不安全类型,系统自动生成的getter/setter方法不会进行加锁操作。其内部的具体实现如下:
@property (nonatomic,strong) UIImage *image0;
@synthesize image0 = _image0;
/// MRC模式下
- (UIImage *)image0{
return _image0;
}
- (void)setImage0:(UIImage *)image0 {
if (_image0 != image0) {
[_image0 release];
_image0 = [[image0 retain] autorelease];
}
}
2.2、atomic
atomic表示原子属性,表示线程安全,系统自动生成的getter/setter方法会进行加锁操作。其内部的具体实现如下:
@property (atomic,strong) UIImage *image1;
@synthesize image1 = _image1;
/// MRC模式下
- (UIImage *)image1 {
@synchronized (self) {
return _image1;
}
}
- (void)setImage1:(UIImage *)image1 {
@synchronized (self) {
if (_image1 != image1) {
[_image1 release];
_image1 = [[image1 retain]autorelease];
}
}
}
那么用
atomic修饰的属性也不一定就完全是线程安全的,比如我们定义一个atomic修饰的NSMutableArray属性,我们在执行remove或者add的时候,此时就不是线程安全的,因为remove和add不是原子操作。线程安全的根本是getter和setter方法的线程安全。
注意:atomic会比nonatomic更消耗性能,线程不安全的类型例如NSMutableArray等,就直接用atomic,线程安全类型的也少用atomic,实在要控制线程安全可通过代码加锁来处理。
3、计数引用类型: strong、weak、assign、copy、retain、unsafe_unretained
3.1、strong
strong一般用于修饰类类型,表示指向该对象并拥有该对象,比如修饰NSObject、UIImage等。属于强引用,也可以称之为浅拷贝(指针拷贝),相当于在该属性赋值的时候,只是将指针指向的地址复制给了另一个指针,所以不管多少次赋值,这些指针也指向的同一个对象。
3.2、weak
weak一般是用于修饰防止循环引用的对象,例如delegate(在MRC下delegate使用assign修饰)。如果我们用来修饰比如NSURL、NSObject类型的时候,就会因为没有强指针指向,在作用域结束的时候被释放。
当一个对象不再有strong类型的指针指向它的时候,它就会被释放,即使还有weak型指针指向它,那么这些weak型指针也将被清除。
之前还看过一个问题:weak属性修饰的变量,如何实现在变量没有强引用后自动置为 nil ?runtime 维护了一个weak_table_t 弱引用表 ,用于存储指向某一个对象的所有weak指针。weak表其实是一个哈希表,key是所指对象的地址,value是weak指针的地址的数组。在对象回收的时候,根据对象的地址将所有weak指针地址的数组,遍历数组把其中的数据置为nil
3.3、assign
assign一般用于修饰基本数据类型,例如NSInteger、BOOL、int、double、id等。assign修饰的属性,会被分配到栈上,栈的内存会由系统统一管理,不会造成野指针。当然如果是由assign来修饰对象类型,该对象被释放的时候,但是指针还是指向对象之前的地址,就变成了野指针(如果用了assign来修饰对象,在dealloc函数中将该对象指针手动置为nil,也可以消除野指针)。
前文中我提到了,在MRC下delegate使用assign修饰。然后也介绍了weak修饰的对象被释放时,指针会主动置为nil。然后就产生了一个疑问,为啥MRC模式下不用weak来修饰??然后我在代码中试了一下,如果在MRC模式下用weak修饰对象,就会造成每访问一次,引用计数就会自增1,这样会导致该对象无法被释放,如果不把对象置为nil,就会内存泄露。
3.4、copy
同样用于修饰OC对象类型的数据,同时在MRC即(MMR)手动内存管理时期,用来修饰block,因为block需要从栈区copy到堆区,在现在的ARC时代,系统自动给我们做了这个操作,所一现在使用strong或者copy来修饰block都是可以的。copy和strong相同点在于都是属于强引用,都会是属性的计数加一,但是copy和strong不同点在于,它所修饰的属性当引用一个属性值时,是内存拷贝(深拷贝),就是在引用是,会生成一个新的内存地址和指针地址来,和引用对象完全没有相同点,因此它不会因为引用属性的变更而改变。一般用于修饰类似NSString、NSDictionary等
上面提到了block,如果用copy或者strong修饰的话,block会从栈拷贝到堆区。那么如果block在栈区和堆区有什么区别吗?具体的可参见这里
还有就是copy用来修饰NSMutableArray、NSMutableDictionary、NSMutableString这种不可变对象会导致问题,初始化之后实际生成的却是该类型对应的不可变对象,例如NSArray、NSDictionary、NSString
3.5、retain
retain主要用在MRC模式下,作用和strong几乎等同。
但是,还有一点区别。在MRC模式下,修饰block时,strong相当于copy。修饰block时,retain相当于assign。导致结果:如果用strong修饰没有问题,如果用retain修饰会崩溃。报野指针错误。
3.6、unsafe_unretained
unsafe_unretained类型指针指向一块内存时,内存的引用计数也不会增加,这一点与weak一致。但是与weak类型不同的是,当其所指向的内存被销毁时,unsafe_unretained类型的指针并不会被赋值为nil,也就是变成了一个野指针。对野指针指向的内存进行读写,程序就会crash。(直接拷贝的别人写的,没用过,我试了一下别人的demo好像也没因为野指针crash,建议还是少用这个吧,weak还是挺香的)
4、读写类型:readonly,readwrite
4.1、readonly
readonly相当于是系统只会默认生成该属性的getter方法,只可读不可写,用点属性的形式去赋值,那么就会编译报错。
@property (nonatomic,readonly,copy) NSString * testStr;
但是,这并不意味着这个属性就不能赋值。可以有两种方法来强势给该属性赋值
方案一:如果是在该类的外部访问readonly属性,利用kvc直接赋值
方案二:如果是在该类的内部访问,直接按照getter的规范,把getter方法补上就能可读可写。
通常情况下试用readonly这个修饰,已经能保证类内部的实现的安全了,如果要完全杜绝外界使用kvc来访问,可以通过在类的内部重写accessInstanceVariablesDirectly,固定返回No,那么外界就无法再通过kvc形式来更改
+(BOOL)accessInstanceVariablesDirectly {
return NO;
}
4.2、readwrite
readwrite就是声明该属性可读可写,一般默认就是readwrite,可以不用额外声明。
5、访问类型:extern、static、const
5.1、extern
用法一:首先在.h文件中声明如
#import <UIKit/UIKit.h>
extern NSString * testStr;
@interface FromView : UIView
@end
然后在.m文件中赋值
#import "FromView.h"
NSString *testStr = @"testStr";
@implementation FromView
@end
然后我们在其他文件中引入该文件的.h之后就能访问我们定义的testStr,需要注意的是,一定分成两步来写,定义同时并赋值会报错。
用法二:用于访问全局变量
可以使用extern关键字访问全局变量,前提是该全局变量没有static关键字修饰
我们先定义了 NSString *globalStr = @"helloworld";,然后再别的文件中可访问该变量
extern NSString *globalStr;
globalStr = @"hello world";
5.2、static
static修饰的称之为静态变量,如果配合上const使用,就是静态常量。
- static关键字修饰局部变量,可延长局部变量的生命周期,直到程序结束运行为止。
- static关键字修饰局部变量,不会改变局部变量的作用域,该局部变量还是只能在当前作用域范围内使用。
- static关键字修饰(在.m中声明)的全局变量,使其只在本.m文件内部有效,而其他文件不可连接或引用该变量(即使在外部使用extern关键字也无法访问)。
被static修饰的变量仅在 < 编译阶段 > 初始化一次,在 < 全局 / 静态区 > 为它分配一份内存,一直到程序结束运行由系统回收。
5.3、const
const用于修饰指针变量和基本数据类型,const放在谁前面,谁就是常量不可再更改,下面代码演示:
6、局部引用类型:__weak、__block、__strong
6.1、__weak
有时在使用block 的时候,由于self 是被强引用的,在 ARC 下,当编译器自动将代码中的block从栈拷贝到堆时,block 会强引用和持有self,而 self 恰好也强引用和持有了 block,就造成了传说中的循环引用。 此时 __weak 就出场了,在变量声明时用 __weak修饰符修饰变量 self,让 block 不强引用 self,从而破除循环。
__weak __typeof(self) weakSelf = self;
self.testBlock = ^{
[weakSelf test];
});
ps: 弱引用不会影响对象的释放,但是当对象被释放时,所有指向它的弱引用都会自定被置为 nil,这样可以防止野指针。
6.2、__block
在一个 block里头如果使用了在 block之外的变数,会将这份变数先复制一份再使用,也就是,在没有特别宣告下,对于目前的block 来说,所有的外部的变数都是只可读的,不可改的。 如果我们想让某个 block 可以改动某个外部的变数,我们则需要将这个可以被 block 改动的变数前面加上__block。从另一个角度说,使用了__block关键字的变量会将变量从栈上复制到堆上。栈上那个变量会指向复制到堆上的变量。
__block int i = 1;
void (^block)(void) = ^{
i = i + 1;
};
6.3、__strong
在上述使用 block中,虽说使用__weak,但是此处会有一个隐患,你不知道 self 什么时候会被释放,为了保证在block内不会被释放,我们添加__strong。
__weak __typeof(self) weakSelf = self;
self.testBlock = ^{
__strong __typeof(weakSelf) strongSelf = weakSelf;
[strongSelf test];
});
PS: __strong 表示强引用,对应定义 property 时用到的 strong。当对象没有任何一个强引用指向它时,它才会被释放。
weakSelf是为了block不持有self,避免循环引用,而再声明一个strongSelf是因为一旦进入block执行,就不允许self在这个执行过程中释放。block执行完后这个strongSelf会自动释放,没有循环引用问题。
比较__block & __weak
1. __block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。
2. __weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int),__strong也是一样的。
3. __block对象可以在block中被重新赋值,__weak不可以。
4. __block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。
如果各位大佬看完觉得有哪里写的不对或需要补充的,请留言指出
参考文章: