iOS之自动释放池 一

55 阅读6分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第13天,点击查看活动详情

自动释放池:AutoReleasePool,是OC中的一种内存自动回收机制,它可以将加入AutoreleasePool中的变量release的时机延迟。简单来说,当创建一个对象,在正常情况下,变量会在超出其作用域时立即release。如果将其加入到自动释放池中,这个对象并不会立即释放,而会等到runloop休眠/超出autoreleasepool作用域之后进行释放。

image.png

  • Runloop会处于休眠状态,等待用户交互来唤醒Runloop
  • 用户每次交互都会启动一次Runloop,用于处理用户的所有点击、触摸等事件
  • Runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中
  • 在一次完整的Runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池

结构

使用cpp文件探索

创建一个Mac工程,在main.m中,自动生成以下代码:

#import <Foundation/Foundation.h>

int main(int argc, const char * argv[]) {
 
    @autoreleasepool {
        NSLog(@"Hello, World!");
    }
    return 0;
}

生成cpp文件

clang -rewrite-objc main.m -o main.cpp

打开cpp文件,来到main函数

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

    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_jl_d06jlfkj2ws74_5g45kms07m0000gn_T_main_da0d58_mi_0);
    }
    return 0;
}
  • autoreleasepool被注释掉了,但作用域还在
  • 作用域中生成对__AtAutoreleasePool类型声明的代码 找到__AtAutoreleasePool定义
struct __AtAutoreleasePool {
  __AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
  ~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
  void * atautoreleasepoolobj;
};
  • __AtAutoreleasePool是一个结构体,包含构造函数和析构函数
  • 结构体声明,触发构造函数,调用objc_autoreleasePoolPush函数
  • 当结构体出作用域空间,触发析构函数,调用objc_autoreleasePoolPop函数

使用汇编代码探索

打开项目,在main函数中autoreleasepool处设置断点,查看汇编代码

image-2.png

  • 调用objc_autoreleasePoolPush函数
  • 调用objc_autoreleasePoolPop函数 进入objc_autoreleasePoolPush函数

image-3.png

  • 源码来自于libobjc框架

源码探索

打开objc4-818.2源码,找到objc_autoreleasePoolPush函数

void *
objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
  • 调用AutoreleasePoolPage命名空间下的push函数
AutoreleasePoolPage

找到AutoreleasePoolPage的定义,首先看到这样一段注释:

/***********************************************************************
   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.
   每个指针要么是一个要释放的对象,要么是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.
   池令牌是指向该池的POOL_BOUNDARY的指针。当池被弹出,每个比哨兵热的对象都被释放
     
   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. 
   线程本地存储指向热页,其中存储新自动释放的对象
**********************************************************************/

通过注释我们可以了解到以下几点:

  • 自动释放池和线程有关系
  • 自动释放池是一个存储指针的栈结构
  • 指针要么是一个要释放的对象,要么是POOL_BOUNDARY自动释放池边界,俗称:哨兵对象
    • 哨兵对象的作用:当自动释放池将对象进行pop操作时,需要知道边界在哪里,否则会破坏别人的内存空间。而哨兵对象,就是作为边界的标识而存在
  • 自动释放池的栈空间被分成一个双链接结构的页面列表,可添加和删除页面
    • 双向链表的特点,一个页中同时存在父节点和子节点。可向前找到父页面,也可向后找到子页面
  • 线程本地存储指向热页,其中存储新自动释放的对象
    • 栈原则,先进后出,可以理解为最后一个页面就是热页。里面的对象最后被push,最先被pop AutoreleasePoolPage继承于AutoreleasePoolPageData
class AutoreleasePoolPage : private AutoreleasePoolPageData
{
	friend struct thread_data_t;

public:
	static size_t const SIZE =
#if PROTECT_AUTORELEASEPOOL
		PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
		PAGE_MIN_SIZE;  // size and alignment, power of 2
#endif
    
private:
	static pthread_key_t const key = AUTORELEASE_POOL_KEY;
	static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
	static size_t const COUNT = SIZE / sizeof(id);
    static size_t const MAX_FAULTS = 2;
    
    ...
}
AutoreleasePoolPageData

找到AutoreleasePoolPageData的定义

class AutoreleasePoolPage;
struct AutoreleasePoolPageData
{
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    struct AutoreleasePoolEntry {
        uintptr_t ptr: 48;
        uintptr_t count: 16;

        static const uintptr_t maxCount = 65535; // 2^16 - 1
    };
    static_assert((AutoreleasePoolEntry){ .ptr = MACH_VM_MAX_ADDRESS }.ptr == MACH_VM_MAX_ADDRESS, "MACH_VM_MAX_ADDRESS doesn't fit into AutoreleasePoolEntry::ptr!");
#endif

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

结构体中,包含以下成员变量:

  • magic:用来校验AutoreleasePoolPage的结构是否完整
  • next:指向最新添加的autoreleased对象的下一个位置,初始化时执行begin()
  • thread:指向当前线
  • parent:指向父节点,第一个节点的parent值为nil
  • child:指向子节点,最后一个节点的child值为nil
  • depth:代表深度,从0开始,往后递增1
  • hiwat:代表high water mark最大入栈数量标记
打印结构

搭建测试项目,关闭ARC模式

image-4.png 打开main.m文件,写入以下代码:

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
  
        NSObject *objc = [[[NSObject alloc] init] autorelease];
        
        _objc_autoreleasePoolPrint();
    }
    return 0;
}
  • _objc_autoreleasePoolPrint函数,用于打印自动释放池的结构
  • 创建NSObject实例对象,加入自动释放池
  • 调用_objc_autoreleasePoolPrint函数,打印结构 输出以下内容:
##############
AUTORELEASE POOLS for thread 0x1000ebe00
2 releases pending.
[0x10700b000]  ................  PAGE  (hot) (cold)
[0x10700b038]  ################  POOL 0x10700b038
[0x10700b040]       0x100705f60  NSObject
##############
  • 打印出当前自动释放池所属线程
  • 2个需要释放的对象
  • 当前的Page信息,占56字节。因为只有一页,即是冷页面,也是热页面
  • 哨兵对象POOL
  • NSObject对象
_objc_autoreleasePoolPrint

官方用于对自动释放池内容调试打印的函数

void 
_objc_autoreleasePoolPrint(void)
{
    AutoreleasePoolPage::printAll();
}

进入printAll函数

    static void printAll()
    {
        _objc_inform("##############");
        _objc_inform("AUTORELEASE POOLS for thread %p", objc_thread_self());

        AutoreleasePoolPage *page;
        ptrdiff_t objects = 0;
        for (page = coldPage(); page; page = page->child) {
            objects += page->next - page->begin();
        }
        _objc_inform("%llu releases pending.", (unsigned long long)objects);

        if (haveEmptyPoolPlaceholder()) {
            _objc_inform("[%p]  ................  PAGE (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
            _objc_inform("[%p]  ################  POOL (placeholder)", 
                         EMPTY_POOL_PLACEHOLDER);
        }
        else {
            for (page = coldPage(); page; page = page->child) {
                page->print();
            }
        }

        _objc_inform("##############");
    }

#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
    __attribute__((noinline, cold))
    unsigned sumOfExtraReleases()
    {
        unsigned sumOfExtraReleases = 0;
        for (id *p = begin(); p < next; p++) {
            if (*p != POOL_BOUNDARY) {
                sumOfExtraReleases += ((AutoreleasePoolEntry *)p)->count;
            }
        }
        return sumOfExtraReleases;
    }
#endif

    __attribute__((noinline, cold))
    static void printHiwat()
    {
        // Check and propagate high water mark
        // Ignore high water marks under 256 to suppress noise.
        AutoreleasePoolPage *p = hotPage();
        uint32_t mark = p->depth*COUNT + (uint32_t)(p->next - p->begin());
        if (mark > p->hiwat + 256) {
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            unsigned sumOfExtraReleases = 0;
#endif
            for( ; p; p = p->parent) {
                p->unprotect();
                p->hiwat = mark;
                p->protect();
                
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
                sumOfExtraReleases += p->sumOfExtraReleases();
#endif
            }

            _objc_inform("POOL HIGHWATER: new high water mark of %u "
                         "pending releases for thread %p:",
                         mark, objc_thread_self());
#if SUPPORT_AUTORELEASEPOOL_DEDUP_PTRS
            if (sumOfExtraReleases > 0) {
                _objc_inform("POOL HIGHWATER: extra sequential autoreleases of objects: %u",
                             sumOfExtraReleases);
            }
#endif

            void *stack[128];
            int count = backtrace(stack, sizeof(stack)/sizeof(stack[0]));
            char **sym = backtrace_symbols(stack, count);
            for (int i = 0; i < count; i++) {
                _objc_inform("POOL HIGHWATER:     %s", sym[i]);
            }
            free(sym);
        }
    }

#undef POOL_BOUNDARY
};
  • 按照自动释放池的结构,通过双向链表遍历page,依次读取page中的内容并进行打印