IMG_5801.JPG

  • 父类和子类也是双向链表结构
  • 自动释放器绑定在线程上
  • 一个自动释放池 一个哨兵对象
  • llvm中以alloc copy new mutablecopy命名的不会加入到自动释放池中,不受自动释放池的影响

    初探

  • 我们建一个工程,main.m,通过xcrun或者clang编译为main.cpp文件 ```objectivec

    import

int main(int argc, const char * argv[]) {

  1. @autoreleasepool {
  2. // insert code here... 开始压栈 ----------------------------
  3. NSLog(@"Hello, World!");
  4. } 作用域结束,开始出栈-------------------
  5. return 0;

}

  1. - 在编译后的.cpp文件中,该函数被编译为了如下代码
  2. - @autoreleasepool编译完,首先做了注释,然后多了__AtAutoreleasePool __autoreleasepool
  3. - 接着在.cpp查看__AtAutoreleasePool,是结构体,包含了构造函数和析构函数
  4. - **__AtAutoreleasePool __autoreleasepool 调用等于构造函数,出了作用域后调用其析构函数**
  5. - 因而在@autoreleasepool 作用域内,将对象进行压栈操作,操作作用域进行自动释放池的析构操作
  6. ```objectivec
  7. #pragma clang assume_nonnull end
  8. int main(int argc, const char * argv[]) {
  9. /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
  10. NSLog((NSString *)&__NSConstantStringImpl__var_folders_62_xk0q0gh50qsgfk5m4r5ynbb80000gn_T_main_9ae6df_mi_0);
  11. }
  12. return 0;
  13. }
  1. struct __AtAutoreleasePool {
  2. __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  3. ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  4. void * atautoreleasepoolobj;
  5. };
  • 断点调试,通过汇编查看,其调用了libobjc库的objc_autoreleasePoolPush和objc_autoreleasepoolPop

image.png

结构

  • libobjc库中,搜寻objc_autoreleasePoolPush,其调用了AutoreleasePoolPage::push()

    1. void *
    2. objc_autoreleasePoolPush(void)
    3. {
    4. return AutoreleasePoolPage::push();
    5. }

    AutoreleasePoolPageData

  • AutoreleasePoolPage继承于AutoreleasePoolPageData

    • AutoreleasePoolPageData是一个结构体,其中定义了最大链表数量16,存储的对象指针最多个数2^16 - 1, 另包含了以下成员变量
      • magic_t const magic (16字节:其内部static 静态变量不占内存) -> 用来校验AutoreleasePoolPage的完整性
      • __unsafe_unretained id *next (8字节) -> 指向最新添加的 autoreleased 对象的下一个位置,初始化时指向

begin()

  1. - pthread_t const thread 8字节) -> 指向当前线程
  2. - AutoreleasePoolPage * const parent 8字节)-> 指向父结点,第一个结点的 parent 值为 nil
  3. - AutoreleasePoolPage *child 8字节)-> 指向子结点,最后一个结点的 child 值为 nil
  4. - uint32_t const depth 4字节) -> 代表深度,从 0 开始,往后递增 1
  5. - uint32_t hiwat 4字节)-> 代表 high water mark 最大入栈数量标记
  1. struct AutoreleasePoolPageData
  2. {
  3. #if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
  4. struct AutoreleasePoolEntry {
  5. uintptr_t ptr: 48;
  6. uintptr_t count: 16;
  7. static const uintptr_t maxCount = 65535; // 2^16 - 1
  8. };
  9. static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
  10. #endif
  11. magic_t const magic;
  12. __unsafe_unretained id *next;
  13. pthread_t const thread;
  14. AutoreleasePoolPage * const parent;
  15. AutoreleasePoolPage *child;
  16. uint32_t const depth;
  17. uint32_t hiwat;
  18. AutoreleasePoolPageData(__unsafe_unretained id* _next, pthread_t _thread, AutoreleasePoolPage* _parent, uint32_t _depth, uint32_t _hiwat)
  19. : magic(), next(_next), thread(_thread),
  20. parent(_parent), child(nil),
  21. depth(_depth), hiwat(_hiwat)
  22. {
  23. }
  24. };

AutoreleasePoolPage

  • AutoreleasePoolPage的结构
    • 一个线程的自动释放池是一个栈帧结构,自动释放池绑定在线程上
    • 自动释放池管理的是对象,哨兵是自动释放池的边界
    • 聚焦(正在操作)的页面是hot pool,其它页面称为code pool
    • 自动释放池是一个双向链表,类的加载过程中(父类-子类)也是一个双向链表结构

image.png

  • AutoreleasePoolPage的构造函数调用

    1. AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
    2. ....
    3. AutoreleasePoolPageData(begin(),
    4. objc_thread_self(),
    5. newParent,
    6. newParent ? 1+newParent->depth : 0,
    7. newParent ? newParent->hiwat : 0)
    8. {
    9. if (objc::PageCountWarning != -1) {
    10. checkTooMuchAutorelease();
    11. }
    12. if (parent) {
    13. parent->check();
    14. ASSERT(!parent->child);
    15. parent->unprotect();
    16. parent->child = this;
    17. parent->protect();
    18. }
    19. protect();
    20. }
    21. ....
    22. }

    begin

  • AutoreleasePoolPage调用的begin()函数,断点调试,大小为56,即上述AutoreleasePoolPageData结构体成员变量所占内存之和

  • 自动释放池分页原因
    • 页面操作是一个不断压栈、出栈的流程,对内存操作非常频繁,如果只有一个页面。所有对象都在一起,加锁、解锁效率低下
    • 管理方便,当只有一个页面,某一处发生问题,那么全部页面将出现错误,分页则保证了稳定性
    • 操作系统也是有内存分页,当只有一页,占用内存空间大,需要很长的连续的一段内存,分页后可以不连续,利用率、效率高
  • 新插入objc应该从56位置开始,每个页面都包含成员变量,具体流程为
    1. 先插入哨兵对象,再插入需要释放的objc指针
    2. 当页面满了时,开辟新的页面,同时设置hot pool

image.png

objc_thread_self

  • 获取当前线程,为tls,线程局部存储

    1. __attribute__((const))
    2. static inline pthread_t objc_thread_self()
    3. {
    4. return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
    5. }
  • 初始化NSObject,然后在buildSettings关闭引用计数Objective-C Automatic Reference Counting, objc调用autorelease方法,extern _objc_autoreleasePoolPrint方法

    • 2 releases pending。包含了两个对象,哨兵对象(0x10e80d038) + objc (0x108c04110)
    • @autoRelease和autorelease区别
      • 前者是一个自动释放池,后者是一个动作,压栈操作,将对象压入自动释放池

image.png

  • 循环调用objc的autorelease进行压栈,加入505个时,出现了内存分页
  • 所以一个内存页面的大小为 504 8 + 56 + 8(哨兵)= 4096,即4 1024 = 4k
  • 此过程中哨兵对象并未因新开内存页而创建,所以一个自动释放池中只有一个哨兵对象

image.png

结构图

音视频设备模块框架图 (2).jpg

Q

  • 临时变量什么时候释放?
    • 作用域结束时释放
  • 自动释放池的原理?
    • 双向链表,哨兵,分页,内部成员变量等
  • 自动释放池是否可以嵌套?

    • 可以嵌套,内部的pool作为一个对象加入到外部的pool中,互补影响
    • llvm中以alloc copy new mutablecopy命名的不会加入到自动释放池中,不受自动释放池的影响

      补充 - 触摸事件runloop(对象何时加入AutoReleasePool与RunLoop有关)

  • Runloop启动时创建AutoReleasePool,Runloop结束时销毁AutoReleasePool

  • 当Runloop即将进入休眠时,代表一次事件处理完成,此时销毁AutoReleasePool
  • 程序启动、唤醒 -> 用户点击屏幕 -> Cocoa Touch生成一个触摸事件(Event Object) -> Cocoa Touch生成一个AutoReleasePool -> Application处理Event Object -> 将Application Objects加入到AutoReleasePool中

1631368366976.jpg