我们都知道一个iOS应用的如果是在main
函数中,它的实现是
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
复制代码
我们看到在main
中有个@autoreleasepool
,那它到底是什么呢?让我们转成.cpp
看下:
xcrun --sdk iphoneos clang -arch arm64 -rewrite-objc main.m
复制代码
转换成c++
后是
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
return UIApplicationMain(argc, argv, __null, NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class"))));
}
}
复制代码
@autoreleasepool
对应的就是个__AtAutoreleasePool
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
复制代码
因为是C++
代码,所以可以看出这个结构体构造函数会调用objc_autoreleasePoolPush()
,析构函数会调用objc_autoreleasePoolPop();
,所以main
函数可以理解为:
int main(int argc, char * argv[]) {
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
...
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
}
复制代码
那么objc_autoreleasePoolPush()
与objc_autoreleasePoolPop()
都做了什么?我们可以从源码中一窥究竟
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
复制代码
可以看到这两个方法只是调用了AutoreleasePoolPage
的静态方法,那么AutoreleasePoolPage
是什么?下面我们就去看一看
AutoreleasePoolPage
AutoreleasePoolPage
是一个C++
的类,并且是一个双向链表
class AutoreleasePoolPage
{
magic_t const magic;//16
id *next;//8
pthread_t const thread;//8
AutoreleasePoolPage * const parent;//8
AutoreleasePoolPage *child;//8
uint32_t const depth;//4
uint32_t hiwat;//4
}
复制代码
magic
校验AutoreleasePoolPage
的完整性,thread
保存了当前所在线程
没一个自动释放池都是由多个AutoreleasePoolPage
组成的,而每个AutoreleasePoolPage
都有固定的大小
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
#define PAGE_MAX_SIZE PAGE_SIZE
#define PAGE_SIZE I386_PGBYTES
#define I386_PGBYTES 4096 /* bytes per 80386 page */
复制代码
可以看出每个AutoreleasePoolPage
的大小都是4096
也就是16进制0x1000
,而其中AutoreleasePoolPage
自己的成员占56位
,剩下的空间用于存储加入自动释放池的对象,AutoreleasePoolPage
提供了两个方法begin()
与end()
可以方便快速找到存储自动释放池对象的范围
id * begin() {
return (id *) ((uint8_t *)this+sizeof(*this));//当前`AutoreleasePoolPage`指针 + `AutoreleasePoolPage`大小得到起始位置
}
id * end() {
return (id *) ((uint8_t *)this+SIZE);//当前`AutoreleasePoolPage`指针 + 整个`AutoreleasePoolPage`大小(4096)得到结束位置
}
复制代码
next
指针则指向了下一个为空的位置
大致的AutoreleasePoolPage
我们已经了解了,那么我们回头去看下push
操作
push
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
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);
return dest;
}
复制代码
首先,我们看到传入了个POOL_BOUNDARY
宏,这个宏只是个nil
# define POOL_BOUNDARY nil
复制代码
从后面我们可以看到,传进去这个nil
后会被加入自动释放池,并将这个值返回回来,然后在后面使用pop
操作的时候传入这个POOL_BOUNDARY
时,会一直release
自动释放池中的对象直到找到第一个POOL_BOUNDARY
int main(int argc, char * argv[]) {
{
atautoreleasepoolobj = objc_autoreleasePoolPush();
...
objc_autoreleasePoolPop(atautoreleasepoolobj);
}
}
复制代码
后面讲到
pop
时候我们在细说具体是怎么释放的
然后可以看到调用autoreleaseFast
函数(DebugPoolAllocation
我们不管)
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()
获取可用的AutoreleasePoolPage
,然后剩下要分三种情况
- 当前有
hotpage
并且这个hotpage
并没有满,调用add()
函数
if (page && !page->full()) {
return page->add(obj);
}
复制代码
会调用add()
方法
id *add(id obj)
{
assert(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
/*
*next++ = obj;看着可能会有点抽象了,我们给展开看
*next = obj;
next++;
*/
protect();
return ret;
}
复制代码
这个方法很简单,将当前传入的对象加入第一个为空的位置(next)指向的位置,然后把next
指针向后挪一位,最后返回传入的这个对象
- 自动释放池有
hotpage
,但是hotpage
已经满了,会调用autoreleaseFullPage()
函数
if (page) {
return autoreleaseFullPage(obj, page);
}
static __attribute__((noinline))
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);//创建一个新的page 将上一个的page child指针指向 新的page
} while (page->full());
setHotPage(page);
return page->add(obj);
}
复制代码
这个方法里面在遍历整个AutoreleasePoolPage
链表,找到不满的那个page
或者如果遍历到最后一个page
也都满了就创建一个新的page
,并将这个page
设置为hotPage
,最后调用add()
方法
- 自动释放池没有
hotPage
,会调用autoreleaseNoPage()
函数
else {
return autoreleaseNoPage(obj);
}
static __attribute__((noinline))
id *autoreleaseNoPage(id obj)
{
// "No page" could mean no pool has been pushed
// or an empty placeholder pool has been pushed and has no contents yet
assert(!hotPage());
bool pushExtraBoundary = false;
if (haveEmptyPoolPlaceholder()) {
// We are pushing a second pool over the empty placeholder pool
// or pushing the first object into the empty placeholder pool.
// Before doing that, push a pool boundary on behalf of the pool
// that is currently represented by the empty placeholder.
//1,判断是否有空page
pushExtraBoundary = true;
}
else if (obj != POOL_BOUNDARY && DebugMissingPools) {
// We are pushing an object with no pool in place,
// and no-pool debugging was requested by environment.
_objc_inform("MISSING POOLS: (%p) Object %p of class %s "
"autoreleased with no pool in place - "
"just leaking - break on "
"objc_autoreleaseNoPool() to debug",
pthread_self(), (void*)obj, object_getClassName(obj));
//Debug环境 忽略
objc_autoreleaseNoPool(obj);
return nil;
}
else if (obj == POOL_BOUNDARY && !DebugPoolAllocation) {
// We are pushing a pool with no pool in place,
// and alloc-per-pool debugging was not requested.
// Install and return the empty pool placeholder.
// 2,如果传进来的是`POOL_BOUNDARY`则设置一个空page 使用tls技术 以键值对的方式存储
return setEmptyPoolPlaceholder();
}
// We are pushing an object or a non-placeholder'd pool.
// Install the first page.
//2,创建自动释放池中第一个page
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
//3,将这个page设置为hotPage
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
//4,传入哨兵对象(POOL_BOUNDARY)
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
//5,添加对象进自动释放池
return page->add(obj);
}
复制代码
1,判断是否有空page
2,如果传进来的是POOL_BOUNDARY
则设置一个空page 使用tls技术 以键值对的方式存储
3,将这个page设置为hotPage
4,传入哨兵对象(POOL_BOUNDARY)
5,添加对象进自动释放池
push
操作就是这样的,下面我们继续看下pop
pop
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
复制代码
这个地方传入的ctxt
正式调用push
时返回的那个哨兵对象POOL_BOUNDARY
(上文有说到)
static inline void pop(void *token)
{
AutoreleasePoolPage *page;
id *stop;
//这块貌似关于tls 能力有限不是太懂就不丢人现眼了,如果有大神对这些比较了解 望不吝赐教
if (token == (void*)EMPTY_POOL_PLACEHOLDER) {
// Popping the top-level placeholder pool.
if (hotPage()) {
// Pool was used. Pop its contents normally.
// Pool pages remain allocated for re-use as usual.
pop(coldPage()->begin());
} else {
// Pool was never used. Clear the placeholder.
setHotPage(nil);
}
return;
}
page = pageForPointer(token);//根据token(一个指针)获取当前的page
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);//释放栈中对象 直到stop (stop正常情况应该是)
// memory: delete empty children
if (DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (DebugMissingPools && page->empty() && !page->parent) {
// special case: delete everything for pop(top)
// when debugging missing autorelease pools
page->kill();
setHotPage(nil);
}
else if (page->child) {
// hysteresis: keep one empty child if page is more than half full
if (page->lessThanHalfFull()) {
page->child->kill();
}
else if (page->child->child) {
page->child->child->kill();
}
}
}
复制代码
上面貌似关于tls 能力有限不是太懂就不丢人现眼了,如果有大神对这些比较了解 望不吝赐教
这个方法的下半部分主要是以传入的token
为标记从上往下一直进行release
操作,指导遇到token
为止,最后判断当前 page 使用不满一半,从 child page 开始将后面所有 page 删除;当前 page 使用超过一半,从 child page 的 child page(即孙子,如果有的话)开始将后面所有的 page 删除。具体为什么要又区分不是特别理解...
到此push
与pop
就已经说完了。在我们的理解中ARC环境下编译器会自动的给我们在变量后面加上retain
,release
,autorelease
等方法,下面我们就去看下autorelease
的实现
autorelease
- (id)autorelease {
return ((id)self)->rootAutorelease();
}
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
inline id
objc_object::rootAutorelease()
{
if (isTaggedPointer()) return (id)this;
if (prepareOptimizedReturn(ReturnAtPlus1)) return (id)this;
return rootAutorelease2();
}
__attribute__((noinline,used))
id
objc_object::rootAutorelease2()
{
assert(!isTaggedPointer());
return AutoreleasePoolPage::autorelease((id)this);
}
static inline id autorelease(id obj)
{
assert(obj);
assert(!obj->isTaggedPointer());
id *dest __unused = autoreleaseFast(obj);
assert(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj);
return obj;
}
复制代码
别的先不管,我们可以看到到方法的最下面还是调用到了autoreleaseFast()
方法,这样就和上面的push
操作类似了。
TLS (Thread Local Storage)
那么事实上编译器真的只是在我们代码的后面加上了autorelease
吗?我们写份代码
Hopper Disassemebler
中进行反编译看下
发现编译器并没有给我们添加autorelease
,而是多了两个objc_autoreleaseReturnValue
与objc_retainAutoreleasedReturnValue
方法,我们一个个先看看
id
objc_autoreleaseReturnValue(id obj)
{
if (prepareOptimizedReturn(ReturnAtPlus1))
return obj;
return objc_autorelease(obj);
}
static ALWAYS_INLINE bool
prepareOptimizedReturn(ReturnDisposition disposition)
{
assert(getReturnDisposition() == ReturnAtPlus0);
if (callerAcceptsOptimizedReturn(__builtin_return_address(0))) {
if (disposition) setReturnDisposition(disposition);
return true;
}
return false;
}
复制代码
这个函数调用了prepareOptimizedReturn
,然后调用了callerAcceptsFastAutorelease
,传入一个__builtin_return_address(0)
__builtin_return_address
接收一个称为 level 的参数。这个参数定义希望获取返回地址的调用堆栈级别。例如,如果指定 level 为 0,那么就是请求当前函数的返回地址。如果指定 level 为 1,那么就是请求进行调用的函数的返回地址,依此类推链接
接下来来看 callerAcceptsFastAutorelease
这个函数(以arm64为例):
static ALWAYS_INLINE bool
callerAcceptsOptimizedReturn(const void *ra)
{
// fd 03 1d aa mov fp, fp
// arm64 instructions are well-aligned
if (*(uint32_t *)ra == 0xaa1d03fd) {
return true;
}
return false;
}
复制代码
它检查了主调方在返回值之后是否紧接着调用了objc_retainAutoreleasedReturnValue
,如果是,就知道了外部是ARC环境,反之就走没被优化的老逻辑。
简单的可以理解为,由objc_autoreleaseReturnValue
将对象放入tls(Thread Local Storage)
;而外部由objc_retainAutoreleasedReturnValue
将对象由tls
中取出,这样就不用走autoreleasepool
了,而由tls
代劳了,这样就节省了autoreleasepool
对对象的存储,清除开销了。
那也就是说ARC下只要调用方和被调方都用ARC编译时,所建立的对象都不加入
autoreleasepool
.更简单的说我们自己写的类,调用工厂方法生成对象都不会放 入autoreleasepool
.(引用iOS Objective-C底层 part3:live^ARC )
最后琐事
- 使用容器的block版本的枚举器时,内部会自动添加一个AutoreleasePool:
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 这里被一个局部@autoreleasepool包围着
}];
复制代码
这段引自sunnyxx大神的文章
不过不知道是不是我理解的问题,我在代码中没有看到block中有autoreleasepool
在main
写了如下代码
autoreleasepool
的身影
然后在AutoreleasePoolPage::push()
打上断点
可以看到enumerateObjectsWithOptions:usingBlock:
这个方法中是有push
与pop
操作的(不知道理解的对不对,如果不对,请轻喷)
存疑:
其实AutoreleasePool
还有很多可以说的,比如AutoreleasePool
是在什么时候释放的,在下功力浅薄只知道在runloop
每次循环的开始时候会去push
,结束的时候去pop
但是真的深入就不了解了,此处暂且存疑,待日后修炼归来再来解答
文章参考:
黑幕背后的Autorelease
iOS Objective-C底层 part3:live^ARC
自动释放池的前世今生