对于OC的使用者来说,最会被问到的就是iOS开发中的内存管理。而只要涉及到内存管理,就肯定会涉及到property。而且在平常iOS开发的面试中,我们也经常会被问到相关的问题。所以这次就结合我所见到的和自己对于属性的理解来进行简述,希望对你们有帮助。
在讲述属性前,我们需要对于iOS开发中的内存管理有一个初步的了解。
本文中的部分内容来源于:这里和这里还有部分来源于苹果官方文档。
iOS开发中的内存使用情况
- 栈(stack):栈是编译器自动分配并释放,用来存放函数的参数,局部变量。
- 堆(heap):堆一般是程序员自己分配和释放,如果我们在使用的过程中,没有释放,那么等到程序完全结束,系统将会对堆中的内容进行回收。一般开发中的alloc就是存放在堆中的
- 全局变量(静态变量)(static):全局变量和静态变量是单独存放的,因为他们的声明周期和整个程序的生命周期一致。释放的时间是由整个程序结束后系统负责回收。
- 文字常量区:一般用来存放常量字符串,比如说
String *str = @"hello world"
,他的释放也和全局变量一致,在程序结束后进行释放。 - 程序代码区:用来存放函数的二进制内容的区域。
他们之间的在内存中存放的关系如下图所示:
而在日常开发过程中,我们所遇到最多的也是最容易问到的就是堆和栈,可能有的人对于这两者还是很模糊,所以我再引用之前看到过的一段话来解释(由于很久以前看到了,已经忘记了出处,如果有人能够告诉我,我一定加上):
内管管理就像做菜,而堆的使用就像我们下馆子,我们只要去酒店,告诉老板我们要什么,他们就会帮我们准备好,我们只要吃完付钱,就可以了,也不用去管理那些之后的琐事。而栈就像我们自己做菜,什么东西都得自己买好,做什么,怎么做都得提前想好。做完之后还需要自己收拾最后的垃圾。
iOS内存管理机制
当你创建的对象的时候,你总得需要告诉操作系统,你将要在什么时候对占有这块内存的对象进行释放,就像平常生活中,你怎么才能确定这房间里面是有人的还是没有人的呢?
一般来说,我们肯定会这么干,每个人进去的时候登记下,然后出来的时候再登记下,这样只要我们在需要管理的时候看下登记再按的人数是不是为0就可以了,只要为0,那么就说明里面没有人,否则就有人,苹果也是这么处理内存管理的。在你创建一个对象的时候,他会需要进行retain
,然后在你不在持有他的时候进行release
,所以每个对象都有一个retain count
来进行计数。
在iOS中有2套内存管理机制:MRC(MannulReference Counting)和ARC(Automatic Reference Counting)。其中ARC起源于iOS 4.3,在那之前,苹果开发者只能手动使用retain
和release
来进行内存管理。这样做的问题很明显,如果你在开发过程中,一个不小心没有retain
和release
成对出现,那么很容易使得内存没有释放,最后导致程序内存不足而导致闪退。
对于ARC,苹果的官方文档是这么解释的:
Automatic Reference Counting (ARC) is a compiler feature that provides automatic memory management of Objective-C objects. Rather than having to think about retain and release operations, ARC allows you to concentrate on the interesting code, the object graphs, and the relationships between objects in your application.
ARC works by adding code at compile time to ensure that objects live as long as necessary, but no longer. Conceptually, it follows the same memory management conventions as manual reference counting (described in Advanced Memory Management Programming Guide) by adding the appropriate memory management calls for you.
总的来说,主要意思就是ARC在本质上就像MRC一样,但是它能够让你花费更少的时间来考虑代码中的retain
和release
。与此同时,编译器能够帮你更加准确的将retain
和release
加在你代码中真正需要的地方。
理解如下图:
有人可能会提到GC(Garbage Collection)——垃圾回收机制。在现在的iOS中GC没有被使用,而在MAC OS X中,GC是被使用的,不过对于GC,苹果是这么解释的:
Garbage collection is deprecated in OS X Mountain Lion v10.8, and will be removed in a future version of OS X. Automatic Reference Counting is the recommended replacement technology. To aid in migrating existing applications, the ARC migration tool in Xcode 4.3 and later supports migration of garbage collected OS X applications to ARC
总的来说ARC将在未来的某一天来取代GC在MAC中的位置。
property(属性)
就算理解的再多,我们还是要和实际相结合。实际运用中与之相对应的就是实例变量的属性。
由于 iOS 中 MRC已经太过于古老,在这就不再多提。
在ARC中,我们创建实例变量经常是这样的
@property (atomic/nonatomic/assign/retain/strong/weak/unsafe_unretained/copy) Number* num
所以我们就为让这内容中的这几个property来进行解释。
- atomic:原子性,简单的解释就是说,他是线程安全的,但是由于线程安全,在操作的过程中,编译器会自己给他上锁,解锁。这样会造成资源的浪费(因为我们平常开发中不会那么频繁的考虑到线程安全这个问题)该属性为默认值
- nonatomic:非原子性,这个和上面那个是双子星,但是他们正好相反,这个由于不安全的,但是因为没有了锁的问题,这样资源就会尽可能的被利用,所以我们在日常开发中使用这个,而对于他的安全性,我们一般都自己在后期开发的过程中,在使用多线程的过程中进行考虑。
- assign:给予了setter方法,assign一般指向一个不是指针指向的对象,比如说
CGFloat
这类值。(关于assign
和weak
在delegate中的区别将会在后面提到) - retain:
retain
用于指向一个有指针的对象,就像MRC中的retain,他是为了增加retain count
,这样使得对象能够在autoreleasepool
中持有内存 - strong:
strong
是在ARC中用来代替retain
的,所以原理相同,即retain count
加一 - weak:
weak
和strong
一般只要懂一点英语的就知道,他们又是一堆双子星,strong
是表示持有,而weak
表示,只是简单的引用。这意味着等到持有weak
的对象呗释放的时候,weak
表示的对象也被释放。而需要注意的是weak
释放后他就会为nil。 - unsafe_unretained:
unsafe_unretained
一般很少被用到,主要的原因是他一般用于在Cocoa底层的那些不能支持weak
属性的变量,比如说NSTextView
,NSFont
,NSColorSpace
等。而他和weak不同的地方是,持有weak
的对象被释放后,weak
对象会被指向nil,而unsafe_unretained
则不会被指向为nil。这样就有了安全隐患。 - copy:
copy
一般我们用在NSString
,NSArray
,NSDictionary
,而原因就是copy会在复制的时候讲源对象进行拷贝。这样他只想的将会是一个新的,retain count
为1的对象。这样在我们对于这个对象的内容进行修改的时候,它将不会影响到原来的那个对象。
除了这几种之外,我们还有4种属性没有提到
- getter:设置getter方法
- setter:设置setter方法
- readonly:只读
- readwrite:可读写
这四种因为可以根据字面意思来进行设置在这就不进行过多的介绍。
这里再补上一句之前提到的问题:
关于为什么使用
weak
而不是用assign
来对delegate进行标注。
首先delegate一般的类型都是id,即可以指向所有对象的id
类型。所以我们既可以使用assign指向他,也可以使用weak指向他。但是因为weak在不被持有的时候会指向nil,而大家都知道,所有通过nil的方法在调用函数的时候,都会返回为nil,这样就能够保证了程序的稳定性,就算没有东西返回,他还是能够正常解析(只是解析出来的值为nil)。而如果使用assign的话,如果一不持有他。那么下次再调用它的时候,他将会指向一个不知名的地址,即野指针。这样就会使得整个程序crash。
autoreleasepool(自动释放池)
既然扯到release,那就不得不提下autoreleasepool,顾名思义,他就是一个用来自动帮你释放的池子(这特么不是废话么)。一般情况下我们不怎么会去使用它,因为在AppKit和UIKit的框架中,事情基本上默认的放在autorelease pool block
中完成的。这样子当你完成这些内容后,对应在内存中的数据就会自动释放,这样就不需要你手动去处理这些数据,从而保证了程序的安全性。
当然这里也提到了这是在使用AppKit和UIKit的情况下。我们仍然有以下这几种情况来使用autoreleasepool的情况:
- 编写的是命令行程序,不基于UI框架
- 当你需要写一个循环,循环里面有很多临时变量的时候
- 当你大量使用辅助线程
总的来说autoreleasepool是为了尽可能的减少无用变量在内存中的占用情况。从而使得程序所需要的内存更少。至于autoreleasepool的释放时间,这就要涉及到runloop,具体内容可以参见sunnyxx大神的这篇文章,相信对你肯定有很大帮助。
循环引用
循环引用对于内存来说是一个大问题。这个问题我们更多的在block中可能会碰到,不过总结起来就是这么一句话:
A对象持有B,那么B的
retain count
为1,而此时A因为被其他变量持有,所有A的retain Count
为1,而B又要引用A,那么A的retain count
又要加一,这样等到持有A的那个对象释放A的时候,就算A的retain count
减一,但还是为1,所以无法释放,而因为A无法释放,导致B也无法成功释放,而外部没有持有A或B中的任意一个,这样就导致了这块内存空间一直被持有。
图示如下:
具体的问题我将会在解释block的时候具体说明,需要知道的一点就是,如果存在循环引用,那么就需要把循环引用中间的一条线断掉,从而使用weak来代替,使得当strong的一方被释放的时候,weak也能被正常释放。
希望以上文章能够对你有所帮助。