美文网首页
6. 自动释放池 autorelease

6. 自动释放池 autorelease

作者: 算命的李老师 | 来源:发表于2020-10-07 03:35 被阅读0次

    @autoreleasepool 原理

    ==@autoreleasepool==

    实现原理:以栈为节点通过双向链表形式组合而成的

    编译期

    @autoreleasepool {} 被转换为一个 __AtAutoreleasePool 结构体:

    {
        __AtAutoreleasePool __autoreleasepool;
    }
    

    结构体会在初始化时调用 objc_autoreleasePoolPush() 方法,会在析构时调用 objc_autoreleasePoolPop

    //每个AutoreleasePoolPage 的大小都是 4096 字节(16 进制 0x1000)
    class AutoreleasePoolPage {
        magic_t const magic;
        id *next;//next 指向了下一个为空的内存地址
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    };
    //magic 用于对当前 AutoreleasePoolPage 完整性的校验
    //thread 保存了当前页所在的线程
    //自动释放池中的 AutoreleasePoolPage 是以双向链表的形式连接起来的
    //parent 和 child 就是用来构造双向链表的指针。
    
    ==AutoreleasePoolPage::push==
    • 有 AutoreleasePoolPage 并且当前 page 不满

    调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中

    • 有 AutoreleasePoolPage 并且当前 page 已满

    调用 autoreleaseFullPage 初始化一个新的页
    调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中

    • 无 AutoreleasePoolPage

    调用 autoreleaseNoPage 创建一个 hotPage
    调用 page->add(obj) 方法将对象添加至 AutoreleasePoolPage 的栈中
    最后的都会调用 page->add(obj) 将对象添加到自动释放池中。

    ==AutoreleasePoolPage::pop==
    1. 使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage
    2. 调用 releaseUntil 方法释放栈中的对象,直到 stop
    3. 调用 child 的 kill 方法
    ==autorelease 方法==

    在 autorelease 方法的调用栈中,最终都会调用 autoreleaseFast 方法,将当前对象加到 AutoreleasePoolPage 中。

    ==释放时机==

    在没有手加Autorelease Pool的情况下,Autorelease对象是在当前的runloop迭代结束时释放的,而它能够释放的原因是系统在每个runloop迭代中都加入了自动释放池Push和Pop。

    - (void)viewDidLoad {
        [super viewDidLoad];
        @autoreleasepool {
            NSString *str = [NSString stringWithFormat:@"sunnyxx"];
        }
        NSLog(@"%@", str); // Console: (null)
        //手动干预下在括号结束时释放。
    }
    

    Autorelease返回值的快速释放机制

    ARC runtime有一套对autorelease返回值的优化策略。

    + (instancetype)createSark {
        id tmp = [self new];
        return objc_autoreleaseReturnValue(tmp); // 代替我们调用autorelease
    }
    
    • Thread Local Storage

    Thread Local Storage(TLS优化)线程局部存储,目的很简单,将一块内存作为某个线程专有的存储,以key-value的形式进行读写。

    在MRC时代,autoreleased对象的创建与持有是以以下形式进行的:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        ViewController *obj = [ViewController viewController];
        [obj retain];
        [obj release];
    }
    //函数依次调用中autorelease、retain的作用效果又是相互抵消的
    //所以在ARC时代编译器会根据函数的调用列表来判断autorelease之后是否紧接着调用了retain,如果是则将原本需要push进Autoreleasepool的对象直接返回给函数的调用方
    

    原本的代码被转换成:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        
        id temp = objc_retainAutoreleasedReturnValue([ViewController viewController]);
        ViewController *obj = temp
    }
    

    使用pthread提供的方法实现:

    void* pthread_getspecific(pthread_key_t);
    int pthread_setspecific(pthread_key_t , const void *);
    //在返回值身上调用objc_autoreleaseReturnValue方法时,
    //runtime将这个返回值object储存在TLS中,然后直接返回这个object(不调用autorelease);
    //同时,在外部接收这个返回值的objc_retainAutoreleasedReturnValue里
    //发现TLS中正好存了这个对象,那么直接返回这个object(不调用retain)。
    //于是乎,调用方和被调方利用TLS做中转,很有默契的免去了对返回值的内存管理。
    
    • 其他Autorelease相关知识点

    使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:

    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
    // 这里被一个局部@autoreleasepool包围着
    }];
    当然,在普通for循环和for in循环中没有

    ==为什么autoreleasepool可以嵌套:==

    是因为每次创建autoreleasepool时,实际上是插入哨兵对象的过程。next指针置为哨兵对象(nil),转移向下一位地址,添加对象。

    ==Autoreleasepool对象从哪里来?==

    对于每一个Runloop运行循环,系统会隐式创建一个Autoreleasepool对象

    App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。

    • 第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。

    • 第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。

    什么对象自动加入到 autoreleasepool中

    • 第一种

    当使用alloc/new/copy/mutableCopy开始的方法进行初始化时,会生成并持有对象(也就是不需要pool管理,系统会自动的帮他在合适位置release)

       例如: NSObject *stu = [[NSObject alloc] init]; 
    

    那么对于其他情况,例如

    id obj = [NSMutableArray array];
    //这种情况会自动将返回值的对象注册到autorealeasepool,代码等效于:
    @autorealsepool{
        id __autorealeasing obj = [NSMutableArray array];
    }
    
    • 第二种

    __weak修饰符只持有对象的弱引用,而在访问引用对象的过程中,该对象可能被废弃。那么如果把对象注册到autorealeasepool中,那么在@autorealeasepool块结束之前都能确保对象的存在。

    最新情况下编译器运行说明

    参考:https://stackoverflow.com/questions/40993809/why-weak-object-will-be-added-to-autorelease-pool

    The new implmenetation of __weak of Apple LLVM version 8.0.0 (clang-800.0.42.1) do not postpond the release to autoreleasepool, but use objc_release directly.

    • 第三种

    id或对象的指针在没有显式指定时会被附加上__autorealeasing修饰符 如: ==形参==

     + (nullable instancetype)stringWithContentsOfURL:(NSURL *)url + encoding:(NSStringEncoding)encerror:(NSError **)error;
     //等价于
        NSString *str = [NSString stringWithContentsOfURL: encoding: error:<#(NSError * _Nullable __autoreleasing * _Nullable)#>]
    

    相关文章

      网友评论

          本文标题:6. 自动释放池 autorelease

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