『ios』-AutorealeasePool 解决上次面试留下的坑

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

第一个
    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组成的

『ios』-AutorealeasePool 解决上次面试留下的坑_第1张图片
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自习室欢迎进入,一起学习一起进步。

『ios』-AutorealeasePool 解决上次面试留下的坑_第2张图片
IMG_7291.JPG

你可能感兴趣的:(『ios』-AutorealeasePool 解决上次面试留下的坑)