OC 学习记录随笔 之AutoreleasePool

总资料
全是随笔 笔记 与 学习资料。没有规律。

自动释放池

数据结构

  • class AutoreleasePoolPage : private AutoreleasePoolPageData 继承与AutoreleasePoolPageData, 经过综合后的主要结构为:
class AutoreleasePoolPage
{
    
    
    
    magic_t const magic;
    __unsafe_unretained id *next; //能够存储的 obj 的位置的 指针
    pthread_t const thread; //线程
    AutoreleasePoolPage * const parent; //前一page
    AutoreleasePoolPage *child;// 后一 page
    uint32_t const depth; 
    uint32_t hiwat;
    
    static void * operator new(size_t size) {
    
    
        return malloc_zone_memalign(malloc_default_zone(), SIZE, SIZE);
    }
    static void operator delete(void * p) {
    
    
        return free(p);
    }
}

通过上面可以看出,

  • AutoreleasePoolPage 是new 出来的,是存放在堆区的。
  • page 是一个双向链表
  • magic 用来校验 page的结构是否完整
  • next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向 begin() ;
  • thread 指向当前线程。说明了,AutoreleasePoolPage和线程一一对应的。
  • parent、child , 前后链表节点
  • depth 深度从0开始,代表了存储的数量
  • begin 和 end 之间代表 所有的存储空间,每一张page有4096个字节大小,减去头部的字节, 剩余的就是可以存储的字节大小,当页full 填满后,会新开辟一页来继续存储

在这里插入图片描述

初步解析

main.m 的源代码代码:

int main(int argc, char * argv[]) {
    
    
    NSString * appDelegateClassName;
    @autoreleasepool {
    
    
        
        // Setup code that might create autoreleased objects goes here.
        appDelegateClassName = NSStringFromClass([AppDelegate class]);
    }
    return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}

我们将这个文件通过以下命令
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main-arm63.cpp
来转换成cpp文件,然后节选出关键代码为:


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 为C++的结构体。
extern "C" __declspec(dllimport) void * objc_autoreleasePoolPush(void);
extern "C" __declspec(dllimport) void objc_autoreleasePoolPop(void *);

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

通过上面转换的代码可以看出。

  • @autoreleasepool 包含的括号里面,会自动在最开始的时候,初始化一个 __AtAutoreleasePool 的C++的局部结构体变量。

  • 然后在初始化这个结构体的时候,会在初始化方法里面,调用C的objc_autoreleasePoolPush()方法,将返回的void *atautoreleasepoolobj指针存起来。

  • 然后在方法的大括号结束的时候,C++的局部变量会由于出栈导致 被释放,进入自己的析构方法,会触发 objc_autoreleasePoolPop(atautoreleasepoolobj)方法。

  • 在push的时候插入哨兵对象nil,然后返回哨兵对象的地址。 在析构的时候析构到 保存起来的地址为止

继续查看 objc4的开源代码

追踪objc_autoreleasePoolPush() 方法

void * objc_autoreleasePoolPush(void)
{
    
    
    return AutoreleasePoolPage::push();
}
#   define POOL_BOUNDARY nil
static inline void *push()
{
    
    
    id *dest;
    if (slowpath(DebugPoolAllocation)) {
    
    
        // Each autorelease pool starts on a new pool page.
        //初始化一个page 
        //POOL_BOUNDARY 其实就是最开始的标志位的 nil 指针
        dest = autoreleaseNewPage(POOL_BOUNDARY);
    } else {
    
    
        dest = autoreleaseFast(POOL_BOUNDARY);
    }
    ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
    return dest;
}
static __attribute__((noinline))  id *autoreleaseNewPage(id obj)
{
    
    
    AutoreleasePoolPage *page = hotPage(); //hot page 表示当前激活的page
    if (page) return autoreleaseFullPage(obj, page);
    else return autoreleaseNoPage(obj);
}
static inline id *autoreleaseFast(id obj)
{
    
    
    AutoreleasePoolPage *page = hotPage();
    if (page && !page->full()) {
    
    
        return page->add(obj);  //将obj 加入到page 里面
    } else if (page) {
    
    
        return autoreleaseFullPage(obj, page);
    } else {
    
    
        return autoreleaseNoPage(obj);
    }
}

// 将 obj 真正加入到 page 的具体位置
id *add(id obj)
{
    
    
    ASSERT(!full());
    unprotect();
    id *ret;
    
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  ...... 忽略
#endif
    ret = next;  // faster than `return next-1` because of aliasing
    *next++ = obj;
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    // Make sure obj fits in the bits available for it
    ASSERT(((AutoreleasePoolEntry *)ret)->ptr == (uintptr_t)obj);
#endif
done:
    protect();
    return ret;
}

追踪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.
        page = hotPage();
        if (!page) {
    
    
            // Pool was never used. Clear the placeholder.
            return setHotPage(nil);
        }
        // Pool was used. Pop its contents normally.
        // Pool pages remain allocated for re-use as usual.
        page = coldPage();
        token = page->begin();
    } else {
    
    
        page = pageForPointer(token);
    }

    stop = (id *)token;
    if (*stop != POOL_BOUNDARY) {
    
     //不是最开始的 标志位 nil
        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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) {
    
    
        return popPageDebug(token, page, stop);
    }
    
    //pop page 的 token 对象
    return popPage<false>(token, page, stop);
}


template<bool allowDebug>
static void
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
    
    
    if (allowDebug && PrintPoolHiwat) printHiwat();
    
    //page 开始释放,直到遇到  stop 对象
    page->releaseUntil(stop);
    
    // 后面的都为 删除后,处理 page的操作
    // memory: delete empty children
    if (allowDebug && DebugPoolAllocation  &&  page->empty()) {
    
    
        // special case: delete everything during page-per-pool debugging
        AutoreleasePoolPage *parent = page->parent;
        page->kill();
        setHotPage(parent);
    } else if (allowDebug && 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();
        }
    }
}

//挨个释放,直到遇到  stop 为止
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();
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
        AutoreleasePoolEntry* entry = (AutoreleasePoolEntry*) --page->next;

        // create an obj with the zeroed out top byte and release that
        id obj = (id)entry->ptr;
        int count = (int)entry->count;  // grab these before memset
#else
        id obj = *--page->next;
#endif
        memset((void*)page->next, SCRIBBLE, sizeof(*page->next));
        page->protect();

        if (obj != POOL_BOUNDARY) {
    
    
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            // release count+1 times since it is count of the additional
            // autoreleases beyond the first one
            // 真正进行释放操作
            for (int i = 0; i < count + 1; i++) {
    
    
                objc_release(obj);
            }
#else
            objc_release(obj);
#endif
        }
    }

    setHotPage(this);

#if DEBUG
    // we expect any children to be completely empty
    for (AutoreleasePoolPage *page = child; page; page = page->child) {
    
    
        ASSERT(page->empty());
    }
#endif
}

被 自动释放池包裹的对象释放

@autoreleasepool { } 包裹的里面,对象在初始化的时候 会自动调用 - (id)autorelease 方法, 所以能在 pool::pop()的时候一并被释放。

源码:

- (id)autorelease {
    
    
    return _objc_rootAutorelease(self);
}

id
_objc_rootAutorelease(id obj)
{
    
    
    ASSERT(obj);
    return obj->rootAutorelease();
}

inline id
objc_object::rootAutorelease()
{
    
    
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
    
    
    ASSERT(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}
static inline id autorelease(id obj)
{
    
    
    ASSERT(!obj->isTaggedPointerOrNil());
    id *dest __unused = autoreleaseFast(obj);
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  (id)((AutoreleasePoolEntry *)dest)->ptr == obj);
#else
    ASSERT(!dest  ||  dest == EMPTY_POOL_PLACEHOLDER  ||  *dest == obj);
#endif
    return 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);
    }
}

我们可以在上面看到 最后也进入了static inline id *autoreleaseFast(id obj) 方法,相当于进入了上一小节里面的 push 方法。 所以会将当前创建的 obj 对象加入到relase page 里面去。

没有被直接包裹的对象

在这里插入图片描述

如下,没有被@autoreleasepool { } 直接包裹的对象,会在什么时候释放呢?

- (void)viewDidLoad {
    
    
    [super viewDidLoad];
    
    MYPerson *person = [[MYPerson alloc] init];
    
}

其实并不是在 } 这里进行释放,而是和RunLoop有关。
我们修改打印一下代码。
NSLog(@"%@", [NSRunLoop currentRunLoop]);
直接调用私有方法, 可以打印出runloop (自己在Xcode13.2.1 打印后并未找到下面的 observe。 有知道原因的么?)

<CFRunLoopObserver 0x600001d145a0 [0x7fff80617cb0]>{
    
    
valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54),
 context = <CFArray 0x6000022454a0 [0x7fff80617cb0]>{
    
    
type = mutable-small, count = 1, values = (
    0 : <0x7fabae001038>
)}}
<CFRunLoopObserver 0x600001d14640 [0x7fff80617cb0]>{
    
    
valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, 
callout = _wrapRunLoopWithAutoreleasePoolHandler (0x7fff4808bf54), context = <CFArray 0x6000022454a0 [0x7fff80617cb0]>{
    
    
type = mutable-small, count = 1, values = (
    0 : <0x7fabae001038>
)}}
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    
    
    kCFRunLoopEntry = (1UL << 0),
    kCFRunLoopBeforeTimers = (1UL << 1),
    kCFRunLoopBeforeSources = (1UL << 2),
    kCFRunLoopBeforeWaiting = (1UL << 5),
    kCFRunLoopAfterWaiting = (1UL << 6),
    kCFRunLoopExit = (1UL << 7),
    kCFRunLoopAllActivities = 0x0FFFFFFFU
};
  • activities = 0x1 == kCFRunLoopEntry, 代表runloop 进入的时候

  • activities = 0xa0 == kCFRunLoopExit + kCFRunLoopBeforeWaiting, 代表开始等待和退出runloop

  • activities = 0x1 会在包含自己的自动释放池(如果没有手动包含,那么就会说main文件的自动释放池)里面, 进行Push操作,将哨兵对象(边界对象)POOL_BOUNDARY 压入栈中。

  • activities = 0xa0callout 方法,会先进行 pop, 然后在进行push一个哨兵对象。exit的时候只会进行pop。 也就是说pop的时候会把上个循环push后面的所有能自动释放的对象都进行释放。

  • 最终顺序为:Entry --> push --> beforeWait —> pop --> push --> …多个beforewait… —> exit --> pop。 保证push 和 pop成对出现。 在每次循环的时候清空 能够释放的对象。

综述:

  • @autoreleasepool { } 能够嵌套使用。可以存在N个page, 每个page 4096=4K个大小,是个双向链表
  • @autoreleasepool 是c++的结构体。
    • 在初始化的时候,会创建page,并将POOL_BOUNDARY压入push栈中作为标识
    • {} 中间的 调用了-autorelase() 方法的对象,会push到栈中,位于POOL_BOUNDARY之后
    • } 的时候会调用析构函数,会pop对象,直到遇到POOL_BOUNDARY标志为止。
  • 和runLoop的关系为
    • runloop 每次循环都会调用 pushpop 方法。
    • 每次循环都会释放能够释放的对象
    • 手动添加了@autoreleasepool 的情况就是综合上面 的case进行释放。主要用于for循环或者其他内部代码包含有大量内存的,能够保证及时释放。

猜你喜欢

转载自blog.csdn.net/goldWave01/article/details/122387064
今日推荐