• iOS ------ autoreleasePool


    一,@autoReleasePool{}

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
        
        }
        return 0;
    }
    

    我们平时创建一个main函数的代码的时候,就会发现其中有一个这个东西@autoreleasepool{},使用clang编译之后:@autoreleasepool{…}被编译成了{__AtAutoreleasePool __autoreleasepool; … }。

    __AtAutoreleasePool到底是什么?

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

    它其实是一个结构体,在创建__AtAutoreleasePool结构体变量的时候调用了objc_autoreleasePoolPush(void),销毁的时候会调动objc_autoreleasePoolPop(void *),分别是构造函数和析构函数,所以我们可以看出其其实是一个C++封装的自动释放池变量,会将@autoreleasepool{…}中{}中的内容添加到自动释放池中,方便内存管理。

    二,AutoreleasePoolPage

    从上边的__AtAutoreleasePool我们可以看到这两个方法objc_autoreleasePoolPushobjc_autoreleasePoolPop

    void *objc_autoreleasePoolPush(void) {
        return AutoreleasePoolPage::push();
    }
     
    void objc_autoreleasePoolPop(void *ctxt) {
        AutoreleasePoolPage::pop(ctxt);
    }
    

    这里又引入了新的类AutoreleasePoolPage

    class AutoreleasePoolPage {
        magic_t const magic;//AutoreleasePoolPage 完整性校验
        id *next;//存放下一个autorelease对象的地址
        pthread_t const thread; //AutoreleasePoolPage 所在的线程
        AutoreleasePoolPage * const parent;//父节点
        AutoreleasePoolPage *child;//子节点
        uint32_t const depth;//深度,也可以理解为当前page在链表中的位置
        uint32_t hiwat;
    }
    
    • 其实自动释放池(即所有的AutoreleasePoolPage对象)其实就是一个由AutoreleasePoolPage构成的双向链表。
    • 每个AutoreleasePoolPage对象占用4096字节内存,其中56个字节用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址。

    在这里插入图片描述

    • 调用push()方法,往自动释放池中添加一个POOL_BOUNDARY,并返回它存放的内存地址;接着每有一个对象调用autorelease方法,会将它的内存地址添加进自动释放池中。
    • 会调用pop方法的传参token即为POOL_BOUNDARY对应在Page中的地址。当销毁自动释放池时,会调用pop()方法将自动释放池中的autorelease对象全部释放(实际上是从自动释放池的中的最后一个入栈的autorelease对象开始,依次给它们发送一条release消息,直到遇到这个POOL_BOUNDARY

    讲一下POOL_BOUNDARY

    • POOL_BOUNDARY的前世叫做POOL_SENTINEL,称为哨兵对象或者边界对象;
    • POOL_BOUNDARY用来区分不同的自动释放池,以解决自动释放池嵌套的问题;

    Autoreleasepool嵌套探究

    单个page嵌套(autorelease对象较少)

    由于ARC环境下不能调用autorelease等方法,所以需要将工程切换为MRC环境。

    int main(int argc, const char * argv[]) {
        _objc_autoreleasePoolPrint();             // print1
        @autoreleasepool { //r1 = push()
            _objc_autoreleasePoolPrint();         // print2
            NSObject *p1 = [[[NSObject alloc] init] autorelease];
            NSObject *p2 = [[[NSObject alloc] init] autorelease];
            _objc_autoreleasePoolPrint();         // print3
            @autoreleasepool { //r2 = push()
                NSObject *p3 = [[[NSObject alloc] init] autorelease];
                _objc_autoreleasePoolPrint();     // print4
                @autoreleasepool { //r3 = push()
                    NSObject *p4 = [[[NSObject alloc] init] autorelease];
                    _objc_autoreleasePoolPrint(); // print5
                } //pop(r3)
                _objc_autoreleasePoolPrint();     // print6
            } //pop(r2)
            _objc_autoreleasePoolPrint();         // print7
        } //pop(r1)
        _objc_autoreleasePoolPrint();             // print8
        return 0;
    }
    

    autorelease对象的进出栈流程图如下所示。它们的作用域只在@autoreleasepool{}之间,超出之后就全部调用pop释放。

    在这里插入图片描述

    印证了上面的push和pop方法和POOL_BOUNDARY的原理

    多个page嵌套(autorelease对象较少)

    int main(int argc, const char * argv[]) {
        @autoreleasepool { //r1 = push()
            for (int i = 0; i < 600; i++) {
                NSObject *p = [[[NSObject alloc] init] autorelease];
            }
            @autoreleasepool { //r2 = push()
                for (int i = 0; i < 500; i++) {
                    NSObject *p = [[[NSObject alloc] init] autorelease];
                }
                @autoreleasepool { //r3 = push()
                    for (int i = 0; i < 200; i++) {
                        NSObject *p = [[[NSObject alloc] init] autorelease];
                    }
                    _objc_autoreleasePoolPrint();
                } //pop(r3)
            } //pop(r2)
        } //pop(r1)
        return 0;
    }
    

    一个AutoreleasePoolPage对象的内存大小为4096个字节,它自身成员变量占用内存56个字节,所以剩下的4040个字节用来存储autorelease对象的内存地址。又因为64bit下一个OC对象的指针所占内存为8个字节,所以一个Page可以存放505个对象的地址。

    当一个page满的时候,会创建一个新的page,并且每个page之间是以栈为结点通过双向链表的形式组合而成。

    在这里插入图片描述

    autorelease和RunLoop

    iOS在主线程RunLoop中注册了两个Observer

    • 第1个Observer监听了kCFRunLoopEntry事件,在即将进入RunLoop时,会调用objc_autoreleasePoolPush(),自动创建一个__AtAutorreleasePool结构体对象。
    • 第2个Observer
      • ① 监听了kCFRunLoopBeforeWaiting事件,RunLoop即将休眠时,会调用objc_autoreleasePoolPop()objc_autoreleasePoolPush();释放旧的池,创建新的池。
      • ② 监听了kCFRunLoopBeforeExit事件,RunLoop即将退出时,会调用objc_autoreleasePoolPop()释放自动释放池。

    在这里插入图片描述

    总结:

    • 自动释放池本质是一个AutoReleasePoolPage结构体对象,是一个栈结构存储的页,每一个AutoReleasePoolPage都是一个双向链表的形式连接。
    • 自动释放池的压栈和出栈主要是通过__AtAutoreleasePool的构造函数和析构函数objc_autoreleasePoolPushobjc_autoreleasePoolPop
    • 调用push操作就是创建一个新的AutoreleasePoolPage,对AutoreleasePoolPage的具体操作就是插入一个哨兵,并返回插入哨兵POOL_BOUNDARY的内存地址,每有对象调用autorelease方法,会将对象的内存地址添加进page中。
    • 调用pop操作的时候,传入的参数就是push返回的哨兵POOL_BOUNDARY的内存地址token,从自动释放池最后pushautorelease对象开始依次release释放掉,直到遇到哨兵POOL_BOUNDARY
  • 相关阅读:
    MFC程序示例
    java for循环语句
    【java实战】项目经验_04
    MySQL 8.0性能优化实战培训
    2022-2028全球油气无人机行业调研及趋势分析报告
    Linux Mint 的更新管理器现在支持 Flatpak
    学习vue第二天
    kotlin 音频播放,多音轨同时播放,音频播放期间,可以随时设置播放速度
    开发工具创新升级,鲲鹏推进计算产业“竹林”式生长
    半个月时间把MySQL重新巩固了一遍,梳理了一篇几万字 “超硬核” 文章!
  • 原文地址:https://blog.csdn.net/m0_73974056/article/details/140957760