autoreleasepool和autorelease原理探究

如果您希望拥有更好的阅读体验,欢迎访问 我的开发笔记


前段时间离职了,现在终于有时间来了解些以前一直没有弄懂的知识了,啥也不说了,失业才是第一生产力,啊哈哈哈哈哈。。。。。

先把项目改为MRC,在main.m中写如下代码.

int main(int argc, const char * argv[]) {
	@autoreleasepool {
		__unused RTStudent *stu = [[[RTStudent alloc] init] autorelease];
	}
	return 0;
}

使用xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m转为C++代码,再整理后得

int main(int argc, const char * argv[]) {
    { 
        __AtAutoreleasePool __autoreleasepool;
        RTStudent *stu = objc_msgSend(objc_msgSend(objc_msgSend(objc_getClass("RTStudent"), sel_registerName("alloc")), sel_registerName("init")), sel_registerName("autorelease"));
    }
 return 0;
}

其中__AtAutoreleasePool是一个结构体:

struct __AtAutoreleasePool {
  __AtAutoreleasePool() {
    atautoreleasepoolobj = objc_autoreleasePoolPush();
  } //构造函数
  
  ~__AtAutoreleasePool() {
    objc_autoreleasePoolPop(atautoreleasepoolobj)
  } //析构函数
  void * atautoreleasepoolobj;
};

objc_msgSend转为OC方法:

int main(int argc, const char * argv[]) {
 {
    __AtAutoreleasePool __autoreleasepool; 
    RTStudent *stu = [[[RTStudent alloc] init] autorelease];
 }
 return 0;
}

main函数中可见@autoreleasepool的作用是在{}中声明了一个__AtAutoreleasePool __autoreleasepool结构体对象,会自动调用__AtAutoreleasePool的构造函数__AtAutoreleasePool(),就又调用了objc_autoreleasePoolPush()函数,当出了{}的作用域时,会销毁局部变量__autoreleasepool,此时就会调用析构函数~__AtAutoreleasePool(),然后就调用了objc_autoreleasePoolPop()函数,可见autorelease的对象,会自动销毁应该和pushpop有关了

那么push和pop到底做了哪些事情呢?

在NSObject.mm文件中找到objc_autoreleasePoolPush()objc_autoreleasePoolPop()

void * objc_autoreleasePoolPush(void)
{
    return AutoreleasePoolPage::push();
}
void objc_autoreleasePoolPop(void *ctxt)
{
    AutoreleasePoolPage::pop(ctxt);
}

具体实现是调用了AutoreleasePoolPage类的pushpop方法,进入方法查看后,发现不论是push还是pop都主要和AutoreleasePoolPage对象在管理

class AutoreleasePoolPage 
{
#   define EMPTY_POOL_PLACEHOLDER ((id*)1)

#   define POOL_BOUNDARY nil
    static pthread_key_t const key = AUTORELEASE_POOL_KEY;
    static uint8_t const SCRIBBLE = 0xA3;  // 0xA3A3A3A3 after releasing
    static size_t const SIZE = 
#if PROTECT_AUTORELEASEPOOL
        PAGE_MAX_SIZE;  // must be multiple of vm page size
#else
        PAGE_MAX_SIZE;  // size and alignment, power of 2
#endif
    static size_t const COUNT = SIZE / sizeof(id);

    magic_t const magic;
    id *next;
    pthread_t const thread;
    AutoreleasePoolPage * const parent;
    AutoreleasePoolPage *child;
    uint32_t const depth;
    uint32_t hiwat;
}
  • 每个AutoreleasePoolPage对象占用4096字节内存,除了用来存放它内部的成员变量,剩下的空间用来存放autorelease对象的地址
  • 所有的AutoreleasePoolPage对象通过双向链表的形式连接在一起
  • next指向下一个可用于存放autorelease对象的地址

autoreleasepool和autorelease原理探究_第1张图片

那么从哪个地址开始存放需要autorelease的对象呢?它有一个方法begin(),可以看到,this的起始地址+自己的成员所占的大小,就是存放需要autorelease的对象的起始地址

    id * begin() {
        return (id *) ((uint8_t *)this+sizeof(*this));
    }

那么结束地址在哪里呢?它有一个方法end(),可以看到,this的起始地址+自己的数据结构的大小

    id * end() {
        return (id *) ((uint8_t *)this+SIZE);
    }

由于一个page对象只有4096字节,所以当需要autorelease对象太多的话,一个page装不下,所以可能一个pool中有很多个page对象,而child指针指向的就是下一个page对象,parent指向的就是上一个page对象

push,autorelease,pop做了哪些事情呢?

  1. 调用push方法会将一个POOL_BOUNDARY入栈,并且返回其存放的内存地址,也就是begin()的地址,并把这个地址返回给__AtAutoreleasePool对象的atautoreleasepoolobj,然后next指向下一个地址
   static inline void *push() 
    {
        id *dest;
        if (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); // POOL_BOUNDARY == *atautoreleasepoolobj
        return dest;
    }
  1. 一旦在自动释放池中,有对象调用了autorelease方法,就是将需要autorelease的对象放入page中,然后next指向下一个地址:
//调用了rootAutorelease()
- (id)autorelease {
    return ((id)self)->rootAutorelease();
}

//这个方法里调用了rootAutorelease2函数
inline id objc_object::rootAutorelease() {
    if (isTaggedPointer()) return (id)this;
    if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;

    return rootAutorelease2();
}

//这个方法里调用了AutoreleasePoolPage的autorelease函数
__attribute__((noinline,used)) id objc_object::rootAutorelease2() {
    assert(!isTaggedPointer());
    return AutoreleasePoolPage::autorelease((id)this);
}

//这个方法里调用了page->add(obj) 将obj入栈
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);
    }
}
  1. 调用pop传递一个参数atautoreleasepoolobj,就从最后一个入栈的autorelease对象开始调用release方法,直到遇到POOL_BOUNDARY这个地址,结束释放.
   static inline void pop(void *token) 
    {
        AutoreleasePoolPage *page;
        id *stop;

        ..........

        page = pageForPointer(token);
        stop = (id *)token;
        if (*stop != POOL_BOUNDARY) {
            if (stop == page->begin()  &&  !page->parent) {
                // Start of coldest page may correctly not be POOL_BOUNDARY:
                // 1. top-level pool is popped, leaving the cold page in place
                // 2. an object is autoreleased with no pool
            } else {
                // Error. For bincompat purposes this is not 
                // fatal in executables built with old SDKs.
                return badPop(token);
            }
        }

        if (PrintPoolHiwat) printHiwat();

        page->releaseUntil(stop);

        .........
    }

    //释放
   void releaseUntil(id *stop) 
    {
        while (this->next != stop) {
            AutoreleasePoolPage *page = hotPage();
            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);
            }
        }
        
        ...........
    }

打印自动释放池的信息

AutoreleasePoolPage类中有一个打印的方法

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

虽然_objc_autoreleasePoolPrint没有暴露出来,但可以使用extern void __objc_autoreleasePoolPrint(void);来声明,然后使用

extern void _objc_autoreleasePoolPrint(void);

int main(int argc, const char * argv[]) {
	@autoreleasepool {
		__unused RTStudent *stu = [[[RTStudent alloc] init] autorelease];
		__unused RTStudent *stu2 = [[[RTStudent alloc] init] autorelease];
		@autoreleasepool {
			__unused RTStudent *stu = [[[RTStudent alloc] init] autorelease];
			@autoreleasepool {
				__unused RTStudent *stu = [[[RTStudent alloc] init] autorelease];
				__unused RTStudent *stu2 = [[[RTStudent alloc] init] autorelease];
				_objc_autoreleasePoolPrint();
			}
		}
	}
	return 0;
}
objc[88455]: ##############
objc[88455]: AUTORELEASE POOLS for thread 0x100399380
objc[88455]: 8 releases pending.
objc[88455]: [0x100801000]  ................  PAGE  (hot) (cold)
objc[88455]: [0x100801038]  ################  POOL 0x100801038
objc[88455]: [0x100801040]       0x10054f4a0  RTStudent
objc[88455]: [0x100801048]       0x100422ae0  RTStudent
objc[88455]: [0x100801050]  ################  POOL 0x100801050
objc[88455]: [0x100801058]       0x100403100  RTStudent
objc[88455]: [0x100801060]  ################  POOL 0x100801060
objc[88455]: [0x100801068]       0x100423740  RTStudent
objc[88455]: [0x100801070]       0x100405aa0  RTStudent
objc[88455]: ##############

可以看到,使用一次@autoreleasepool{},即创建一个__AtAutoreleasePool对象,就会调用一次push,放进一个POOL_BOUNDARY,page对象next指针指向下一个地址,然后把需要autorelease对象放入page中,next指针移动相应数量的地址,当一个page没有满autorelease对象的时候,多个释放池会共用这个page,直到它了,才会创建新的page对象

autorelease对象在什么时机会被调用release?

  1. 当对象被@autoreleasePool{}包裹的时候,在{}结束的时候,会被release
- (void)viewDidLoad {
	[super viewDidLoad];
	
	@autoreleasepool {
		__unused RTPerson *obj = [[[RTPerson alloc] init] autorelease];
	}
	NSLog(@"%s",__func__);
}

//打印信息
2019-05-27 22:27:16.307421+0800 Test[91865:5115718] dealloc
2019-05-27 22:27:16.307560+0800 Test[91865:5115718] -[ViewController viewDidLoad]
  1. 当对象没有被@autoreleasePool{}包裹的时候,会在这一次runloop,休眠之前release
- (void)viewDidLoad {
	[super viewDidLoad];
	
	__unused RTPerson *obj = [[[RTPerson alloc] init] autorelease];
	NSLog(@"%s",__func__);
}

-(void)viewWillAppear:(BOOL)animated {
	[super viewWillAppear:animated];
	NSLog(@"%s",__func__);
}

-(void)viewDidAppear:(BOOL)animated {
	[super viewDidAppear:animated];
	NSLog(@"%s",__func__);
}
//打印信息
2019-05-27 22:29:21.045179+0800 Test[91982:5120141] -[ViewController viewDidLoad]
2019-05-27 22:29:21.045454+0800 Test[91982:5120141] -[ViewController viewWillAppear:]
2019-05-27 22:29:21.048347+0800 Test[91982:5120141] dealloc
2019-05-27 22:29:21.058970+0800 Test[91982:5120141] -[ViewController viewDidAppear:]

打印当前的runloop的observers时,会发现有两个observe和AutoreleasePool相关

    "{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10b8871b1), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7f8baa009058>\n)}}",

    activities = 0x1 即  kCFRunLoopEntry = (1UL << 0) ,当runloop进入的时候,会调用回调,调用objc_autoreleasePoolPush()函数

    "{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10b8871b1), context = {type = mutable-small, count = 1, values = (\n\t0 : <0x7f8baa009058>\n)}}"

    kCFRunLoopBeforeWaiting = (1UL << 5) => 32
    kCFRunLoopExit = (1UL << 7) => 128

    activities = 0xa0 (十进制的160) 即  kCFRunLoopBeforeWaiting | kCFRunLoopExit

    当runloop即将休眠和退出的时候调用回调,调用objc_autoreleasePoolPop()、objc_autoreleasePoolPush()函数
    当runloop即将退出的时候调用回调,会调用objc_autoreleasePoolPop()函数

欢迎访问

我的开发笔记

你可能感兴趣的:(iOS)