美文网首页
Autorelease 原理

Autorelease 原理

作者: iVikings | 来源:发表于2020-05-08 16:30 被阅读0次

AutoreleasePoolPage

我们新建一个 Command Line Tool 项目,查看 main.m 文件代码如下:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
    }
    return 0;
}

使用 clang 将 main.m 转化成 C++ 代码

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

可以从 main.cpp 中看到如下代码:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

    }
    return 0;
}

由此可见,ARC下,我们使用 @autoreleasepool{ } 来使用一个 AutoreleasePool,随后编译器将其改写成下面的样子:

void *context = objc_autoreleasePoolPush();
// { }中的代码
objc_autoreleasePoolPop(context);

而这两个函数都是对 AutoreleasePoolPage 的简单封装,所以自动释放池的主要底层数据结构是:__AtAutoreleasePool、AutoreleasePoolPage

调用了 autorelease的对象 最终都是通过 AutoreleasePoolPage 对象来管理的

底层源码

obj4 源码:https://opensource.apple.com/tarballs/objc4/

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
    magic_t const magic;
    __unsafe_unretained id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;

    AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
        : magic(), next(_next), thread(_thread),
          parent(_parent), child(nil),
          depth(_depth), hiwat(_hiwat)
    {
    }
};

AutoreleasePoolPage 是一个 C++ 实现的类

  • AutoreleasePool 并没有单独的结构,而是由若干个AutoreleasePoolPage双向链表 的形式组合而成(分别对应结构中的 parent指针child指针
  • AutoreleasePool 是按线程一一对应的(结构中的 thread指针 指向当前线程)
  • AutoreleasePoolPage 每个对象会开辟 4096字节 内存(也就是虚拟内存一页的大小),除了上面的实例变量所占空间(7 * 8 = 56 字节),剩下的空间(4040字节) 全部用来储存autorelease对象的地址
  • id *next 指针作为游标指向栈顶最新 add 进来的autorelease对象的下一个位置
  • 一个AutoreleasePoolPage 的空间被占满时,会新建一个AutoreleasePoolPage对象,形成双向链表,后面加入的autorelease对象 会添加在新的 AutoreleasePoolPage 当中

AutoreleasePoolPage 结构:

AutoreleasePoolPage 结构
  • 每当调用一次 objc_autoreleasePoolPush 方法, runtime 会向当前的 AutoreleasePoolPage 入栈一个 POOL_BOUNDARY(哨兵对象,值为 nil),并且返回其存放的内存地址

  • autorelease 对象地址依次入栈到 page 中

  • 当调用 objc_autoreleasePoolPop 时,会传入一个POOL_BOUNDARY (哨兵对象) 的内存地址作为入参

    1. 根据传入的哨兵对象地址找到哨兵对象所处的 page
    2. 在当前page中,将晚于哨兵对象插入的所有 autorelease对象 都发送一次 release 消息,并向回移动 next指针 到正确位置(从最新加入的对象一直向前清理,可以向前跨越若干个page,直到哨兵所在的page)
  • 嵌套的 AutoreleasePool 也是当调用 objc_autoreleasePoolPush 方法时, 会继续向当前的 AutoreleasePoolPage 后面入栈一个 POOL_BOUNDARY(哨兵对象)进行标记,并不影响外层的 AutoreleasePool


私有API 窥探 AutoreleasePoolPage 存储结构

我们还可以通过 私有API extern void _objc_autoreleasePoolPrint(void); 查看 AutoreleasePoolPage 存储结构;

项目改成 MRC 进行调试:
Objective-C Automatic Refrrence Counting 设置为 NO

具体代码如下:

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        WBPerson *p1 = [[[WBPerson alloc] init] autorelease];
        WBPerson *p2 = [[[WBPerson alloc] init] autorelease];
        
        @autoreleasepool {
            WBPerson *p3 = [[[WBPerson alloc] init] autorelease];
            
            @autoreleasepool {
                WBPerson *p4 = [[[WBPerson alloc] init] autorelease];
                
                _objc_autoreleasePoolPrint();
            }
        }
    }
    
    return 0;
}

控制台可以看到如下输出:

objc[35517]: ##############
objc[35517]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[35517]: 7 releases pending.
objc[35517]: [0x10080a000]  ................  PAGE  (hot) (cold)
objc[35517]: [0x10080a038]  ################  POOL 0x10080a038
objc[35517]: [0x10080a040]       0x100510bd0  WBPerson
objc[35517]: [0x10080a048]       0x10050fd10  WBPerson
objc[35517]: [0x10080a050]  ################  POOL 0x10080a050
objc[35517]: [0x10080a058]       0x10050fe30  WBPerson
objc[35517]: [0x10080a060]  ################  POOL 0x10080a060
objc[35517]: [0x10080a068]       0x10050fc00  WBPerson
objc[35517]: ##############

因为一个 AutoreleasePoolPage对象占用 4096字节内存,内部成员变量占用 56 字节(7 * 8 = 56),所以以下三个地址(嵌套三个 @autoreleasepool)是 POOL_BOUNDARY (哨兵对象) 的内存地址(一个哨兵对象占2个字节)
objc[35517]: [0x10080a038] ################ POOL 0x10080a038
objc[35517]: [0x10080a050] ################ POOL 0x10080a050
objc[35517]: [0x10080a060] ################ POOL 0x10080a060

参考:
1、小码哥 iOS 底层原理
2、黑幕背后的 Autorelease

相关文章

  • iOS autorelease与自动释放池

    autorelease、autorelease pool以及原理 autorelease与MRC、ARC auto...

  • Autorelease原理

    ARC下,我们使用@autoreleasepool{}来使用一个AutoreleasePool,随后编译器将其改写...

  • Autorelease 原理

    AutoreleasePoolPage 我们新建一个 Command Line Tool 项目,查看 main.m...

  • autorelease原理

    对象调用autorelease会在它所在的@autoreleasepool{}大括号结束的时候调用release释...

  • OC-内存管理autorelease原理

    先提几个问题,然后带着问题一起来学习autorelease的原理 加入autorelease pool的对象释放时...

  • iOS内存管理-深入解析自动释放池

    主要内容: AutoreleasePool简介 AutoreleasePool底层原理 Autorelease与N...

  • Autorelease原理总结

    Autorelease对象什么时候释放? 这个问题拿来做面试题,问过很多人,没有几个能答对的。很多答案都是“当前作...

  • Autorelease实现原理

    内存管理一直是学习 Objective-C 的重点和难点之一,尽管现在已经是 ARC 时代了,但是了解 Objec...

  • iOS -autorelease原理

    Autorelease机制是iOS开发者管理对象内存的好伙伴,MRC中,调用[obj autorelease] 来...

  • autorelease 的原理

    autorelease 的内部使用的双向链表class AutoreleasePoolPage{// 内存是409...

网友评论

      本文标题:Autorelease 原理

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