美文网首页iOS进阶iOSer 的自我修养iOS
各个线程 Autorelease 对象的内存管理

各个线程 Autorelease 对象的内存管理

作者: Joy___ | 来源:发表于2017-03-25 00:26 被阅读3915次

    最近和 bestswifterkuailejim 搞了一套模拟面试,然后不管是应届生还是工作两三年的高级工程师都对下面这几个问题比较懵逼,可能是开发中用到的不多,在这里浅浅的讨论下

    • Autoreleasepool 与 Runloop 的关系
    • ARC 下什么样的对象由 Autoreleasepool 管理
    • 子线程默认不会开启 Runloop,那出现 Autorelease 对象如何处理?不手动处理会内存泄漏吗?

    针对第一个问题,比较容易理解,可以看一下:ibireme深入理解RunLoop,主线程默认为我们开启 Runloop,Runloop 会自动帮我们创建Autoreleasepool,并进行Push、Pop 等操作来进行内存管理

    第二个问题,ARC 下什么样的对象由 Autoreleasepool 管理呢?大多数人的回答是:“都会由 pool 进行管理”。其实并不是这样的,对于普通的对象是由编译器在合适的地方为我们 Realease 了。针对这个问题,我已经总结过:引用计数带来的一次讨论,是参考了经典的《iOS与OS X多线程和内存管理 》这本书。

    针对第三个问题,感觉比较难以回答,需要很细致的读过 Runtime 、Autoreleasepool 的源码才可以。我也是参考了 StackOverFlow 的回答:does NSThread create autoreleasepool automaticly now?。我再来简单阐述下,在子线程你创建了 Pool 的话,产生的 Autorelease 对象就会交给 pool 去管理。如果你没有创建 Pool ,但是产生了 Autorelease 对象,就会调用 autoreleaseNoPage 方法。在这个方法中,会自动帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage,如果你还是不理解,可以先看看 Autoreleasepool 的源代码,再来看这个问题 ),并调用 page->add(obj)将对象添加到 AutoreleasePoolPage 的栈中,也就是说你不进行手动的内存管理,也不会内存泄漏啦!StackOverFlow 的作者也说道,这个是 OS X 10.9+和 iOS 7+ 才加入的特性。并且苹果没有对应的官方文档阐述此事,但是你可以通过源码了解。这里张贴部分源代码:

    static __attribute__((noinline))
    id *autoreleaseNoPage(id obj)
    {
        // No pool in place.
        // hotPage 可以理解为当前正在使用的 AutoreleasePoolPage。
        assert(!hotPage());
        
        // POOL_SENTINEL 只是 nil 的别名
        if (obj != POOL_SENTINEL  &&  DebugMissingPools) {
            // We are pushing an object with no pool in place, 
            // and no-pool debugging was requested by environment.
            _objc_inform("MISSING POOLS: Object %p of class %s "
                         "autoreleased with no pool in place - "
                         "just leaking - break on "
                         "objc_autoreleaseNoPool() to debug", 
                         (void*)obj, object_getClassName(obj));
            objc_autoreleaseNoPool(obj);
            return nil;
        }
        
        // Install the first page.
        // 帮你创建一个 hotpage(hotPage 可以理解为当前正在使用的 AutoreleasePoolPage
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
    
        // Push an autorelease pool boundary if it wasn't already requested.
        // POOL_SENTINEL 只是 nil 的别名,哨兵对象
        if (obj != POOL_SENTINEL) {
            page->add(POOL_SENTINEL);
        }
    
        // Push the requested object.
        // 把对象添加到 自动释放池 进行管理
        return page->add(obj);
    }
    

    Reference

    相关文章

      网友评论

      • 2206f664a737:对第三个问题,我觉得说的不是很清楚。而且很多博客对线程和autoreleasepool的关系都描述成"一个线程对应一个autoreleasepool"也不恰当,对autoreleasepool和AutoreleasepoolPage关系描述也不恰当。
        实际一个线程对应一个autoreleasepool堆栈,而这个堆栈则是通过AutoreleasepoolPage的双向链表(数据结构)进行实现的。所以对于第三个问题“子线程对Autorelease对象如何处理”,子线程中在第一次push的时候创建AutoreleasepoolPage双链表,也就是autoreleasepool堆栈,然后所有的autoreleasepool后者说Autorelease对象都被压入到栈里,只是每个autoreleasepool之间都有一个POOL_SENTINEL标记界限(栈层次)。而调用[obj autoreplease]或者@autoreleasepool{}都会发生push操作。
        2206f664a737:而调用[obj autoreplease]或者@autoreleasepool{}都会发生push操作,前者将obj压栈,后者将POOL_SENTINEL标记压栈。
      • 2206f664a737:```
        __weak id ref = nil;

        @Implementation ViewController

        - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        NSString *str;
        @autoreleasepool {
        {
        NSString *str = [[NSString alloc] initWithFormat:@"aaa"];
        ref = str;
        }
        NSLog(@"-.ref = %@(%p)", ref, ref);
        NSString *str = [@[@"1", @"2", @"3", @"4"] componentsJoinedByString:@"|"];
        // ref = str;
        NSLog(@"0.ref = %@(%p)", ref, ref);
        }
        NSLog(@"1.ref = %@(%p)", ref, ref);
        }
        - (void)viewWillAppear:(BOOL)animated {
        [super viewWillAppear:animated];
        NSLog(@"2.ref = %@(%p)", ref, ref);
        }
        - (void)viewDidAppear:(BOOL)animated {
        [super viewDidAppear:animated];
        NSLog(@"3.ref = %@(%p)", ref, ref);
        }

        @EnD
        ```
        这面这段代码的输出结果:
        ```
        2017-09-29 14:37:19.073666+0800 SJAdditionsTest[1820:1188820] -.ref = aaa(0xa000000006161613)
        2017-09-29 14:37:19.073804+0800 SJAdditionsTest[1820:1188820] 0.ref = aaa(0xa000000006161613)
        2017-09-29 14:37:19.073898+0800 SJAdditionsTest[1820:1188820] 1.ref = aaa(0xa000000006161613)
        2017-09-29 14:37:19.074108+0800 SJAdditionsTest[1820:1188820] 2.ref = aaa(0xa000000006161613)
        2017-09-29 14:37:19.085965+0800 SJAdditionsTest[1820:1188820] 3.ref = aaa(0xa000000006161613)
        ```
        和预期的完全不一样,大神能解释下吗?
        啊哈呵:NSString *str = [[NSString alloc] initWithFormat:@"aaa"]; 这个Tagged Pointer“假对象”,设置@“aaaaaaaaaaaa”很长的字符串就可以了
      • 涂耀辉::+1:第三条找了好久,官方说有些线程需要自己创建自动释放池,gcd、nsoperation,nsthread都试了,发现对象能释放,还以为是说c或者c++类型的线程需要自动释放池。。
        PokerFace_u:your detached thread does not make Cocoa calls, you do not need to create an autorelease pool.

        POSIX thread APIs 方式创建的线程 不用添加autoreleasepool,cocoa框架创建的线程都会自动加上 @autoreleasepool
        Xml_Sw:@涂耀辉 在这里都能看见大神
      • TerryD:有个问题请教下:就是如果子线程里没有显示创建runloop ,也没有手动创建autoreleasepool,那么需要自动释放的对象加入hotpage 后什么时候释放,还有由哪个线程负责释放啊
        Joy___:@SimpleWorld :stuck_out_tongue_winking_eye:大概了解就行
        TerryD:@Joy___ 明白了,我也觉得是这样,只是c语言基础不牢,看那一坨源码是在:confounded:
        Joy___:我觉得吧,如果子线程没有创建 runloop,则是执行完操作就退出了,那么子线程销毁的时候还是由 pool 对对象进行释放
      • Rainqin:博主在上海网易吗?
        Joy___:@Rainqin 北京
      • KylinRoc:膜拜架构 Joy :+1:
      • DamonYJ:架构joy,膜拜
        Joy___:@DamonYJ :wink:
      • 大兵布莱恩特:个人水平不一样,我是一年工作经验用三年,还是初中级水平。
        大兵布莱恩特:@Joy___ :grin:一般 是要努力提升技术了。
        Joy___:@大兵布莱恩特 你也很棒:innocent:
      • Rainqin:工作两三年就是高级了?
        Joy___:@Rainqin :wink: 可以的
        Rainqin:呵呵,三年连门都没入
        Joy___:三年的高级挺多的吧 :stuck_out_tongue_winking_eye:
      • 折腾范儿_味精:等微博转发
        Joy___:@折腾范儿_味精 求转发 哈哈

      本文标题:各个线程 Autorelease 对象的内存管理

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