iOS基础(十二) - 自动释放池(NSAutoreleasePool)

BGImage.png

前言:因为所以,闲的蛋疼,搞搞事,撸一下NSAutoreleasePool。

autoreleasePool是什么?什么是autoreleasePool?

先来一段代码:

//MRC
id object;
{
  NSString *str1 = @"1";
  NSString *str2 = @"2";
  NSArray *array = @[str1, str2];
  NSLog(@"array: %ld", CFGetRetainCount(array));
  object = [NSArray arrayWithArray: array];
  NSLog(@"array2: %ld", CFGetRetainCount(array));
  NSLog(@"object: %ld", CFGetRetainCount(object));
  [array release];
}

2018-01-18 20:29:11.307512+0800 BasicDemo[50165:1729663] array: 1
2018-01-18 20:29:11.307789+0800 BasicDemo[50165:1729663] array2: 1
2018-01-18 20:29:11.307831+0800 BasicDemo[50165:1729663] object: 1

想想在 MRC 时代,需要手动插入 retainrelease,比如上面的代码在一个函数域中,array 是局部变量,object 是全局变量,一旦函数调用完毕,需要手动调用 release,释放掉 array, 但是,这里的全局变量并不持有 array,也就是说,一旦 array 被释放掉了,全局变量 object 也会变成 nil,这时候访问 object 会有 crash 的风险。那怎么办呢?
有两个方法:
方法一:对象object想长期持有array,可以直接retain数组array。
方法二:假如object只是想暂时持有array,这时候可以使用autoreleasePool来延时释放对象array。

所以,autoreleasePool 主要作用就是延时释放对象,释放对象也是调用release方法。在 ARC 时代,一般很少用autoreleasePool,因为,我们创建对象一般会用strong或者copy来修饰对象,这些修饰词会让对象持有所赋值对象或者新开辟一块内存,创建一个新的对象持有,所以,不必担心。

ARC与autoreleasePool

ARC和autoreleasePool其实是不同的机制,两者并没有冲突。无论MRC还是ARC都有autoreleasePool,autoreleasePool只是延时释放对象的一种机制。可以实现在不持有对象的所有权的情况下,使用对象,不持有也就不用担心对象的内存管理问题,只管使用就好。

ARC环境下,什么对象会加入autoreleasePool?

(1)alloc/new/copy/mutableCopy等持有对象的方法,不会加入autoreleasePool;其他不持有对象的方法通过objc_autoreleaseReturnValue和objc_retainAutoreleasedReturnValue来判断是否需要加入autoreleasePool,这是编译器的优化。

id object = [[NSObject alloc] init];    //自己生成并持有对象,不需要加入autoreleasePool
id object2 = [NSMutableArray array];    //MRC:不是自己生成并且不持有对象,需要加入autoreleasePool
id object3 = [NSMutableArray array];
[object3 retain];    //MRC:不是自己生成,但持有对象,不需要加入autoreleasePool

(2)iOS5及之前的编译器,关键字__weak修饰的对象,会自动加入autoreleasePool;iOS5及之后的编译器,则直接调用的release,不会加入autoreleasePool。

//ARC源码
id obj = [[NSObject alloc] init];
{
    id __weak obj1 = obj;
    NSLog(@"%@", obj1);
}

/* iOS5及之前编译器做法 */
id obj1;
objc_initWeak(&obj1, obj);
-----------------------
//调用方法之前,先新生成一个临时对象retain,然后再将该临时对象加入autoreleasePool,这样可以避免对象提前释放掉
id tmp = objc_loadWeakRetained(&obj1);
objc_autorelease(tmp);
NSLog(@"%@", tmp);
-----------------------
//对象释放掉,清除所有的弱引用,并将对象置为nil
objc_destroyWeak(&obj1);

/* 现在的编译器做法*/
id obj = objc_msgSend(NSObject, "new");
id obj1;
objc_initWeak(&obj1, obj);
//调用方法前,先新生成一个临时对象retain
-----------------------
id tmp = objc_loadWeakRetained(obj1);
NSLog(@"%@", obj1);
//方法调用完之后,再release该临时对象,没有加入autoreleasepool
objc_release(tmp);
-----------------------
//释放对象
objc_destroyWeak(&obj1);

(3)id指针和对象指针(id *,NSError **),会自动加上关键字__autorealeasing,加入autoreleasePool。
资料参考:
《黑幕背后的Autorelease》,《iOS高级编程--内存管理》,Why __weak object will be added to autorelease pool?,《iOS高级编程--内存管理》。

线程 & runLoop & autoreleasePool

如下图:

iOS基础(十二) - 自动释放池(NSAutoreleasePool)_第1张图片
Thread & RunLoop & AutoreleasePool.png

ibireme 大神的 深入理解RunLoop可以知道:
(1)在cocoa框架下,每个线程都会有一个默认不开启的runLoop(主线程的runLoop默认是开启)。
(2)每个runLoop会创建autoreleasePool。
(3)runLoop即将进入休眠或者runLoop即将退出时,会释放旧的autoreleasePool对象,创建新的autoreleasePool对象。
苹果官方文档 Advanced Memory Management Programming Guide还可以知道:
(1)cocoa应用里每个线程都维护自己的一个autoreleasePool栈。
(2)如果循环产生很多临时变量,可以使用本地的autoreleasePool来降低内存的峰值。
(3)子线程,如果没有开启RunLoop,是不会自动创建autoreleasePool;但是,如果出现autorelease对象,则会自动创建autoreleasePool。
参考: does NSThread create autoreleasepool automatically now?以及 Objective-C Autorelease Pool 的实现原理。

autoreleasePool实现原理

可通过objc库的NSObject.mm来查看autorelease的实现(推荐一个编译Runtime源码的blog)。

这里会说一些理解的关键点,更详细的分析请移步:黑幕背后的Autorelease,Objective-C Autorelease Pool 的实现原理。

首先,使用clang命令,获得@autoreleasepool的cpp文件:

/* @autoreleasepool */ 
{
     __AtAutoreleasePool __autoreleasepool; 
}

__AtAutoreleasePool是一个结构体:

extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
//构造函数调用的是objc_autoreleasePoolPush()
//析构函数调用的是objc_autoreleasePoolPop(atautoreleasepoolobj)

可以得出autoreleasepool的创建和释放代码如下:

void *atautoreleasepoolobj = objc_autoreleasePoolPush();
...
objc_autoreleasePoolPop(atautoreleasepoolobj)

因为这两个函数都是extern修饰的,想要查看具体的实现还需要去看objc的源码
在NSObject.mm源码可以找到AutoreleasePoolPage类:

iOS基础(十二) - 自动释放池(NSAutoreleasePool)_第2张图片
autoreleasePoolPage.png
class AutoreleasePoolPage 
{
    ...
    #define POOL_BOUNDARY nil
    static size_t const SIZE = 4096;    //AutoreleasePoolPage的内存大小
    static size_t const COUNT = SIZE / sizeof(id);    

    magic_t const magic;  //一些校验信息
    id *next;    //当前加入autoreleasepool对象的下一个位置
    pthread_t const thread;  //当前使用autoreleasePool的线程
    AutoreleasePoolPage * const parent;  //上一个autoreleasePage
    AutoreleasePoolPage *child;  //下一个autoreleasePage
    uint32_t const depth;  //autoreleasePage的深度
    uint32_t hiwat;

    static inline void *push() {
     //创建并持有NSAutoreleasePool对象
    }

    static inline void pop(void *token) {
    //废弃NSAutoreleasePool对象,释放pool里面的所有对象
    }

    static inline id autorelease(id obj) {
        ...
        //将obj对象加入NSAutoreleasePool里面
        id *dest __unused = autoreleaseFast(obj);
        ...
        return obj;
    }

     static inline id *autoreleaseFast(id obj)
     {
         AutoreleasePoolPage *page = hotPage();
         if (page && !page->full()) {    //page存在并且没有满,则添加当前对象
             return page->add(obj);
         } else if (page) {    //page存在,但是page已满,则调用autoreleaseFullPage(obj, page)
             return autoreleaseFullPage(obj, page);
         } else {    //page不存在,则调用autoreleaseNoPage(obj)
             return autoreleaseNoPage(obj);
         }
     }

     static __attribute__((noinline))
     id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
     {
        ...
         do {
             if (page->child) page = page->child;
             else page = new AutoreleasePoolPage(page);
         } while (page->full());   //寻找到不为空的page,否则,新建一个空的page

         setHotPage(page);
         return page->add(obj);
     }

     static __attribute__((noinline))
     id *autoreleaseNoPage(id obj)
     {
         ...
        
         // Install the first page.
         AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
         setHotPage(page);
        
         // Push a boundary on behalf of the previously-placeholder'd pool.
         if (pushExtraBoundary) {
             page->add(POOL_BOUNDARY);
         }
  
         // Push the requested object or pool.
         return page->add(obj);
     }

     id *add(id obj) {
      //添加对象
     }

     id * begin() {
         //当前autoreleasePage开始存放对象的地址
         return (id *) ((uint8_t *)this+sizeof(*this));
     }

     id * end() {
          //当前autoreleasePage的最高地址
          return (id *) ((uint8_t *)this+SIZE);
     }

     bool empty() {
        //当前的autoreleasePage已满
         return next == begin();
     }

     bool full() { 
         //当前autoreleasePage为空
         return next == end();
     }

     void releaseAll() 
     {
     //释放当前缓冲池的所有对象
     }
}

autoreleasePool的组成结构:

iOS基础(十二) - 自动释放池(NSAutoreleasePool)_第3张图片
autoreleasePool.png
注意几点:

1.autoreleasePool调用push返回来的是hotPage里面的POOL_BOUNDARY对象的位置。
2.每个线程独立管理自己的自动释放池。
3.autoreleasePool由NSAutoreleasePoolPage组成,以双向链表的形式。
4.autoreleasePool可以嵌套,一个autoreleasePage可以包含一个或者多个autoreleasePool,一个autoreleasePool可以跨越多个autoreleasePage。
5.autoreleasePool释放对象是从next-1的位置开始释放,直到POOL_BOUNDARY的位置。
6.autoreleasePool如果有多个嵌套,释放的时候要从最里面的autoreleasePool开始释放,直到最外面的autoreleasePool。
7.调用autorelease方法,如果没有NSAutoreleasePoolPage,则会创建一个。

参考
黑幕背后的Autorelease
Objective-C Autorelease Pool 的实现原理
iOS中autorelease的那些事儿
@autoreleasepool-内存的分配与释放
深入理解RunLoop
Advanced Memory Management Programming Guide
Objective-C高级编程 iOS与OS X多线程和内存管理
引用计数带来的一次讨论
各个线程 Autorelease 对象的内存管理
iOS高级编程--内存管理

你可能感兴趣的:(iOS基础(十二) - 自动释放池(NSAutoreleasePool))