iOS内存管理
开发中,内存的分配及引用计数的增减,每个开发者都知道new、copy、alloc,retain都会有与之对应的release,如果没有new、copy、alloc,retain而产生的新对象,生成的代码都是autorelease,在ARC模式下开发,编译器会自动帮助开发者添加对应的代码。本文将从内存的分配、内存的释放、循环引用及内存释放检测原理方面分享内存管理的小知识。
- 内存分配
- 内存的释放
- 循环引用
- 内存释放检测原理
内存分配
当App运行时,系统会一块内存地址,并把这块内存划分为5个区域:
- 栈区:编译器或系统自动分配并释放;存放函数的参数值、局部变量等
- 堆区:开发者分配和释放,如果开发者没有释放,则程序结束时,由操作系统统一回收
- 全局区:程序结束时,由操作系统统一回收;存放全局变量和静态变量
- 常量区:程序结束时,由操作系统统一回收;存放常量字符串
- 代码区:存放app的二进制代码
内存的释放
当每次调用release时,会自动判断引用计数是否为0,然后再调用dealloc;可以从Runtime的源码中发现调用流程:
1 | - (void)dealloc { |
object_cxxDestruct函数最终调用objc_storeStrong函数来释放ivars,实际上是对ivars变量的引用计数-1.
1 | void __cdecl -[NSobject .cxx_destruct] (NSObject *self, SEL a2) { |
循环引用
在ARC时代,我们使用Strong指针来强引用一个对象(引用计数加1),避免该对象被释放。使用Weak指针来弱引用一个对象(引用计数减1),当这对象没有St rong指针指向该对象时(引用计数为0),该对象也就会被释放。
那么,当一个Strong对象A在使用完毕后,同时还有St rong指针B指向它。而且指针B同时也直接或间接的被A对象持有。恭喜你,这就是循环引用
还有另一种情况,这并不属于循环引用的范畴,但也放在这一起说了吧。就是一个单例对象持有一个对象B,因为单例对象是在栈里的,只有APP销毁时,该单例才会被释放,这也就导致了对象B会一直存在于内存中.
常见的几种循环引用
1 | 1、objA -> objB -> block ->objA |
内存释放检测原理
当一个UIViewController被pop或dismiss后,该UIViewController 包括它的view,view的subviews 等等将很快被释放(除非你把它设计成单例,或者持有它的强引用,但一般很少这样做)。于是,我们只需在一一个ViewController被pop或dismiss一小段时间后,看看该UIViewController,它的view, view 的subviews 等等是否还存在。具体的方法是,为基类NSObject添加一个方法-willDealloc方法,该方法的作用是,先用一个弱指针指向self, 并在一小段时间(3秒)后,通过这个弱指针调用-assertNotDealloc, 而-assertNotDealloc主要作用是直接中断言。
1 | - (BOOL)willDealloc { |
这样,当我们认为某个对象应该要被释放了,在释放前调用这个方法,如果3秒后它被释放成功,weakSelf 就指向nil,不会调用到-assertNotDealloc方法,也就不会中断言,如果它没被释放(泄露了) ,-assertNotDealloc 就会被调用中断言。这样,当一个UIViewController被pop或dismiss时(我们认为它应该要被释放了),我们遍历该UIViewController上的所有view,依次调-willDealloc,若3秒后没被释放,就会中断言。