理解@property使用的关键字

摘要

@property是什么
众所周知,在iOS开发中,@property是用来声明属性的,默认会自动创建该属性的存储器以及带下划线的成员变量。对于紧随其后的那些关键字也是烂熟于心,信手拈来,但是会用并不代表知道为什么要这样用。

小Tip

属性和变量

在项目中常见的两种写法:

@interface Test : NSObject
{
// 声明变量
NSString *testName;
NSString *testType;
}
// 声明属性
@property(nonatomic,strong) NSString *testName;
@property(nonatomic,strong) NSString *testType;
@end

以上两种声明的区别在于:
(1)声明变量时没有使用到@property,而声明属性时需要用到
(2)使用@property声明的属性,默认会自动生成属性的存取器,也就是getter/setter方法,以及带下划线的成员变量;而单独声明变量则没有前者特性。


@property的关键字

从大体上我们可以归为三类:原子性,存取控制,内存管理

原子性

atomic | nonatomic

1. atomic:原子性,同一时刻只能有一个线程能访问该属性,在进行属性的get/set时候是安全的。也就是说atomic只能在一定程度上保证线程安全,并不是一定的线程安全。

因为原子属性是保证同一时刻只能有一个线程操作该对象,但是不能保证在一次完整的set -> get操作过程中,只有同一个线程操作。比如,线程A先set该属性,完成后线程B也set了该属性,改变了原来的值,在B完成后A再进行get操作,此时会发现属性的值已经不是当初A线程set进去的,因此会有问题。另外一种更极端的情况是在A线程set完成,B线程进入把该对象release了,等A线程再来访问时就会导致crash。所以想要真正的线程安全,还是需要使用到加锁来实现,具体实现就不在本文的讨论范围了。

** 2. nonatomic** :非原子性,顾名思义也就是和atomic含义是相反的。不保证线程安全,可以多个线程同时访问。

小结:在日常开发中,我们绝大部分时候都是使用nonatomic,最主要的原因是nonatomic效率比atomic高很多(坊间传闻约快20倍,具体没有去验证过),因此,在明确只有一个线程访问的情况下推荐使用nonatomic。但是,并不是说任何时候都是用nonatomic,具体要根据实际情况,如果某属性确实存在线程不安全的问题,还是要考虑使用atomic的。


存取控制

readwrite | readonly | writeonly
  1. readwrite:可读可写,默认值。同时拥有getter和setter方法

  2. readonly:可读不可写,只有getter没有setter方法

  3. writeonly:可写不可读,只有setter没有getter方法,很少使用

相对来说,这几个关键字是很简单也容易理解的。


内存管理

strong | weak | unsafe_unretained | assign | retain | copy

1. assign:
(1)默认值,用于修饰值类型(基本数据类型),如:int,float,double,CGFloat,NSInteger等。另外就是不存在所有权关系的对象,如:delegate。
(2)assign修饰的属性不牵涉内存管理,不会被引用计数器管理。

2. retain:
ARC后用于取代retain的,作用等同于retain。表示实例变量对传入的对象要有所有权关系,也就是强引用。

3. strong:
ARC后用于取代retain的,作用等同于retain。表示实例变量对传入的对象要有所有权关系,也就是强引用。

4. weak:
针对对象类型,在setter方法中,不会对传入的对象执行引用计数器+1的操作。也就是对传入的对象没有所有权,是弱引用。另外当传入的对象引用计数器为0,也就是被释放后,用weak声明的实例变量会指向nil,即空对象。

5. unsafe_unretained:
(1)从字面上来看,我们可以拆分成两部分来理解,unretained和retain相反,也就是和strong相反,因此等同于weak,其实也是用在iOS5之前替代weak使用的
(2)unsafe,不安全的,如上所述等同于weak的话那应该是安全的,这里不安全的是指当传入的对象被释放后,使用unsafe_unretained修饰的变量是不知道的,也不会像weak一样指向nil,所以此时访问可能会引起crash。因此,总结来说unsafe_unretained作用是等同于weak,但是是不安全的。

6. copy:在不同情况下会有不同的操作
(1)传入的对象是不可变的,如NSString对象
A. 强引用传进来的对象,使其引用计数器+1,等同于strong
B. 该变量指向地址和传入对象的指向地址一致
(2)传入对象是可变对象,如NSMutableString对象
A. 拷贝一份传入对象,生成一个新的对象,值等同于传入对象值,但是不会强引用传入对象
B. 此时变量指向地址就是上述生成的新对象地址,与传入对象地址无关,也不相同;因此,传入对象改变也不会影响到当前变量的值,两者已经是不相干的两个对象了。

关于copy更多深入分析 请参考此文


几个常见问题

unsafe_unretained、weak、assign 的区别?
  1. 三者都不会对引用计数器进行操作,都不是对象的拥有者
  2. unsafe_unretained作用等同于weak,但其实不安全的,不会对传入的对象做赋nil处理,可能产生野指针。
@property(nonatomic,strong) NSString *strongString;
@property(nonatomic, unsafe_unretained) NSString *unsafe_unretainedString;
self.strongString= [[NSStringalloc] initWithString:"strongString"];
self.unsafe_unretainedString=self.strongString;
self.strongString=nil;
NSLog(@"unsafe_unretainedString == %@",self.unsafe_unretainedString);

这时打印unsafe_unretainedString的值时程序会crash掉。原因是strongString已经释放掉了,但是unsafe_unretainedString并不知道已被释放,所以造成了unsafe_unretainedString访问野指针的内存。

3.assign用于修饰值类型或id,当被释放后如果不手动赋nil,会产生野指针

@property(nonatomic,strong) NSString *strongString;
@property(nonatomic,assign) NSString *assignString;
self.strongString= [[NSString alloc] initWithString:"strongString"];
self.assignString=self.strongString;
self.strongString=nil;
NSLog(@"assignString == %@",self.assignString);

assign的问题和unsafe_unretained类似,可能会产生野指针。

4.weak修饰的变量,当传入的对象被释放后,会自动指向nil,不会产生野指针

@property(nonatomic,strong) NSString *strongString;
@property(nonatomic,weak) NSString *weakString;
self.strongString= [[NSStringalloc] initWithString:"strongString"];
self.weakString=self.strongString;
self.strongString=nil;
NSLog(@"weakString == %@",self.weakString);

由于 self.strongString与self.weakString指向同一地址,且self.weakString没有retain内存地址,而self.strongString =nil释放 了内存,所以self.strongString为nil。由于self.weakString声明为weak的指针,指针指向的地址一旦被释放,这些指针都将被赋值为nil。这样的好处能有效的防止野指针。


delegate使用assign还是weak,为什么?
  1. 理论上来说,两者都是可以使用的,其一,都不会操作delegate的引用计数器,不是强引用对象。其二,在实际开发中,几乎所有场景下,delegate所指向的对象A的生存期都会被delegate变量B自身的生存期覆盖,也就是说A在使用B的过程中,B都是存在的,因此使用assign也不会有问题。
  2. 但是,由于assign不会处理delegate被释放后的情况,上述的也只是大部分情况,不排除可能会出现delegate被释放了而A还在使用,产生野指针错误,而weak正好会处理这个情况,因此我们一般使用weak来修饰delegate,会更加安全

self对象是被strong,weak还是其他关键字修饰的?

在实际开发中,我们随处可见[self testMothod];这种写法,但是真正去思考self的生命周期和内存管理的估计很少。试问假如self在方法执行的过程中被释放了,会发生什么?

通过 查阅clang文档,我们发现了关于self的解释:

The self parameter variable of an Objective-C method is never actually retained by the implementation. It is undefined behavior, or at least dangerous, to cause an object to be deallocated during a message send to that object.
To make this safe, for Objective-C instance methods self is implicitly const unless the method is in the init family. Further, self is always implicitly const within a class method.
Rationale
The cost of retaining self in all methods was found to be prohibitive, as it tends to be live across calls, preventing the optimizer from proving that the retain and release are unnecessary — for good reason, as it’s quite possible in theory to cause an object to be deallocated during its execution without this retain and release. Since it’s extremely uncommon to actually do so, even unintentionally, and since there’s no natural way for the programmer to remove this retain/release pair otherwise (as there is for other parameters by, say, making the variable __unsafe_unretained), we chose to make this optimizing assumption and shift some amount of risk to the user.

上述,self既不是strong也不是weak,而是被unsafe_unretained修饰的,因此要求我们调用的时候保证self是存在的,否则可能会引起无法预料的crash。这样设计的原因是因为在对象方法调用时,我们一定是会把self传递进去,并且在绝大部分情况下做为调用方,我们不会把self释放掉,如果为了处理不到1%情况下的异常而在方法执行时增加retain和release进行保护的话,会对性能有重大影响,因此ARC设计也就没有帮我们处理,需要调用方自行保证self的存在。

以上便是对iOS常用的property后关键字的分析理解。另外,关于copy和block的使用和探讨将在后续的文章进行,这块较复杂内容也较多。

博客迁移

个人博客

你可能感兴趣的:(理解@property使用的关键字)