iOS开发-@property你不知道的事
写这篇博客是因为偶尔看到一篇文章,具体我忘了。大意就是说找一个靠谱的iOS开发人员,问他一个问题@property'的用法,就能大致知道他的水平。一开始我认为这是一个很低级的问题。然后我看了他大致看了一下他的问题。然后就有了接下来这篇文章。
首先我们创建一个CLT工程。然后创建一个ClassA,在.h文件中添加如下代码
@interface ClassA : NSObject
{
int _num;
char _chr;
}
@end
如上所示,我们定义两个成员变量.最近两年学习iOS朋友估计对这种写法比较陌生,莫慌,切听我慢慢道来.
首先我们将这个声明在main函数使用一下,大家就会发现其实这个时候我们的实例对象时不能访问这两个成员变量.
](http://upload-images.jianshu.io/upload_images/967672-3e1852c1847b2733.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
这个时候有没有想说一句,既然不能访问,我们声明他有神马意义,完全没用啊.原因是神马那.
图2这句话原因应该很清楚了,这个时候我们发现原来,实例变量是protected属性,原则上是不能访问的.这时候有过面向对象变成的朋友会说,改成public属性就可以了.没错,改为public属性的确可以解决,但是这不是我今天要说的重点,有兴趣的朋友可以去尝试一下.
我们大家都应该知道面向对象的三大特征,封装,继承,多态.既然OC是一门面向对象编程的语言,我们我应该能够想到用面向对象语言的特性去解决.没错,就是用数据的封装去解决.有过编程经验的朋友应该听说过set,get方法(好像也叫属性访问控制器),他们是面向对象编程语言惯用的实例变量封装方法.形式如下:
- (void)setNum:(int)num;
- (int)num;
上面两个方法如果看不懂,兄弟,证明你学的有些问题,而且还不小.在这里我不跟大家详谈,如果不懂得朋友,可以看一些关于面向对象的书籍.至于实现,很简单,不过我还是讲他写出来.
- (void)setNum:(int)num {
_num = num;
}
- (int)num {
return _num;
}
为什么我们这样写,我简单也跟大家提一句,其实看到这两个函数,大家应该都明白,提示我们就是讲原来通过实例变量直接访问实例变量属性变成通过公共方法去访问实例变量的属性.
大家肯定会很纳闷,今天你不是要说property,为啥主角到现在都没出场那.ok,现在马上出场.现在我们注释掉原来的声明方式.采用property声明
这个时候
这个时候会出现以下情况:
1.在main函数中可以通过set,get方法访问成员变量,但是不能直接访问.
2.在类中可以直接访问到相应的实例变量.
这个时候我们在来创建另外一个类,我们让个这个类继承与这个类,我们实现是一个共有方法,然后输出num的值,这个时候我们会发现我们可以通过set和get方法可以访问到num,但是不能直接通过变量名访问.
通过以上的实际操作我们得出以下结论.
property声明的变量属性不是public.
property声明的变量会自动生成相应的实例变量,set,get方法
在这里再跟大家多说一点,估计也有很多见到过这个东西。就是如下的形式
@synthesize num = _num;
对于上面这行代码,《objective-c 2.0程序编程》有明确的说明,编译器会自动生成相应的set,get方法,并且与成员变量(_变量名)相关联。
话说如果到了这里你就觉得@property的使用就结束了,那我只能说一句,年轻人,现在知识个开头而已。言归正传,
2property的内存管理
说到这里就要说下事例变量的属性了(其实就是括号里面的内容)。说明一下,变量的属性主要是和内存管理有关,强调一下,是主要是内存管理有关,不是全部
atomic(原子性) vs nonatomic(非原子性)【线程安全】
这主要是针对与多线程编程环境下来说的。怎么说那,假设我们现在多线程环境编程中,如果同时又两个或者两个以上的线程同时对一个属性就行赋值,这个时候为了保证属性调用前后的一致性,我们通常要做些多余的事,这就是传说中的线程安全。也就是说线程安全就是为了保证在多线程环境中,有且仅有一个线程能对当前属性进行set和get操作,其他线程必须等待其完成操作之后再进行操作。
回到咱们主题上,Objective-C中原子性就是为了保证线程安全而存在。如果当前的某一属性的属性为原子性,那么任何一个线程对其记性set和get方法时都会对当前的属性进行加锁和解锁操作(Objective-C中保证线程安全的一种方法)。从而保证其在多线程编程环境的线程安全。通常情况下,我们不会涉及过多的线程安全,并且加锁和解锁操作也会造成相当多的资源开销,所以我们一般都将属性设置为非原子性。但是苹果公司为了安全考虑出发,默认情况下,这个属性是非原子性
readwrite(读写) vs readonly (只读) 【访问控制】
这两个属性相对还是比较好理解的。这属性默认情况下是读写的,这就是为什么我们可以对实例变量进行取值和赋值的操作,其实质就是有set和get方法。通过这个说明相信聪明的你已经猜到只读的含义和实质了。只读的含义大家用心领悟一下,在这里我我说一下。其实就是就是只写了get方法,没有提供get方法。到这里不知道大家有没有想过一个问题。为啥没有只写方法,想了半天,突然发现,只写有不能读有啥用啊
另外如果你不喜欢编译器默认的set和get方法,你可以自定义set和get方法,声明如下
@property (getter=isRunning, readonly) BOOL running;
为了操作的方便,苹果属性将读写属性设置为实例变量的默认属性
接下来是今天要说的重头戏
strong (强引用) vs weak(弱引用) vs assign(赋值) vs copy(复制)
每一遍编程语言都无法绕开的深渊--内存管理。特别是在移动设备上这种内存资源相对短缺的设备上。当然这个也是有相对来说的,比如现在Android手机最大的运行内存已经可达到4G,呵呵哒...... 但是就目前iOS设备来说,内存资源还是比较稀缺的(貌似iPhone 7要扩容了)。
在大多数的面向对象的语言中都是通过垃圾回收,但是苹果公司开发一套我认为相当流b的机制--对象拥有关系(object ownership)。专有的名词没找到,我就强行秀一波英语。大概就是说,当我们对一个对象就行操作时,我们必须确定他的存在,就是说我们必须拥有它。如果当前对象没有拥有者,那么操作系统(不是编译器)会在一个合适的时间(目前我也不确定,有种说法是两个runloop切换期间)将其销毁并释放掉其所占内存。这个机制的实现依赖于ARC机制。至于ARC机制在这里我不多讲,有一点跟大家说明,当对象拥有者增加时,当前对象的引用计数会+1,如果引用计数为0时,就是没有对象拥有,那么这个对象就可能被释放。
strong属性就是表示我拥有当前对象,并且当前对象的引用计数+1.strong有一个好处就是说,我能够确认当前对象的存在,因为只要我不消失,当前对象就不会被销毁,即不会消失。这就保证了我能够在需要的时候随时访问当前这个对象。
引用计数模态.png
通过上面的介绍大家应该能推出来如果要释放一个对象,必须是他的引用计数(拥有者的数量)为0.好大家看下面这张图
循环引用模态.jpg
简单说明一下,对象A强应用对象B,同时对象B也强引用对象A,这时候大家想象一下,当我程序执行结束后,操作系统会怎样释放这两个对象那,因为程序执行过程中,两个对象的引用计数都不可能为0,因而都不会被释放,但是当程序执行结束,会怎样?没错,这个就是经典的循环引用计数问题,而他的直接后果就是鼎鼎大名的“内存泄漏”。当程序执行完成之后,操作系统不会释放掉任何一方,从而导致两者一直留在内存中,导致我们的内存越来越下。
很明显这个不是我们想要的,但是以上的情况和多时候我们又无法避免。为了解决这个问题,另外一个属性被发明出来,他就说weak,同样咱们看一下模态图
弱引用解决循环引用.jpg
简单说明,对象A此时仍然强引用对象B,而对象B弱引用对象A。细心同学发现此时对象B拥有对象A为虚线,并且对象A的引用计数并没有增加。或许你会想是不是画错了。我可以告诉NO,这这你weak的精髓。简单明了,当B释放的时候,我的两个对象都会被释放掉,并且当前对象A的指针会被置为nil。弱引用的实质就是和strong一样拥有当前对象,能够对象当前对象进行操作,但是不使其引用计数增加。这样就完美的解决了循环引用问题。给他家提个醒,不要乱用weak,可能会导致奇怪的bug。代理协议的实例对象通常设置为weak属性
至于copy这个属性可能是有人觉得比较茫然。如果一个属性被设置为copy属性时,对象记性读写操作,他获得的是当前的对象的一份copy,而不是简单对其进行应用。并且源对象的引用计数不会增加。通常经常下copy属性主要用于源对象可能发生改变,而不像当前对象受其影响。不如说我们将一个NSMutableString类型的String赋值给NSString类型的对象,这个时候我们为了防止修改NSMutableString对象对当前对象影响。我们会考虑将其设置为copy。通常情况string对象和block对象会被设置为copy属性。
总结一下,以上三个属性都是针对于对象来说的,但是大家都知道Objective-C不是一门新的编程语言,它只是在C语言的基础上加上了一层面向对象的特性。那么问题来了,C语言中有好多基础类型,声明这些属性时我们怎样设置的他的属性?别急,不还有一个吗?assign主要用来修饰Objective-C中基础属性。Objective-C支持64位以后全面更新了基础属性的定义。其实就是做了一些兼容64的修改,例如int -> NSInteger等。
当然还有一些其他的属性,比如unsafe_unretained,这个跟weak有很多相似,当时不同的是他不会将当前的对象指针置为nil。如果你需要使用他,请确定当前环境不支持weak,因为从他名字就知道这货不是很好。哈哈哈。
当然以上的所有都是针对于ARC机制下来说,对于老的开发程序员(就是MRC下开发的前辈们),还有retain等属性。在这里我就不再展开来说。我表示我赶上了MRC的尾巴,但是当时没有研究这个东东,感觉太坑了。就直接跳进了ARC的怀抱。现在想想,还是真是幸福啊。哈哈
参考文献:
《Objective-C 2.0程序设计》 (美)Steophen G.Kochan 著
Ry`s Cocoa Tutorial http://rypress.com/tutorials/objective-c/properties
Objective-C Property Attributes https://realm.io/news/tmi-objective-c-property-attributes/
iOS-Blog http://www.ios-blog.co.uk/tutorials/objective-c/synthesize-vs-dynamic/
StackOverflow1 http://stackoverflow.com/questions/5170631/what-does-synthesize-window-window-do
StackOverflow2 http://stackoverflow.com/questions/8927727/objective-c-arc-strong-vs-retain-and-weak-vs-assign