概述
Objective-C 中没有垃圾回收机制,内存管理是依赖对象引用计数器来进行的。
每个对象内部都有一个与之对应的整数(retainCount)叫 “引用计数器”,当一个对象在创建之后它的引用计数器为1,当调用这个对象的 alloc、retain、new、copy 方法之后引用计数器自动在原来的基础上加 1,表示有一个对象或指针持有这块内存,拥有这块内存的所有权,如果这时候又有另外一个对象或指针指向这块内存,那么为了表示这个后来的对象或指针对这块内存的所有权,引用计数会再加 1 成为 2,之后若有一个对象或指针不再指向这块内存时,引用计数 -1,表示这个对象或指针不再拥有这块内存的所有权,当一块内存的引用计数变为 0,表示没有任何对象或指针持有这块内存,系统便会立刻释放掉这块内存。
通过 dealloc 方法来查看是否一个对象应经被回收,如果没有被回收则有可能造成内存泄露。
当一个对象被释放后,需要引用它的变量手动设置为 nil,否则造成野指针错误(即被释放之后,再一次去访问,发现内存已经被回收,指针瞬间就没有归宿感,犹如离家的孩子,孤独无助),并且在给空对象发送消息是不会引起错误的。
野指针错误形式在 Xcode 中通常错误信息为:Thread 1:EXC_BAD_ACCESS(code=EXC_I386_GPFLT) pointer being freed was not allocated,表明指针已经被释放没有被分配,就是说你访问了一块已经不属于你的内存。
黄金法则
如果对一个对象进行了 alloc、retain、copy 操作,就获取了对应对象的所有权,那么就有义务对其进行release 或者 autorelease 操作。简单来说就是需要遵循一个原则:“谁创建,谁释放”。具体来说:
alloc、new:类初始化方法,开辟新的内存空间,引用计数 +1;retain:实例方法,不会开辟新的内存空间,引用计数 +1;copy: 实例方法,把一个对象复制到新的内存空间,新的内存空间引用计数 +1,旧的不会;其中分为浅拷贝和深拷贝,浅拷贝只是拷贝地址,不会开辟新的内存空间;深拷贝是拷贝内容,会开辟新的内存空间;strong:强引用, 引用计数 +1;release:实例方法,释放对象,引用计数 -1;autorelease: 延迟释放;autoreleasepool自动释放池;当执行完之后引用计数 -1;- 使用方法
initWithFormat和stringWithFormat创建字符串的长度大于 9 时,引用计数 +1; assign: 弱引用;weak也是弱引用,两者区别:assign不但能作用于对象还能作用于基本数据类型,但是所指向的对象销毁时不会将当前指向对象的指针指向nil,有野指针的生成;weak只能作用于对象,不能作用于基本数据类型,所指向的对象销毁时会将当前指向对象的指针指向nil,防止野指针的生成。
ARC 环境下的内存管理
Block 引起的循环引用
循环引用:self 对 block 拥有一个强引用,而 block 内部又对 self 有一个强引用,形成闭环,发生内存泄露。
解决办法:在 MRC 中,block 不会 retain 住对象的,但在 ARC 则会 retain 住,需要 __weak 来达到一个弱引用的效果。
- (void)performSelector:引起的内存泄露
运行原理:系统会自动将 Object 的引用计数 +1,直到 selector 执行完毕后才会将引用计数 -1。如果 selector一直没有被执行的话,self 就一直不能被释放,造成内存泄露。
解决方法:将未执行的 perform 取消:
[NSObject cancelPreviousPerformRequestWithTarget:self];
NSTimer 的问题
提出问题:NSTimer 是用来在未来某个时刻执行一次或多次我们指定的方法,那么系统是如何保证 timer 触发action 时,我们指定的方法是有效的,如果接收不到呢?
运行原理:系统会自动 retain 住接受者对象,直到我们指定的方法被执行。
解决办法:如果 timer 被要求执行一次我们指定的方法,系统会自动调用 invalidated,而重复性的 timer 则不会自动调用,一般我们会在 dealloc 的时候,对 timer 调用 invalidated 方法,使 timer 失效;否则dealloc 永远不会被调用,我们需要做的是在 dealloc 之前调用 invalidated。