RunLoop 和 autoreleasepool
- RunLoop和线程的一一对应的,对应的方式是以key-value的方式保存在一个全局字典中
- 主线程的RunLoop会在初始化全局字典时创建
- 子线程的RunLoop会在第一次获取的时候创建,如果不获取的话就一直不会被创建
- RunLoop会在线程销毁时销毁
在iOS应用启动后会注册两个Observer管理和维护AutoreleasePool。不妨在应用程序刚刚启动时打印currentRunLoop可以看到系统默认注册了很多个Observer,其中有两个Observer的callout都是** _ wrapRunLoopWithAutoreleasePoolHandler**,这两个是和自动释放池相关的两个监听。
第一个Observer会监听RunLoop的进入,它会回调objc_autoreleasePoolPush()向当前的AutoreleasePoolPage增加一个哨兵对象标志创建自动释放池。这个Observer的order是-2147483647优先级最高,确保发生在所有回调操作之前。
第二个Observer会监听RunLoop的进入休眠和即将退出RunLoop两种状态,在即将进入休眠时会调用objc_autoreleasePoolPop() 和 objc_autoreleasePoolPush() 根据情况从最新加入的对象一直往前清理直到遇到哨兵对象。而在即将退出RunLoop时会调用objc_autoreleasePoolPop() 释放自动自动释放池内对象。这个Observer的order是2147483647,优先级最低,确保发生在所有回调操作之后。
主线程的其他操作通常均在这个AutoreleasePool之内(main函数中),以尽可能减少内存维护操作(当然你如果需要显式释放【例如循环】时可以自己创建AutoreleasePool否则一般不需要自己创建)。
其实在应用程序启动后系统还注册了其他Observer(例如即将进入休眠时执行注册回调_UIGestureRecognizerUpdateObserver用于手势处理、回调为_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv的Observer用于界面实时绘制更新)和多个Source1(例如context为CFMachPort的Source1用于接收硬件事件响应进而分发到应用程序一直到UIEvent)。
在主线程执行的代码,通常是写在诸如事件回调、Timer回调内的。这些回调会被 RunLoop 创建好的 AutoreleasePool 环绕着,所以不会出现内存泄漏,开发者也不必显示创建 Pool 了。
自动释放池的创建和释放,销毁的时机如下所示
- kCFRunLoopEntry; // 进入runloop之前,创建一个自动释放池
- kCFRunLoopBeforeWaiting; // 休眠之前,销毁自动释放池,创建一个新的自动释放池
- kCFRunLoopExit; // 退出runloop之前,销毁自动释放池
什么情况下需要创建自动释放池?
其实自动释放池存在的意义是为了延迟释放一些对象,延迟向对象发送release消息。在实际的开发中,有两种情况是需要手动创建自动释放池的。
1、在多线程中,因为子线程中可能会使用便利构造器等方法来创建对象(类方法),那么这些对象的释放只能放在自动释放池中,此时需要在子线程中添加自动释放池。
使用便利构造器等方法来创建对象是autorelease的对象,需要制动释放池才能释放
主线程已经添加过自动释放池,在main函数里面。
2、就是如果一段代码里面(比如for循环)大量使用便利构造器创建对象,也需要手动添加自动释放池。
什么是便利构造器?
便利构造器在初始化方法的基础上前进了一⼩小步。封装了对象创建过程。
便利构造器是“+”方法,返回本类型的实例,方法名以类名开头,其实一些类中的静态方法也是便利构造器。
可以有0到多个参数。内部实现:封装了alloc和初始化方法。使用起来更加简洁。
默认主线的运行循环(runloop)是开启的,子线程的运行循环(runloop)默认是不开启的,也就意味着子线程中不会创建autoreleasepool,所以需要我们自己在子线程中创建一个自动释放池。(子线程里面使用的类方法都是autorelease,就会没有池子可释放,也就意味着后面没有办法进行释放,造成内存泄漏。)----在主线程中如果产生事件那么runloop才回去创建autoreleasepool,通过这个道理我们就知道为什么子线程中不会创建自动释放池了,因为子线程的runloop默认是关闭的,所以他不会自动创建autoreleasepool,需要我们手动添加
NSThread和NSOperationQueue开辟子线程需要手动创建autoreleasepool,GCD开辟子线程不需要手动创建autoreleasepool,因为GCD的每个队列都会自行创建autoreleasepool