iOS底层探索--内存管理(下)

  • iOS内存管理(上)简单的说了下retain、release和dealloc。不过关于内存管理还有个比较重要的东西autoreleasepool,也是兄弟们常说的自动释放池

作为一个开发者,有一个学习的氛围跟一个交流圈子特别重要,这是一个我的iOS交流群:196800191,加群密码:112233,不管你是小白还是大牛欢迎入驻 ,分享BAT,阿里面试题、面试经验,讨论技术, 大家一起交流学习成长!

1. 自动释放池autoreleasepool

1.1. autoreleasepool 结构分析

  1. 兄弟们在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);
}
  1. 我们首先通过clang看下他的结构是啥样子的
    xcrun -sdk iphonesimulator clang -rewrite-objc main.m

  2. 得到main.cpp文件,定位位置

  3. 我们看下他的结构

他就是个结构体,有自己的构造和析构

1.2. autoreleasepool 压栈追踪

  1. 我们看下构造函数
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  1. 然后找到他的压栈处理
void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}

这个时候,我们遇到了个AutoreleasePoolPage,到这了,那我们就看看这个的结构

1.2.1. 拓展 AutoreleasePoolPage

  1. 我追寻的过程
  2. 有个比较官方的名词解释,咱们先看一下
* magic 用来校验 AutoreleasePoolPage 的结构是否完整;

* next 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向begin() ;

* thread 指向当前线程;

* parent 指向父结点,第一个结点的 parent 值为 nil ;

* child 指向子结点,最后一个结点的 child 值为 nil ;

* depth 代表深度,从 0 开始,往后递增 1;

* hiwat 代表 high water mark 最大入栈数量标记

好吧,到了这一步,大家可能和我第一次过来一样,比较懵逼。。这个是干啥用呢,咋还有父节点和子节点

  1. 这个时候看下自动释放池实现的官方文档
/***********************************************************************
   Autorelease pool implementation

   A thread's autorelease pool is a stack of pointers.
   Each pointer is either an object to release, or POOL_BOUNDARY which is
      an autorelease pool boundary.
   A pool token is a pointer to the POOL_BOUNDARY for that pool. When
      the pool is popped, every object hotter than the sentinel is released.
   The stack is divided into a doubly-linked list of pages. Pages are added
      and deleted as necessary.
   Thread-local storage points to the hot page, where newly autoreleased
      objects are stored.
**********************************************************************/

  1. 我来翻译一波了,😆(展示下我的大白话)

一个线程的自动释放池 是一个栈 --> 由好多指针组成(说白了就是栈结构,所以有后面的出栈和压栈。)

一个池子里的标记指向POOL_BOUNDARY(边界/哨兵),当这个池子被释放的时候,比POOL_BOUNDARY 还hot的进行 release

这个栈 划分为 双向链接的页,页 在必要的时候进行增加或者删除

本地线程 存储的是 新进来的,并把它设成 hot page(聚焦页面)

这样的话,这个页结构就好懂了些。由于是双向链接的页,所有有了parent和child

1.3. autoreleasepool压栈实现

  1. 那我们看下压栈的处理
	static inline void *push() 
    {
        id *dest;
        if (slowpath(DebugPoolAllocation)) {
            // Each autorelease pool starts on a new pool page.
            dest = autoreleaseNewPage(POOL_BOUNDARY);
        } else {
            dest = autoreleaseFast(POOL_BOUNDARY);
        }
        ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
        return dest;
    }

每一个自动释放池 会在一个新页面上启动

  1. 我们追踪下
  2. 如果刚开始加
	//上面还有些乱起把遭的。。太长了,我就不沾了    
    // Install the first page.
        AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
        setHotPage(page);
        
        // Push a boundary on behalf of the previously-placeholder'd pool.
        if (pushExtraBoundary) {
            page->add(POOL_BOUNDARY);
        }
        
        // Push the requested object or pool.
        return page->add(obj);

简单来说就是。如果没有页面,就给他个。并设置成hotpage,第一个页面的话会给个 POOL_BOUNDARY,然后add

  1. 看下add代码
	id *add(id obj)
    {
        ASSERT(!full());
        unprotect();
        id *ret = next;  // faster than `return next-1` because of aliasing
        *next++ = obj;
        protect();
        return ret;
    }

这个就是他核心的push代码。(就是一步一步压,然后平移next指针)

  1. 如果页面满了
	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);
    }

由于它是双向链接结构的页。所以页面满了,会找下一个页。通过child,然后把最新也设置成hotpage,在add

  1. 补充

上面其实就是他的压栈。

补充知识:听说面试有人问到过:就是一个自动释放池只有一个哨兵/边界,一页最大容量是505,第一页是哨兵加上504(当然这个大家也可以通过源码知道)

1.4. autoreleasepool 出栈

  1. autoreleasepool的出栈其实就是压栈的反向操作。然后通过parent结点release
  2. 首先看下我们刚才的的出来的析构
//构造
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
//析构  参数是构造得出的对象
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  1. 兄弟们看下我的探究过程

我只是直接把找的核心代码,这些代码兄弟们可以去看看。会更加清晰。(我感觉有注释,人家写的代码又好,就不做过多的解释了,😆)

  1. 我们看下releaseUntil
    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) {
                objc_release(obj);
            }
        }

        setHotPage(this);

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



这就是objc源码的好处,注释让人很舒服。 总结下就是:这个就是通过parent寻找,然后next进行- -操作,
不是哨兵的话objc_release

好了,希望对大家有帮助吧。我又要加班了~~😿o(╥﹏╥)o
原文作者:小谷先森
原文地址:https://juejin.cn/post/6899718676585545742

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值