美文网首页iOS相关技术实现iOS 一般知识点『ios』进阶
IOS-AutorealeasePool 解决上次面试留下的坑

IOS-AutorealeasePool 解决上次面试留下的坑

作者: butterflyer | 来源:发表于2018-07-20 15:52 被阅读54次

    这篇算是解决以前留下的坑吧,这块之前看过不少东西,但是看过也只是看过没有正儿八经的研究过,最直接的就是上次去优信面试被问的贼尴尬。
    先扔个问题:

    第一个
        for (int i = 0; i < 100000000; i++) {
                UIImage *image = [UIImage imageNamed:@"logo"];
                [self.muarr addObject:image];
        }
     第二个  
        for (int i = 0; i < 100000000; i++) {
            @autoreleasepool{
                UIImage *image = [UIImage imageNamed:@"logo"];
                [self.muarr addObject:image];
            }
        }
    

    第一个跟第二个的区别是什么?内存会发生什么变化?
    实际测试结果,第一个跑完占用的内存是第二个的两倍。
    那为什么呢?
    答案在这里,加了autoreleasepool后,每当一个循环跑完,里面的临时变量image就会被释放掉,所以跑完内存会小很多。
    剩下的就剩下刨根问底了,走起吧!

    autoreleasepool{}的本地函数是什么样的?

    int main(int argc, const char * argv[]) {
    {
        void * atautoreleasepoolobj = objc_autoreleasePoolPush();
        // do whatever you want
        objc_autoreleasePoolPop(atautoreleasepoolobj);
    }
    return 0;
    }
    
    void *objc_autoreleasePoolPush(void) {
        return AutoreleasePoolPage::push();
    }
    
    void objc_autoreleasePoolPop(void *ctxt) {
        AutoreleasePoolPage::pop(ctxt);
    }
    
    

    这就是autoreleasepool函数原来的面目,两部,push 和 release操作。
    然后我注意到了这个 AutoreleasePoolPage,那么这个的构成是什么呢?

    class AutoreleasePoolPage {
        magic_t const magic;
        id *next;
        pthread_t const thread;
        AutoreleasePoolPage * const parent;
        AutoreleasePoolPage *child;
        uint32_t const depth;
        uint32_t hiwat;
    };
      magic是用于对当前  autoreleasepoolpage的完整性的校验,
      thread是当前所在线程
    

    每个自动释放池都由好多个autoreleasepoolpage组成的

    image.png
    从别人那搞的图片看看吧! 其中parent 和 child 就很好理解了,这两个就是构造双向链表的指针。

    说了这么多还是看下push和 pop是怎么实现的吧。

    void *objc_autoreleasePoolPush(void) {
        return AutoreleasePoolPage::push();
    }
    static inline void *push() {
       return autoreleaseFast(POOL_SENTINEL);
    }
    static inline id *autoreleaseFast(id obj)
    {
       AutoreleasePoolPage *page = hotPage();
       if (page && !page->full()) {
           return page->add(obj);
       } else if (page) {
           return autoreleaseFullPage(obj, page);
       } else {
           return autoreleaseNoPage(obj);
       }
    }
    
    static inline void pop(void *token) {
        AutoreleasePoolPage *page = pageForPointer(token);
        id *stop = (id *)token;
    
        page->releaseUntil(stop);
    
        if (page->child) {
            if (page->lessThanHalfFull()) {
                page->child->kill();
            } else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
    

    上面的add过程说白了就是压栈的过程。每当对象调用autorelease方法的时候,都会将对象加入到* AutoreleasePoolPage*栈中。
    调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息

    autoreleasepool什么时候释放?

    我最开始的理解也是在括号结束之后才会去释放,但是忘了一点,runloop,
    系统在每个runloop中都会加入 push 和 pop的监听,。所以肯定是在runloop结束的时候。

    通过查阅资料看到下面这句话

    App启动后,苹果在主线程 RunLoop 里注册了两个 Observer,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()。
    
    第一个 Observer 监视的事件是 Entry(即将进入Loop),其回调内会调用 _objc_autoreleasePoolPush() 创建自动释放池。其 order 是-2147483647,优先级最高,保证创建释放池发生在其他所有回调之前。
    
    第二个 Observer 监视了两个事件: BeforeWaiting(准备进入休眠) 时调用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 释放旧的池并创建新池;Exit(即将退出Loop) 时调用 _objc_autoreleasePoolPop() 来释放自动释放池。这个 Observer 的 order 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
    

    所以 _objc_autoreleasePoolPush 和 _objc_autoreleasePoolPop 的优先级肯定是最高的,以保证能正确的push和pop

    看完上面不知道能理解多少,对于博客还是写的有点乱。
    看看下面这个问题

    - (void)viewDidLoad {
        [super viewDidLoad];
        
        //Init
        _memoryUsageList1 = [NSMutableArray new];
        
            //创建一个串行队列
        dispatch_queue_t serialQueue = dispatch_queue_create("test.autoreleasepool", DISPATCH_QUEUE_SERIAL);
        
        __block NSString *strTest;
        dispatch_sync(serialQueue, ^{
                for (int i = 0; i < kIterationCount; i++) {
                    @autoreleasepool { 
                        NSNumber *num = [NSNumber numberWithInt:i];  // 1
                        NSString *str = [NSString stringWithFormat:@"%d ", i];  // 2
                        //Use num and str...whatever...
                        strTest = [NSString stringWithFormat:@"%@%@", num, str];  // 3
                        if (i % kStep == 0) {
                            [_memoryUsageList1 addObject:@(getMemoryUsage())];  // getM方法是获取内存的函数
                            NSLog(@"000----%f", getMemoryUsage());
                        }
                    }
                }
        });
    }  // 4
    

    看上面 1 2 都是临时变量,所以在一次循环之后就会被释放掉。
    这是我刚开始的理解(而strTest 的作用域是viewdidload,所以每次循环,strTest的指针都指向了一个新的对象,但是原来的对象没有被释放掉,所以内存就会一直增加。)感觉有不对的地方。
    然后我看到这句话

    自己创建的对象:使用 alloc new copy mutablecopy 以及他们的驼峰变形 allocObject newObject copyObject mutablecopyObject。这八种创建的才是自己创建的对象。
    
         不是自己创建的:除去以上八中都不是自己创建的。
    
         autoreleasepool:只有非自己创建的对象才会注册到离该对象最近的autoreleasepool中去。
    

    也就是说 [NSString stringWithFormat:@"%@%@", num, str] 不会自动加入到自动释放池中,这时候就体现了我们自己加的这个autoreleasepool的作用了,强行给他加了一个作用域,所以每当一个autoreleasepool结束的时候, [NSString stringWithFormat:@"%@%@", num, str] 就会被释放掉。

    借鉴地址AutorealeasePool
    借鉴地址 runloop

    相关文章

      网友评论

        本文标题:IOS-AutorealeasePool 解决上次面试留下的坑

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