众所周知,堆内存的空间需要程序猿们自己去管理的。那么提到堆内存,就不得不提到跟对内存息息相关的东西--指针。当我们使用完堆内存空间时,要养成随手释放的好习惯,不然APP的内存占用会越来越多...
Xcode为我们提供了ARC,能够自动管理内存实在是coooooooooool。(7.0之后默认为ARC)。但是一旦有了BUG却不容易发现问题所在。(发现不了的BUG才是最头痛的吧啊喂)
在ARC中,会经常看到Strong和weak这两个标示符。前者表示强引用,会有引用计数的改变,多用于对象。而后者表示弱引用,不会对引用计数发生改变。要注意避免交叉引用的情况,造成两个类释放不掉可就一点都不coooooool了。
既然有自动管理内存(ARC),那么就一定会有手动管理内存(MRC)。
MRC模式下就不会有人帮我们打理堆内存了。一切需要我们自己动手,丰衣足食。
内存管理大致可分为类内和类外。但是在此之前,我们要引入一个引用计数的概念。每一块内存空间都有一个引用计数。这个引用计数说白了就是拥有这个内存的所有权的对象的数量。只有拥有所有权的对象才有权限释放这块内存。(当然或许你也想直接指向一块内存然后将它release掉...但是吧,真的少年。没多少人会刻意这么做吧。)
MRC模式下,alloc语句的作用是向堆内存中申请一块可用空间,申请完之后的引用计数为1(因为申请的那个人也必然是有这块内存的所有权的),retain,copy会将一块内存的引用计数+1。有增有减,平衡之道也。而release就是这样一条可以释放所有权(引用计数-1)的语句。但是如果我们不想立即释放掉某个对象的所有权(这个对象对我们还有用啊喂),但是在后面又找不到合适的机会去让它释放掉(好纠结)。这时我们可以用到autorelease,被autorelease标记的对象会在自动释放池(autoreleasepoor)结束时被释放掉。换句话说,就是当自动释放池结束时,会让池子里的每个标记上autorelease的对象release一次(然后就不会再持有标记了)。
在类外我们需要保证的一点就是引用计数的平衡,也就是说,我们有多少个alloc、retain和copy,我们就要对应地写上多少个release或者autorelease(记得要把被标记的对象放到池子里去啊)。当然,能够让引用计数增加的操作不止retain和copy,比如将对象添加到容器时,或者将视图添加到父视图上...但是这些引用计数会在容器释放时自动释放掉(其实子视图添加到父视图上的原理也如同数组)。也许你会见到一个控件添加到父视图上之后立即release的写法,但那个release是平衡alloc而写上去的。
接下来就是类内,在每个类中,有个类似于C++析构函数的方法dealloc。在MRC中,每创建一个类最好能够重写一下dealloc。尽管他的里面可能只写着[super dealloc]。类内的局部变量的内存管理与类外基本是相同的。唯一注意一点是我们在写便利构造器时,返回的对象要标记为autorelease。顺便说一句系统的方法中是自带自动释放池(虽然你看不见)的,但是我们自己写的方法是没有自动释放池的。但是我们也不用刻意在每个自己写的方法里都写上一个池子。只要保证被autorelease标记的对象最后都在池子里就行。(有点绕口啊喂)
成员变量的内存管理其实是最容易出现过度释放的。尤其是容器类型。之前也说过便利构造器中要加上autorelease,显然,系统的便利构造器也是这样干的。所以,在类内,成员变量的初始化最好不要用便利构造器。(不然有可能会崩得很凄惨)。其他方面其实跟局部变量差不多。保证引用计数平衡,要是不能即刻释放的对象统统把release写到dealloc中。
再来说说属性。虽然是在类内,但是属性的赋值也要用上self.(就是set方法),因为被标记为retain和copy的属性。他们执行set方法引用计数都会+1。如果说成员变量容易出现过度释放,那么属性就容易出现内存泄露的问题。我们会忽略set方法的引用计数+1。
到这里,内存管理神马的基本上就差不多了。
以下是几个小补充:
1.引用计数不会变成0,当一个对象release而这块内存空间的引用计数为1时,系统不会把引用计数-1,而是直接将这块内存回收掉。(dealloc)
2.当我们将一个对象release掉以后,可以让这个对象指向nil,用来防止它调皮地操作已经不属于它的内存区域。
3.关于retain和copy的区别:copy只能用来修饰遵循NSCopying协议的对象,如NSString等,而retain可以修饰一切对象。还有一个重要的区别是用copy后即使是可变的对象(可变字符串,可变容器)也会变成不可变的。而retain则不会。