Autoreleasepool
Autoreleasepool的结构
我们的main函数中定义一个自动释放池
int main(int argc, const char * argv[]) {
@autoreleasepool {
}
return 0;
}
然后通过clang查看源码实现
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
}
可以看到我们的@autoreleasepool
转换成了__AtAutoreleasePool
,__AtAutoreleasePool
的实现如下:
struct __AtAutoreleasePool {
__AtAutoreleasePool() {atautoreleasepoolobj = objc_autoreleasePoolPush();}
~__AtAutoreleasePool() {objc_autoreleasePoolPop(atautoreleasepoolobj);}
void * atautoreleasepoolobj;
};
__AtAutoreleasePool
有两个方法,一个是push(),一个是pop()。
通过符号断点的方式可以确定objc_autoreleasePoolPush
在objc的源码中,然后我们找到对应的源码:
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.
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.
**********************************************************************/
class AutoreleasePoolPage : private AutoreleasePoolPageData
通过注释我们可以发现:
- AutoreleasePoolPage是个栈的结构,也就是先进后出;
- AutoreleasePoolPage中的节点有两种:要释放的对象以及pool boundary(也就是边界值)。
- page会组成一个双向链接的结构;
- tls 线程存储;
然后看下AutoreleasePoolPageData
的实现:
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
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)
{
}
};
next是定位偏移;thread为关联的线程;parent为父节点;child为子节点;
下面我们看下AutoreleasePoolPage的初始化方法:
AutoreleasePoolPage(AutoreleasePoolPage *newParent) :
AutoreleasePoolPageData(begin(),
objc_thread_self(),
newParent,
newParent ? 1+newParent->depth : 0,
newParent ? newParent->hiwat : 0)
{
if (parent) {
parent->check();
ASSERT(!parent->child);
parent->unprotect();
parent->child = this;
parent->protect();
}
protect();
}
可见,初始化方法中调用了AutoreleasePoolPageData
的初始化方法,传入了begin()。begin()就是AutoreleasePoolPage
的地址除去上面的那个magic、next、thread、parrent、child等属性后的位置。也就是开始存储pointer的地方了。
自动释放池的结构如下,每个AutoreleasePoolPage作为一个双向链表的节点。AutoreleasePoolPage的头部位置存储着一些属性:magic、next等。
下面我们向自动释放池中添加一些对象,然后打印一下
int main(int argc, const char * argv[]) {
@autoreleasepool {
for (int i = 0; i<5; i++) {
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"%@", obj);
}
_objc_autoreleasePoolPrint();
}
return 0;
}
打印结果如下:
objc[18996]: ##############
objc[18996]: AUTORELEASE POOLS for thread 0x1000d1dc0
objc[18996]: 6 releases pending.
objc[18996]: [0x100804000] ................ PAGE (hot) (cold)
objc[18996]: [0x100804038] ################ POOL 0x100804038
objc[18996]: [0x100804040] 0x1006b8ee0 NSObject
objc[18996]: [0x100804048] 0x101100200 NSObject
objc[18996]: [0x100804050] 0x101300020 NSObject
objc[18996]: [0x100804058] 0x10063d6c0 NSObject
objc[18996]: [0x100804060] 0x1006b5e60 NSObject
objc[18996]: ##############
地址0x100804000到0x100804038存储的是AutoreleasePoolPage的一些头部属性信息。然后从0x100804038开始存储了6个对象,但是我们自己是创建了5个对象。还有一个就是我们哨兵对象,也就是边界对对象。这个边界对象是自动释放池的边界,当嵌套使用自动释放池的时候,每个自动释放池都会有对象的边界。
当我们自动释放池的对象超过505的时候,就会进行分页,创建新的AutoreleasePoolPage。也就是说一个AutoreleasePoolPage只能装505个对象,注意因为第一个需要有个边界对象,所以只能有504个对象。
Autoreleasepool的压栈
首先找到objc_autoreleasePoolPush
函数,调用了push函数
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
push函数中调用autoreleaseFast
函数
static inline void *push()
{
id *dest;
if (slowpath(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;
}
autoreleaseFast
函数中,如果存在page并且page没有满,就会调用page->add(obj)
函数向page中添加对象;如果page已满了,调用autoreleaseFullPage
;如果还没有创建page,那么使用autoreleaseNoPage
进行创建。
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);
}
}
- 首先我们来看下创建page的函数
autoreleaseNoPage
// Install the first page.
AutoreleasePoolPage *page = new AutoreleasePoolPage(nil);
setHotPage(page);
// Push a boundary on behalf of the previously-placeholder'd pool.
if (pushExtraBoundary) {
page->add(POOL_BOUNDARY);
}
// Push the requested object or pool.
return page->add(obj);
主要就是创建了Page,然后设置为聚焦page,如果是第一个page就先插入边界,然后再插入对象。
- 然后看下
page->add(obj)
函数向存在的page中添加对象。
id *add(id obj)
{
ASSERT(!full());
unprotect();
id *ret = next; // faster than `return next-1` because of aliasing
*next++ = obj;
protect();
return ret;
}
很简单,就是移动next的位置,指向obj。
- 最后我们看下page满的情况:
autoreleaseFullPage
函数实现
do {
if (page->child) page = page->child;
else page = new AutoreleasePoolPage(page);
} while (page->full());
setHotPage(page);
return page->add(obj);
一个doWhile循环,查找child,也就是双链表中的那个节点。知道找到没有装满的page,如果找到最后找不到,就创建一个新的。然后设置为hotPage,最后添加对象进去。
上面就是我们的压栈过程。
Autoreleasepool的出栈
首先是objc_autoreleasePoolPop
函数,该函数的参数是调用pagePush的时候返回的对象。也就是该page的上下文标识。
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
然后调用pop
函数,pop函数的主要内容为:
AutoreleasePoolPage *page;
id *stop;
page = pageForPointer(token);
stop = (id *)token;
return popPage(token, page, stop);
根据上下文找到对应的page,然后调用popPage函数。popPage函数的试下如下:
popPage(void *token, AutoreleasePoolPage *page, id *stop)
{
if (allowDebug && PrintPoolHiwat) printHiwat();
page->releaseUntil(stop);
// memory: delete empty children
if (allowDebug && DebugPoolAllocation && page->empty()) {
// special case: delete everything during page-per-pool debugging
AutoreleasePoolPage *parent = page->parent;
page->kill();
setHotPage(parent);
} else if (allowDebug && 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();
}
}
}
调用page->releaseUntil(stop);
释放对象。后面就是对page的kill处理以及setHotPage处理。下面看下releaseUntil
函数:
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);
}
一个while循,移动next指,依次给对象发送release消息,如果page为空,就开始处理父节点,设置父节点为hotPage,直到遍历到边界。
Autoreleasepool的嵌套
下面的代码中,我们开启了一个子线程,子线程会自动创建一个自动释放池。
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"objc = %@",objc);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"\n\n obj = %@",obj);
_objc_autoreleasePoolPrint();
});
_objc_autoreleasePoolPrint();
}
然后我们得到下面的打印结果:
2020-03-22 22:03:55.610864+0800 KCObjcTest[20399:607121] objc =
objc[20399]: ##############
objc[20399]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[20399]: 2 releases pending.
objc[20399]: [0x100812000] ................ PAGE (hot) (cold)
objc[20399]: [0x100812038] ################ POOL 0x100812038
objc[20399]: [0x100812040] 0x100686c80 NSObject
objc[20399]: ##############
2020-03-22 22:03:55.612082+0800 KCObjcTest[20399:607493]
obj =
objc[20399]: ##############
objc[20399]: AUTORELEASE POOLS for thread 0x70000e5c0000
objc[20399]: 2 releases pending.
objc[20399]: [0x102802000] ................ PAGE (hot) (cold)
objc[20399]: [0x102802038] ################ POOL 0x102802038
objc[20399]: [0x102802040] 0x1010026e0 NSObject
objc[20399]: ##############
因为自动释放池是和线程相关的,所以主线程和子线程都会创建对应线程的AutoReleasePool,然后分别存储一个边界对象和一个正常的对象。
下面我们在子线程中再次手动创建一个autoreleasepool,那么子线程就会有两个autoreleasepool了。
@autoreleasepool {
// insert code here...
// 1 + 504 + 505 + 505
NSObject *objc = [[NSObject alloc] autorelease];
NSLog(@"objc = %@",objc);
dispatch_async(dispatch_get_global_queue(0, 0), ^{
@autoreleasepool {
NSObject *obj = [[NSObject alloc] autorelease];
NSLog(@"\n\n obj = %@",obj);
_objc_autoreleasePoolPrint();
}
});
_objc_autoreleasePoolPrint();
}
打印结果如下:
2020-03-22 22:07:02.159829+0800 KCObjcTest[20445:609384] objc =
objc[20445]: ##############
objc[20445]: AUTORELEASE POOLS for thread 0x1000d2dc0
objc[20445]: 2 releases pending.
2020-03-22 22:07:02.161109+0800 KCObjcTest[20445:609762]
obj =
objc[20445]: [0x102803000] ................ PAGE (hot) (cold)
objc[20445]: [0x102803038] ################ POOL 0x102803038
objc[20445]: [0x102803040] 0x100712a10 NSObject
objc[20445]: ##############
objc[20445]: ##############
objc[20445]: AUTORELEASE POOLS for thread 0x700007752000
objc[20445]: 3 releases pending.
objc[20445]: [0x101801000] ................ PAGE (hot) (cold)
objc[20445]: [0x101801038] ################ POOL 0x101801038
objc[20445]: [0x101801040] ################ POOL 0x101801040
objc[20445]: [0x101801048] 0x100644f60 NSObject
objc[20445]: ##############
Program ended with exit code: 0
可见子线程中的page中变成了3个对象,有两个边界。分别标识这两个autoreleasepool的作用域。但是一个线程值创建了一个page。
RunLoop
RunLoop的底层实现是个doWhile循环。
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
依赖于RunLoop的六大事件:
我们通过CFRunLoopGetMain
来获取runLoop。
CFRunLoopRef CFRunLoopGetMain(void) {
CHECK_FOR_FORK();
static CFRunLoopRef __main = NULL; // no retain needed
if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed
return __main;
}
_CFRunLoopGet0
的主要实现:创建一个dict,然后将线程和runloop对应着放到dict中。所以说RunLoop和线程是一一对应的。
CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks);
// 进行绑定 dict[@"pthread_main_thread_np"] = mainLoop
CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop);
CFRunLoopRef newLoop = __CFRunLoopCreate(t);
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
RunLoop是个结构体,包含的以下内容:
pthread_t _pthread;
CFMutableSetRef _commonModes;
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;
_pthread为绑定的线程,还有包含的modes、items。他们的关系图如下:
一个runLoop对应一个线程,对应N个Mode。一个Mode对应N个source、timer、observer。
RunLoop调用timer等事件
下面我们看下timer事件是怎么依赖于RunLoop执行的呢?
首先我们创建一个timer,然后在timer的执行block的地方打上断点,查看堆栈调用情况。
在timer的block执行之前的函数调用顺序为:
__CFRunLoopRun
,__CFRunLoopDoTimers
(timer事件可能会有很多,所以是个集合体), __CFRunLoopDoTimer
, __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
如果是其他的事件的话,流程相似,只不过是换成对应的
__CFRunLoopDoSources0
、__CFRunLoopDoSource1
、__CFRunLoopDoObservers
、__CFRunLoopDoBlocks
。
RunLoop的函数实现
- 通知观察者即将进入RunLoop;
- 使用
dispatch_source_t
构建一个定时器来控制runLoop的超时退出。CFRunLoopRun() 默认的超时时间很大,所以直接设置为不超时,此时就不需要定时器了; - 然后进入一个doWhile循环;
- 这里有个内循环,用于接收等待端口的消息。进入此循环后,线程进入休眠,直到收到新消息才跳出该循环,继续执行run loop;
调用 mach_msg 等待接受 mach_port 的消息。线程将进入休眠, 直到被下面某一个事件唤醒。
1、一个基于 port 的Source 的事件;
2、一个 Timer 到时间了;
3、RunLoop 自身的超时时间到了;
4、被其他什么调用者手动唤醒。
RunLoop循环退出的条件:
1、进入loop时标记了stopAfterHandle
参数,也就是处理完事件就返回,那么处理完成事件后直接退出;
2、timer到期了;
3、被外部调用者强制停止了,通过CFRunLoopStop
函数设置的;
4、设置停止,通过_CFRunLoopStopMode
设置停止;
5、source/timer/observer一个都没有了。