内存管理 之 autoreleasePool

1、autorelease

由于在MRC环境下,LLVM编译器不会帮我们加上retain、release等这些对引用计数操作的方法。
所以下面,我们在MRC环境下来看一段代码。

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"step---1");
        @autoreleasepool {
            Person *per = [[Person alloc] init];
        }
        NSLog(@"step---2");
    }
    NSLog(@"step---3");
    return 0;
}


打印结果:并没有打印person dealloc
====================================
step---1
step---2
step---3

在创建Person的时候加上autoRelease

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        NSLog(@"step---1");
        @autoreleasepool {
            Person *per = [[[Person alloc] init] autorelease];
        }
        NSLog(@"step---2");
    }
    NSLog(@"step---3");
    return 0;
}


打印结果:
====================================
step---1
person dealloc
step---2
step---3

由上面的两段代码可以看出autorelease的作用:
他能够保证autorelease对象在对应的自动释放池销毁时,对autorelease对象进行一个release操作。这样能够管理内存,而且还避免手动调用release方法。

2、 自动释放池@autoreleasepool { }对应__AtAutoreleasePool

NSAutoreleasePool实际上是个对象引用计数自动处理器,在官方文档中被称为是一个类。
使用clang命令编译main.m文件:

xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
内存管理 之 autoreleasePool_第1张图片
屏幕快照 2019-01-16 下午4.35.33.png

查看对应的.cpp代码,可以看出@autoreleasepool对应处是声名了一个局部变量__autoreleasepool,他的类型是结构体__AtAutoreleasePool。

__AtAutoreleasePool __autoreleasepool;//声名的结构体变量


//__AtAutoreleasePool结构体
struct __AtAutoreleasePool {
    __AtAutoreleasePool() {//构造函数,在创建结构体的时候调用
        atautoreleasepoolobj = objc_autoreleasePoolPush();
    }
    ~__AtAutoreleasePool() {//析构函数,在结构体销毁的时候调用
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    void * atautoreleasepoolobj;
};

结合上面的.cpp源码就可以得知:@autoreleasepool { }这样的自动释放池,对应的底层是一个__AtAutoreleasePool类型的局部变量。
当进入作用域(自动释放池起始处)时,创建__autoreleasepool变量,会调用他的构造函数,从而调用objc_autoreleasePoolPush方法。当离开作用域(自动释放池结束处)时,变量__autoreleasepool会被释放(__autoreleasepool是个局部变量),从而调用objc_autoreleasePoolPop方法。

3、AutoreleasePoolPage

在objc_autoreleasePoolPush和objc_autoreleasePoolPop方法中,有个很重要的AutoreleasePoolPage。调用了autorelease的对象最终都是通过AutoreleasePoolPage来管理的。

#pragma mark -AutoreleasePoolPage的内部结构

class AutoreleasePoolPage 
{
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;  //用于创建线程的标识,应该是AutoreleasePoolPage不能同时用在两个线程中
    static uint8_t const SCRIBBLE = 0xA3;  //刷子,可以不需要关心
    static size_t const SIZE =  PAGE_MAX_SIZE     //4096Bytes
    static size_t const COUNT = SIZE / sizeof(id);    //4096/8

    magic_t const magic; //用于验证 AutoReleasePoolPage 的完整性
    id *next; //栈顶指针,指向了下一个能存放autorelease对象地址的区域  
    pthread_t const thread; //当前线程
    AutoreleasePoolPage * const parent; //父节点, AutoReleasePoolPage 是一个双向链表,这个parent也就是指向上一个节点
    AutoreleasePoolPage *child; //子节点(下一个节点)
    uint32_t const depth; //深度,标识这是第几个节点,从0开始计数
    uint32_t hiwat; // high water mark 高水位线,用来报警内存占用
    ......
}

每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址。

在程序的运行过程中,由于创建的对象很多,AutoreleasePoolPage是多个连起来使用的,所有对象通过双向链表的形式连接在一起。
内存管理 之 autoreleasePool_第2张图片
屏幕快照 2019-01-17 下午3.30.53.png
4、autorelease原理

在了解了autoreleasepool和AutoreleasePoolPage后,我们来看一下autorelease的作用流程。
在上面,我们看到了一个自动释放池autoreleasepool创建,就对应生成一个__autoreleasepool对象。在其作用域内,这个对象的创建和销毁分别对应着__AtAutoreleasePool这个结构体的构造函数push和析构函数pop。
autoreleasePoolPush操作

void *objc_autoreleasePoolPush(void){
    return AutoreleasePoolPage::push();//c++类的push方法
}


//push方法内部逻辑
static inline void *push()  {
     id *dest;
     if (DebugPoolAllocation) {
          dest = autoreleaseNewPage(POOL_BOUNDARY); //创建一个AutoreleasePoolPage,并将这个POOL_BOUNDARY入栈存到page表里
     } else {
          dest = autoreleaseFast(POOL_BOUNDARY);//本来就有page表的话,将POOL_BOUNDARY存入到这个page表中
     }
     assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
     return dest;
}

autoreleasePoolPop操作

void  objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);////c++类的pop方法
}


//push方法内部逻辑
static inline void pop(void *token) //token值应该是当初push进去存放的那个POOL_BOUNDARY值
{
      AutoreleasePoolPage *page;
      id *stop;
      if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            if (hotPage()) {
                pop(coldPage()->begin());
            } else {
                setHotPage(nil);
            }
            return;
        }
        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);//对存在page表中的对象进行release,直到遇见POOL_BOUNDARY值
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            //处理一些跨页page存储对象的问题
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            } else if (page->child->child) {
                page->child->child->kill();
            }
        }
}

autorelease操作

objc_object::rootAutorelease2()
{
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);//AutoreleasePoolPage中的autorelease方法,this是调用autorelease方法的对象
}

//autorelease方法内部逻辑
public: static inline id autorelease(id obj)
{
     assert(obj);
     assert(!obj->isTaggedPointer());
     //将对象的内存地址放进page表中,如果page表已满,就创建新表,否则直接加入到当前page表
     id *dest __unused = autoreleaseFast(obj);
        assert(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
        return obj;
}


//将调用autorelease方法的对象的内存地址放进page表中
//是否新建page表视当前情况而定
static inline id *autoreleaseFast(id obj)
{
      AutoreleasePoolPage *page = hotPage();//取当前page表
      if (page && !page->full()) {
          return page->add(obj);
      } else if (page) {
          return autoreleaseFullPage(obj, page);
      } else {
          return autoreleaseNoPage(obj);
      }
}

原理步骤:

  • 调用push方法将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
  • 在@autoreleasepool { }内调用了autorelease方法的对象,会将他们存入到autoreleasePage表中,会有多张autoreleasePage表双向链接来管理
  • 调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY
5、autorelease和runloop

当程序运行时,主线程会默认创建并且运行一个NSRunloop。除了AppDelegate.m文件中的main函数生成了@autoreleasepool外,ARC会在合适的地方添加@autoreleasepool来管理对象。

@autoreleasepool和runloop互相协作,在每个runloop迭代中都加入自动释放池的push和pop操作来管理对象变量。
runloop的运行逻辑

内存管理 之 autoreleasePool_第3张图片
屏幕快照 2019-01-17 下午7.11.28.png

@autoreleasePool创建与释放时机与runloop的observers相关(observers是监听runloop的状态的,比如进入、退出、休眠、唤醒等),当observer监听到了相应的事件会调用autoreleasepool的push、pop方法。

  • 总结:
    iOS在主线程的Runloop中注册了2个Observer:
  • 第1个Observer
    监听了kCFRunLoopEntry事件,会调用objc_autoreleasePoolPush()
  • 第2个Observer
    监听了kCFRunLoopBeforeWaiting事件,会调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()
    监听了kCFRunLoopBeforeExit事件,会调用objc_autoreleasePoolPop()
6、总结
  • <1>Autorelease对象什么时候会被调用release?
    在没有手动加@autoreleasepool的情况下,autorelease对象是在当前的runloop进入休眠(kCFRunLoopBeforeWaiting)或者退出(kCFRunLoopBeforeExit)时释放的。而autoRelease对象能够释放是因为系统在每个runloop的kCFRunLoopEntry、kCFRunLoopBeforeWaiting、kCFRunLoopBeforeExit节点上会调用自动释放池的push和pop方法。

  • <2>方法里有局部对象, 出了方法后会立即释放吗?
    MRC环境下创建局部对象并调用autoRelease

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"ViewController");
    Person *per = [[[Person alloc] init] autorelease];
}


-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear");
}


-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    NSLog(@"viewDidAppear");
}


打印结果:
====================================
ViewController
viewWillAppear
person dealloc  //person并不是一出作用域就释放的
viewDidAppear

ARC环境下自动管理局部对象

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"ViewController");
    Person *per = [[Person alloc] init];
}


-(void)viewWillAppear:(BOOL)animated{
    [super viewWillAppear:animated];
    NSLog(@"viewWillAppear");
}


-(void)viewDidAppear:(BOOL)animated{
    [super viewDidAppear:animated];
    NSLog(@"viewDidAppear");
}


打印结果:
====================================
ViewController
person dealloc   //person一出作用域就释放的
viewWillAppear
viewDidAppear

从上面的例子也可以看出,与MRC环境下的结果不同,ARC环境下的局部对象person一出作用域就释放了,这也表明ARC下创建对象时并没有给它加autoRelease,大概是在离开作用域时对person对象进行release操作的。
所以可以说如果是创建局部变量时,对其进行autoRelease操作了,那么局部变量并不是离开方法后就释放了,什么时候释放和runloop什么时候进入休眠有关。而在ARC环境下,由于编译器会对局部变量进行引用计数管理,局部对象出了方法就会释放。

你可能感兴趣的:(内存管理 之 autoreleasePool)