1.探究autoreleasepool是什么东西
1.clang探究autoreleasepool本质
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
NSLog(@"Hello, World!");
}
return 0;
}
clang -rewrite-objc main.m -o main.cpp
clang之后的代码
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_l2_ftcx04s56rs4kd9gct484_740000gq_T_main_b274fe_mi_0);
}
return 0;
}
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
我们看出__AtAutoreleasePool是c++写的结构体,里面有一个构造函数和析构函数。我们也可以自定义这种c++的结构体
struct MyTest {
MyTest(){
printf("创建函数");
}
~MyTest(){
printf("析构函数");
}
};
//在某个作用域调用
{
MyTest mytest;
}
//打印结果
创建函数析构函数
我们可以看出__AtAutoreleasePool的创建函数是创建一个对象,析构函数是pop这个对象。从源码分析自动释放池
2.汇编看@autoreleasepool的本质
objc_autoreleasePoolPush和objc_autoreleasePoolPop就是上面clang中__AtAutoreleasePool结构体构建函数和析构函数的调用方法。
2.从源码看自动释放池的结构
2.1apple对自动释放池的介绍 (在objc源码中)
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.线程的自动释放池是一个指针的栈结构,
2.栈结构中存放要释放的指针和自动释放池的哨兵(边界)。
3.自动释放池是页的结构并且是双向链表
4.用线程本地存储指向hot页
2.2源码看结构
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
AutoreleasePoolPage是类,继承自AutoreleasePoolPageData,说明我们自动释放池的本质是一个对象。
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结构体中有parent和child,这就是双向链表的体现。
magic :用来校验 AutoreleasePoolPage 的结构是否完整
next :指向最新添加的 autoreleased 对象的下一个位置,初始化时指向begin()
thread :指向当前线程
parent :指向父结点,第一个结点的 parent 值为 nil
child :指向子结点,最后一个结点的 child 值为 nil
depth:代表深度,从 0 开始,往后递增 1
hiwat: 代表 high water mark 最大入栈数量标记
创建自动释放池
我们可以看到在AutoreleasePoolPage中有一个构造方法
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
参数中,objc_thread_self是从tls拿到线程,其他参数都是调用者传来的,但是begin是什么呢?
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));
}
通过联调我们知道sizeof(*this)是56,为什么呢?我们来看结构体AutoreleasePoolPageData的内存布局
struct magic_t {
static const uint32_t M0 = 0xA1A1A1A1; //静态不占字节
# define M1 "AUTORELEASE!"
static const size_t M1_len = 12;//静态不占字节
uint32_t m[4]; //16字节
}
struct AutoreleasePoolPageData
{
magic_t const magic; //16字节
__unsafe_unretained id *next; //指针类型8字节
pthread_t const thread; //指针类型占8字节
AutoreleasePoolPage * const parent;//8字节
AutoreleasePoolPage *child;//8字节
uint32_t const depth;//4字节
uint32_t hiwat;//4字节
}
所以偏移56个字节
所以就有下面的结构
2.3通过打印看自动释放池内的对象
extern void _objc_autoreleasePoolPrint(void);
@autoreleasepool {
NSObject *objc1 = [NSObject new] ;
NSObject *objc2 = [NSObject new] ;
NSObject *objc3 = [NSObject new] ;
NSObject *objc4 = [NSObject new] ;
NSObject *objc5 = [NSObject new] ;
_objc_autoreleasePoolPrint();
}
不打印,我们改成MRC并修改代码
@autoreleasepool {
for (int i = 0 ; i< 5; i++) {
NSObject *objc1 = [[NSObject new] autorelease] ;
}
_objc_autoreleasePoolPrint();
}
打印结果
objc[93582]: ##############
objc[93582]: AUTORELEASE POOLS for thread 0x1000dedc0
objc[93582]: 6 releases pending.
objc[93582]: [0x103012000] ................ PAGE (hot) (cold)
objc[93582]: [0x103012038] ################ POOL 0x103012038
objc[93582]: [0x103012040] 0x1019325a0 NSObject
objc[93582]: [0x103012048] 0x101930e70 NSObject
objc[93582]: [0x103012050] 0x1019058d0 NSObject
objc[93582]: [0x103012058] 0x10192f5c0 NSObject
objc[93582]: [0x103012060] 0x10192eb90 NSObject
objc[93582]: ##############
我们明明压进去5个指针,为什么显示6个对象指针呢?
POOL 0x103012038为我们的边界。
2.4一个AutoreleasePoolPage到底有多大
id * end() {
return (id *) ((uint8_t *)this+SIZE);
}
SIZE = PAGE_MIN_SIZE;
PAGE_MIN_SIZE = 1<<12
1<<12 = 4096,我们知道AutoreleasePoolPage自己占56个字节,(4096-56)/8 = 505,因为有一个边界占一个8字节,所以能存放504个指针。
for (int i = 0 ; i< 505; i++) {
NSObject *objc1 = [[NSObject new] autorelease] ;
}
NSLog(@"------");
_objc_autoreleasePoolPrint();
//打印结果 只截取后面部分
objc[71301]: [0x102009000] ................ PAGE (hot)
objc[71301]: [0x102009038] 0x101852d40 NSObject
objc[71301]: ##############
到505时,又创建一个page,把505存储到新建的page里。
那第2个page能存多少个呢?
for (int i = 0 ; i< 504+506; i++) {
NSObject *objc1 = [[NSObject new] autorelease] ;
}
我们知道第一个page里面会存504个指针,如果第二个page也存504个指针,那么第三个page里还有两个指针。如果第二个page存505个对象指针,第三个page 里有一个指针。打印结果只copy最后部分
objc[72598]: [0x10200dff0] 0x101151d20 NSObject
objc[72598]: [0x10200dff8] 0x101151d30 NSObject
objc[72598]: [0x102813000] ................ PAGE (hot)
objc[72598]: [0x102813038] 0x101151d40 NSObject
objc[72598]: ##############
从打印结果看,第二个page里面存505个指针,除了第一个page,再后面的page中不会重新创建边界对象。
3.创建自动释放池
AutoreleasePoolPage::push();
static inline void *push()
{
id *dest;
if (slowpath(DebugPoolAllocation)) {
//可以通过设置环境来设置DebugPoolAllocation为true
// Each autorelease pool starts on a new pool page.
dest = autoreleaseNewPage(POOL_BOUNDARY);
} else {
dest = autoreleaseFast(POOL_BOUNDARY); //POOL_BOUNDARY为nil
}
ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY);
return dest;
}
//第一个obj为nil
static inline id *autoreleaseFast(id obj)
{
AutoreleasePoolPage *page = hotPage();
if (page && !page->full()) {
//hotpage存在,并且没有满,直接向page中加指针
return page->add(obj);
} else if (page) {
//page存在,并且满了
return autoreleaseFullPage(obj, page);
} else {
//hotpage不存在
return autoreleaseNoPage(obj);
}
}
因为我们第一次创建,所以我们重点判断hotpage不存在的情况
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
return setEmptyPoolPlaceholder();
}
因为obj是空的,并且DebugPoolAllocation我们没在环境变量中设置所以我们得到一个空的占位符。当我们向里面加入对象指针时,初始化完成。
3压栈
3.1第一次压栈
在mrc中,一个对象autorelease,就实现了向自动释放池中压栈。
autorelease--》_objc_rootAutorelease(self)--->obj->rootAutorelease()---->rootAutorelease2()--->AutoreleasePoolPage::autorelease((id)this)--->autoreleaseFast(obj)
autoreleaseFast中会判断hotpage,因为我们在创建的时候会搞一个占位符,所以hotpage返回是NULL,重新走autoreleaseNoPage(obj)
id *autoreleaseNoPage(id obj) {
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
pushExtraBoundary = true;
}
//创建新的page
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
//向page中加入边界(哨兵)
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
//压入我们真正的对象
return page->add(obj);
}
3.2第n次压栈
如果压入的对象不是在刚才线程,则会重新创建page,重新上面步骤。
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);
}
}
如果我们的hotpage,没有满,则直接存储对象指针,并让next++;
我们着重研究hotpage满了的情况:
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 page是否存在,如果存在则设置page为子page,并判断page是否满了,如果不满,则获取page,并设置当前page为hotpage,并向page中压入对象,如果判断到childpage为nil,则重新创建page,在创建page时,会传入原来page中最后的子page,让把创建的page作为最后子page的子page,再设置hotpage并压入对象指针。
自动释放池释放
objc_autoreleasePoolPop实现对指针的释放
objc_autoreleasePoolPop===> pop(void *token)====> popPage(void *token, AutoreleasePoolPage *page, id *stop)====>releaseUntil(id *stop)
在pop(void *token)中page = pageForPointer(token);,取到最子page,在releaseUntil中对page中对象释放。
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
}
对压入栈的对象进行释放,并对page中对象所占内存进行处理memset。
在popPage中有对自动释放池释放
page->kill();并设置父page为hotpage,删除所有的子page。
4自动释放池嵌套
@autoreleasepool {
NSObject *objc1 = [[NSObject new] autorelease] ;
@autoreleasepool {
for (int i = 0 ; i< 5; i++) {
NSObject *objc = [[NSObject new] autorelease] ;
}
_objc_autoreleasePoolPrint();
}
NSObject *objc2 = [[NSObject new] autorelease] ;
NSObject *objc3 = [[NSObject new] autorelease] ;
_objc_autoreleasePoolPrint();
}
嵌套内的打印
objc[77336]: ##############
objc[77336]: AUTORELEASE POOLS for thread 0x1000dedc0
objc[77336]: 8 releases pending.
objc[77336]: [0x10180c000] ................ PAGE (hot) (cold)
objc[77336]: [0x10180c038] ################ POOL 0x10180c038
objc[77336]: [0x10180c040] 0x1010408a0 NSObject
objc[77336]: [0x10180c048] ################ POOL 0x10180c048
objc[77336]: [0x10180c050] 0x101040360 NSObject
objc[77336]: [0x10180c058] 0x10103ec30 NSObject
objc[77336]: [0x10180c060] 0x10103d350 NSObject
objc[77336]: [0x10180c068] 0x10103c920 NSObject
objc[77336]: [0x10180c070] 0x10103d250 NSObject
objc[77336]: ##############
最外面自动释放池打印
objc[77336]: ##############
objc[77336]: AUTORELEASE POOLS for thread 0x1000dedc0
objc[77336]: 4 releases pending.
objc[77336]: [0x10180c000] ................ PAGE (hot) (cold)
objc[77336]: [0x10180c038] ################ POOL 0x10180c038
objc[77336]: [0x10180c040] 0x1010408a0 NSObject
objc[77336]: [0x10180c048] 0x10068c190 NSObject
objc[77336]: [0x10180c050] 0x10068eb10 NSObject
objc[77336]: ##############
我们看出嵌套内的自动释放池没有重新创建释放池,但是多加了一个边界。
第二层自动释放池中push时,因为有hotpage并且没有满,所以直接压进去一个空。我再看一下释放的时候,void releaseUntil(id *stop)中有对对象进行非空判断,POOL_BOUNDARY就是nil
define POOL_BOUNDARY nil