美文网首页iOS基本功
内存管理 之 autoreleasePool

内存管理 之 autoreleasePool

作者: ychen3022 | 来源:发表于2019-01-21 10:51 被阅读41次
    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
    
    屏幕快照 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是多个连起来使用的,所有对象通过双向链表的形式连接在一起。 屏幕快照 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的运行逻辑

    屏幕快照 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

        本文链接:https://www.haomeiwen.com/subject/rwnqjqtx.html