iOS小集

AutoReleasePool

2020-04-09  本文已影响0人  开了那么

内存管理一直是Objective-C 的重点,在MRC环境下,通过调用[obj autorelease]来延迟内存的释放,在现在ARC环境下,我们甚至可以不需要知道Autorelease就能很好的管理内存。但是了解 Objective-C 的内存管理机制仍然是十分必要的,看看编译器帮助我们怎么来管理内存。

AutoreleasePool简介

AutoreleasePool:自动释放池是 Objective-C 开发中的一种自动内存回收管理的机制,为了替代开发人员手动管理内存,实质上是使用编译器在适当的位置插入release、autorelease等内存释放操作。当对象调用 autorelease方法后会被放到自动释放池中延迟释放时机,当缓存池需要清除dealloc时,会向这些 Autoreleased对象做 release 释放操作。

@autoreleasepool 到底是什么?

main.m 文件中的内容是这样的:

int main(int argc, char * argv[]) {
    @autoreleasepool {
        return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
    }
}

在这个 @autoreleasepool 中,所有的事件、消息全部交给了 UIApplication 来处理.
需要注意的是:整个 iOS 的应用都是包含在一个自动释放池 block 中的。
我们通过一下方法在终端中运行,编译main.m文件,转译成c++文件看一下

xcrun  -sdk  iphoneos  clang  -arch  arm64  -rewrite-objc  main.m  -o  main.cpp

转译后上面的mian.m中的代码为下面

int main(int argc, char * argv[]) {
    NSString * appDelegateClassName;
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
    }
    return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}

这时会存在一个__AtAutoreleasePool __autoreleasepool; 我们在main.cpp
中查找会发现一个结构体

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};

这个结构体会在初始化时调用 objc_autoreleasePoolPush() 方法,会在析构时调用 objc_autoreleasePoolPop 方法。
这是我们可以再次重新整理main 函数的工作其实就是

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

AutoreleasePool 是什么

我们现在可以通过苹果开源代码看看AutoreleasePool 到底是一什么样的东西

在源码中我们可以找到NSObject.mm文件,我们可以找到两个方法

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

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

上面的方法看上去是对 AutoreleasePoolPage 对应静态方法 push 和 pop 的封装。
首先我们来了解一下AutoreleasePoolPage 的结构


image.png

每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起,

image.png

如图所示:
parent 和 child 就是用来构造双向链表的指针。
next 指向了下一个为空的内存地址,如果 next 指向的地址加入一个 object,它就会如下图所示移动到下一个为空的内存地址中:

cold 与 hot

我们模拟一个程序,通过一个私有方法 _objc_autoreleasePoolPrint 打印一下 AutoreleasePoolPage

int main(int argc, const char * argv[]) {
    @autoreleasepool { //  r1 = push()
        MJPerson *p1 = [[[MJPerson alloc] init] autorelease];
        MJPerson *p2 = [[[MJPerson alloc] init] autorelease];
        @autoreleasepool { // r2 = push()
            for (int i = 0; i < 600; i++) {
                MJPerson *p3 = [[[MJPerson alloc] init] autorelease];
            }
            @autoreleasepool { // r3 = push()
                MJPerson *p4 = [[[MJPerson alloc] init] autorelease];
                _objc_autoreleasePoolPrint();
            } // pop(r3)
        } // pop(r2)
    } // pop(r1)
    return 0;
}
image.png
image.png

通过图片可以了解,第一个AutoreleasePoolPage 状态是full 然后系统默认会把它状态变为cold ,第二个page 是正在使用的page 状态为hot

POOL_BOUNDARY

当调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址
调用pop方法时传入一个POOL_BOUNDARY的内存地址,会从最后一个入栈的对象开始发送release消息,直到遇到这个POOL_BOUNDARY停止,

objc_autoreleasePoolPush 方法

接下来我们来了解一下push 的整个流程
首先调用push 方法, 调用push 方法的时候,会检测当前的AutoreleasePoolPage 是否存在空间存储 ,如果没有新建一个调用autoreleaseNewPage 方法,如果内存足够调用 autoreleaseFast方法,直接存入

 objc_autoreleasePoolPush(void)
     {
           return AutoreleasePoolPage::push(); 
     }
   static inline void *push() 
    {
        id *dest;
        if (DebugPoolAllocation) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);   //如果没有AutoreleasePoolPage  new 一个新的,传入POOL_BOUNDARY
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);  //如果当前的AutoreleasePoolPage还有内存,存入当前page
        }
        assert(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

//新建一个page 
   id *autoreleaseNewPage(id obj)
     {
        AutoreleasePoolPage *page = hotPage();   调用hotpage方法  
        if (page) return autoreleaseFullPage(obj, page);  hotPage 返回的page 调用autoreleaseFullPage
        else return autoreleaseNoPage(obj);
     }

    static inline AutoreleasePoolPage *hotPage() 
    {
        AutoreleasePoolPage *result = (AutoreleasePoolPage *)
            tls_get_direct(key);
        if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil;
        if (result) result->fastcheck();
        return result;
    }

    id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
    {
        // The hot page is full. 
        // Step to the next non-full page, adding a new page if necessary.
        // Then add the object to that page.
        assert(page == hotPage());
        assert(page->full()  ||  DebugPoolAllocation);

        do {
            if (page->child) page = page->child;
            else page = new AutoreleasePoolPage(page);
        } while (page->full());

        setHotPage(page);
        return page->add(obj);  //加入
    }

   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);
        }
    }

最后的都会调用 page->add(obj) 将对象添加到自动释放池中。

objc_autoreleasePoolPop 方法

void objc_autoreleasePoolPop(void *ctxt) {
    AutoreleasePoolPage::pop(ctxt);
}
static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
            // Popping the top-level placeholder pool.
            if (hotPage()) {
                // Pool was used. Pop its contents normally.
                // Pool pages remain allocated for re-use as usual.
                pop(coldPage()->begin());
            } else {
                // Pool was never used. Clear the placeholder.
                setHotPage(nil);
            }
            return;
        }

        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);  //调用releaseUntil 方法 

        // memory: delete empty children
        if (DebugPoolAllocation  &&  page->empty()) {
            // special case: delete everything during page-per-pool debugging
            AutoreleasePoolPage *parent = page->parent;
            page->kill();
            setHotPage(parent);
        } else if (DebugMissingPools  &&  page->empty()  &&  !page->parent) {
            // special case: delete everything for pop(top) 
            // when debugging missing autorelease pools
            page->kill();
            setHotPage(nil);
        } 
        else if (page->child) {
            // hysteresis: keep one empty child if page is more than half full
            if (page->lessThanHalfFull()) {
                page->child->kill();
            }
            else if (page->child->child) {
                page->child->child->kill();
            }
        }
    }
//while循环,
 void releaseUntil(id *stop) 
    {
        // Not recursive: we don't want to blow out the stack 
        // if a thread accumulates a stupendous amount of garbage
        
        while (this->next != stop) {
            // Restart from hotPage() every time, in case -release 
            // autoreleased more objects
            AutoreleasePoolPage *page = hotPage();

            // fixme I think this `while` can be `if`, but I can't prove it
            while (page->empty()) {
                page = page->parent;
                setHotPage(page);
            }

            page->unprotect();
            id obj = *--page->next;
            memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
            page->protect();

            if (obj != POOL_BOUNDARY) {//如果不是 POOL_BOUNDARY,就调用release 操作,引用计数减-1
                objc_release(obj);
            }
        }

使用 pageForPointer 获取当前 token 所在的 AutoreleasePoolPage
调用 releaseUntil 方法释放栈中的对象,直到 stop
调用 child 的 kill 方法

autorelease 方法

我们已经对自动释放池生命周期有一个比较好的了解,最后需要了解的话题就是 autorelease 方法的实现,先来看一下方法的调用栈:

- [NSObject autorelease]
└── id objc_object::rootAutorelease()
    └── id objc_object::rootAutorelease2()
        └── static id AutoreleasePoolPage::autorelease(id obj)
            └── static id AutoreleasePoolPage::autoreleaseFast(id obj)
                ├── id *add(id obj)
                ├── static id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page)
                │   ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                │   └── id *add(id obj)
                └── static id *autoreleaseNoPage(id obj)
                    ├── AutoreleasePoolPage(AutoreleasePoolPage *newParent)
                    └── id *add(id obj)

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

小结

整个自动释放池 AutoreleasePool 的实现以及 autorelease 方法都已经分析完了,我们再来回顾一下文章中的一些内容:

自动释放池是由 AutoreleasePoolPage 以双向链表的方式实现的
当对象调用 autorelease 方法时,会将对象加入 AutoreleasePoolPage 的栈中
调用 AutoreleasePoolPage::pop 方法会向栈中的对象发送 release 消息

接下来还有一个常见的面试题:
AutoReleasePool的释放时机(原理)
例如:MJPerson *person = [[ [MJPerson alloc] init] autorelease];
其实这是再考runloop 的只是 我们来打印[NSRunLoop mainRunLoop]);

进入程序是调用  activities = 0x1 对应runloop的状态 kCFRunLoopEntry
//    "<CFRunLoopObserver 0x6000028a8320 [0x7fff80617cb0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x6000017ce100 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fab6c804038>\n)}}",
//
退出是调用, activities = 0xa0, = 160 =  kCFRunLoopBeforeWaiting + kCFRunLoopExit
//    "<CFRunLoopObserver 0x6000028a83c0 [0x7fff80617cb0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x6000017ce100 [0x7fff80617cb0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7fab6c804038>\n)}}"
    

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    kCFRunLoopEntry = (1UL << 0),   1
    kCFRunLoopBeforeTimers = (1UL << 1), 2
    kCFRunLoopBeforeSources = (1UL << 2),  4
    kCFRunLoopBeforeWaiting = (1UL << 5),  32
    kCFRunLoopAfterWaiting = (1UL << 6),   64
    kCFRunLoopExit = (1UL << 7),           128
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};

当runloop 调用kCFRunLoopEntry 会调用一次push 操作
当runloop 调用kCFRunLoopBeforeWaiting 会调用一次pop 操作,在调用一次push 操作
当runloop 调用kCFRunLoopExit 会调用一次pop 操作
所以AutoReleasePool 释放是在某次runloop 调用 kCFRunLoopExit 的时候

参考文章:
自动释放池的前世今生 ---- 深入解析 autoreleasepool

上一篇下一篇

猜你喜欢

热点阅读