前言 本文主要分析 AutoReleasePool
以及 NSRunLoop
的底层实现
AutoReleasePool 自动释放池 自动释放池
是OC中的一种 内存自动回收机制
,它可以将加入 AutoReleasePool
中的 变量release的时机延迟
,简单来说,就是当创建一个 对象
,在正常情况下,变量会在超出其作用域的时,立即release。如果对象加入到了自动释放池中,这个对象并 不会立即释放
,会 等到runloop休眠/超出autoreleasepool{}作用域
之后才能 被释放
。其机制如下图所示
从程序启动到加载完成,主线程对应的 runloop
会处于 休眠
状态,等待用户交互来唤醒runloop
用户的每一次 交互
都会启动一次 runloop
,用于处理用户的 所有点击、触摸事件等
runloop
在 监听到交互事件
后,就会 创建
自动释放池,并将所有 延迟释放
的对象添加到自动释放池
在一次完整的runloop结束之前,会向自动释放池中所有对象 发送release消息
,然后 销毁
自动释放池
clang分析 根据之前源码分析经验,我们先通过 clang
来分析
1 2 3 4 int main(int argc, const char * argv[]) { @autoreleasepool { } }
通过clang编译成底层实现,命令为:xcrun -sdk iphonesimulator clang -arch x86_64 - rewrite-objc main.m
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 //*********__AtAutoreleasePool********** struct __AtAutoreleasePool { //构造函数 __AtAutoreleasePool() { atautoreleasepoolobj = objc_autoreleasePoolPush(); } //析构函数 ~__AtAutoreleasePool() { objc_autoreleasePoolPop(atautoreleasepoolobj); } void * atautoreleasepoolobj; }; //*********main********* int main(int argc, const char * argv[]) { { // 是一个结构体 __AtAutoreleasePool __autoreleasepool; } return 0; }
简单来说,自动释放池其本质也是一个 对象
1 2 3 @autoreleasepool {} //等价于 {__AtAutoreleasePool __autoreleasepool; }
关于涉及的构造和析构函数的调用时机,可以通过下面一个案例来验证
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 struct ZJTest{ ZJTest (){ printf("123 - %s\n", __func__); } ~ZJTest(){ printf("456 - %s\n", __func__); } }; int main(int argc, const char * argv[]) { { ZJTest test; } } //**********运行结果********** 123 - ZJTest 456 - ~ZJTest
从而可以得出,在 ZJTest
创建对象时,会自动调用 构造函数
,在出了{}作用域后,会自动调用析构函数
汇编分析
在main代码部分加断点,运行程序,并开启汇编调试
通过调试结果发现,证明了我们clang分析的结果
总结
autoreleasepool
其本质是一个 结构体对象
,一个自动释放池对象就是页,是 栈结构存储
,符合 先进后出
的原则
页的栈底是一个 56
字节大小的 空占位符
,一页总大小为 4096
字节
只有 第一页
有 哨兵
对象,最多存储 504
个对象,从第二页开始最多存储 505
个对象
autoreleasepool
在加入要释放的对象时,底层调用的是 objc_autoreleasePoolPush
方法
autoreleasepool
在调用析构函数释放时,内部的实现时调用 objc_autoreleasePoolPop
方法
底层分析 在 objc
源码中,对 AutoreleasePool
的解释如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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. 线程本地存储指向热页面,该页面存储新自动释放的对象。
通过描述。有以下几点说明:
对于 自动释放池
,我们主要关心的点有以下三点:
自动释放池是什么时候 创建
?
对象时 如何加入到自动释放池
的?
哪些对象才会加入
自动释放池?
下面带着这些问题,我们来进一步探索自动释放池的底层原理
AutoreleasePoolPage
从最初的 clang
或者 汇编
分析我们了解了自动释放池其底层是调用的 objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
两个方法,其源码实现如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 //***********push方法*********** void * objc_autoreleasePoolPush(void) { return AutoreleasePoolPage::push(); } //***********pop方法*********** void objc_autoreleasePoolPop(void *ctxt) { AutoreleasePoolPage::pop(ctxt); }
从源码中我们发现,都是调用的 AutoreleasePoolPage
的 push
和 pop
,以下是其定义,从定义中可以看出,自动释放池是一个 页
,同时也是一个 对象
,这个 页
的大小是 4096字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 //************宏定义************ #define PAGE_MIN_SIZE PAGE_SIZE #define PAGE_SIZE I386_PGBYTES #define I386_PGBYTES 4096 /* bytes per 80386 page */ //************类定义************ 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: ... //构造函数 AutoreleasePoolPage(AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(),//开始存储的位置 objc_thread_self(),//传的是当前线程,当前线程时通过tls获取的 newParent, newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1 newParent ? newParent->hiwat : 0) {...} //析构函数 ~AutoreleasePoolPage() {...} ... //页的开始位置 id * begin() {...} //页的结束位置 id * end() {...} //页是否为空 bool empty() {...} //页是否满了 bool full() {...} //页的存储是否少于一半 bool lessThanHalfFull() {...} //添加释放对象 id *add(id obj){...} //释放所有对象 void releaseAll() {...} //释放到stop位置之前的所有对象 void releaseUntil(id *stop) {...} //杀掉 void kill() {...} //释放本地线程存储空间 static void tls_dealloc(void *p) {...} //获取AutoreleasePoolPage static AutoreleasePoolPage *pageForPointer(const void *p) {...} static AutoreleasePoolPage *pageForPointer(uintptr_t p) {...} //是否有空池占位符 static inline bool haveEmptyPoolPlaceholder() {...} //设置空池占位符 static inline id* setEmptyPoolPlaceholder(){...} //获取当前操作页 static inline AutoreleasePoolPage *hotPage(){...} //设置当前操作页 static inline void setHotPage(AutoreleasePoolPage *page) {...} //获取coldPage static inline AutoreleasePoolPage *coldPage() {...} //快速释放 static inline id *autoreleaseFast(id obj){...} //添加自动释放对象,当页满的时候调用这个方法 static __attribute__((noinline)) id *autoreleaseFullPage(id obj, AutoreleasePoolPage *page) {...} //添加自动释放对象,当没页的时候使用这个方法 static __attribute__((noinline)) id *autoreleaseNoPage(id obj){...} //创建新页 static __attribute__((noinline)) id *autoreleaseNewPage(id obj) {...} public: //自动释放 static inline id autorelease(id obj){...} //入栈 static inline void *push() {...} //兼容老的 SDK 出栈方法 __attribute__((noinline, cold)) static void badPop(void *token){...} //出栈页面 template<bool allowDebug> static void popPage(void *token, AutoreleasePoolPage *page, id *stop){...} __attribute__((noinline, cold)) static void popPageDebug(void *token, AutoreleasePoolPage *page, id *stop){...} //出栈 static inline void pop(void *token){...} static void init(){...} //打印 __attribute__((noinline, cold)) void print(){...} //打印所有 __attribute__((noinline, cold)) static void printAll(){...} //打印Hiwat __attribute__((noinline, cold)) static void printHiwat(){...}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 class AutoreleasePoolPage; struct AutoreleasePoolPageData { // 用来校验AutoreleasePoolPage的结构是否完整 magic_t const magic;//16个字节 // 指向最新添加的autoreleased对象的下一个位置,初始化时指向begin() __unsafe_unretained id *next;//8字节 // 指向当前线程 pthread_t const thread;//8字节 // 指向父节点,第一个结点的parent值为nil AutoreleasePoolPage * const parent;//8字节 // 指向子节点,最后一个结点的child值为nil AutoreleasePoolPage *child;//8字节 // 表示深度,从0开始,往后递增1 uint32_t const depth;//4字节 // 表示high water mark 最大入栈数量标记 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) { } };
其中 AutoreleasePoolPageData
结构体的内存大小为 56
字节:
属性 magic
的类型是 magic_t
结构体,所占内存大小为 m[4]
,所占内存(即4*4=16
字节)
属性 next
(指针)、thread
(对象)、parent
(对象)、child
(对象)均占 8
字节(即4*8=32
字节)
属性 depth、hiwat
类型为 unit32_t
,实际类型是 unsigned int
类型,均占 4
字节(即2*4=8
字节)
objc_autoreleasePoolPush 源码分析 进入 push
源码实现,有以下逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 // 入栈 static inline void *push() { id *dest; // 判断是否有pool if (slowpath(DebugPoolAllocation)) { // Each autorelease pool starts on a new pool page.自动释放池从新池页面开始 // 如果没有,则创建 dest = autoreleaseNewPage(POOL_BOUNDARY); } else { // 压栈一个POOL_BOUNDARY,即压栈哨兵 dest = autoreleaseFast(POOL_BOUNDARY); } ASSERT(dest == EMPTY_POOL_PLACEHOLDER || *dest == POOL_BOUNDARY); return dest; }
创建页 autoreleaseNewPage
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 //创建新页 static __attribute__((noinline)) id *autoreleaseNewPage(id obj) { //获取当前操作页 AutoreleasePoolPage *page = hotPage(); //如果存在,则压栈对象 if (page) return autoreleaseFullPage(obj, page); //如果不存在,则创建页 else return autoreleaseNoPage(obj); } //******** hotPage方法 ******** //获取当前操作页 static inline AutoreleasePoolPage *hotPage() { //获取当前页 AutoreleasePoolPage *result = (AutoreleasePoolPage *) tls_get_direct(key); //如果是一个空池,则返回nil,否则,返回当前线程的自动释放池 if ((id *)result == EMPTY_POOL_PLACEHOLDER) return nil; if (result) result->fastcheck(); return result; } //******** autoreleaseNoPage方法 ******** 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; //判断是否是空占位符,如果是,则压栈哨兵标识符置为YES 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. pushExtraBoundary = true; } //如果对象不是哨兵对象,且没有Pool,则报错 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", objc_thread_self(), (void*)obj, object_getClassName(obj)); objc_autoreleaseNoPool(obj); return nil; } //如果对象是哨兵对象,且没有申请自动释放池内存,则设置一个空占位符存储在tls中,其目的是为了节省内存 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. return setEmptyPoolPlaceholder();//设置空的占位符 } // We are pushing an object or a non-placeholder'd pool. // Install the first page. //初始化第一页 AutoreleasePoolPage *page = new AutoreleasePoolPage(nil); //设置page为当前聚焦页 setHotPage(page); // Push a boundary on behalf of the previously-placeholder'd pool. //压栈哨兵的标识符为YES,则压栈哨兵对象 if (pushExtraBoundary) { //压栈哨兵 page->add(POOL_BOUNDARY); } // Push the requested object or pool. //压栈对象 return page->add(obj); }
其中 autoreleaseNoPage
方法中发现 当前线程的自动释放池
是通过 AutoreleasePoolPage
创建的,其定义中有 构造方法
,而构造方法的实现是通过父类 AutoreleasePoolPageData
的初始化方法(从上面的定义中可以得知)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 //**********AutoreleasePoolPage构造方法********** AutoreleasePoolPage(AutoreleasePoolPage *newParent) : AutoreleasePoolPageData(begin(),//开始存储的位置 objc_thread_self(),//传的是当前线程,当前线程时通过tls获取的 newParent, newParent ? 1+newParent->depth : 0,//如果是第一页深度为0,往后是前一个的深度+1 newParent ? newParent->hiwat : 0) { if (parent) { parent->check(); ASSERT(!parent->child); parent->unprotect(); //this 表示 新建页面,将当前页面的子节点 赋值为新建页面 parent->child = this; parent->protect(); } protect(); } //**********AutoreleasePoolPageData初始化方法********** 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) { }
其中 AutoreleasePoolPageData
方法传入的参数含义为:
begin()
表示 压栈
的位置(即下一个要释放对象的压栈地址)。可以通过源码调式 begin,发现其具体实现等于 页首地址+56
,其中的 56
就是结构体 AutoreleasePoolPageData
的内存大小
1 2 3 4 5 6 //********begin()******** //页的开始位置 id * begin() { //等于 首地址+56(AutoreleasePoolPage类所占内存大小) return (id *) ((uint8_t *)this+sizeof(*this)); }
objc_thread_self()
表示的是 当前线程
,而当前线程时通过tls获取的
1 2 3 4 5 6 __attribute__((const)) static inline pthread_t objc_thread_self() { //通过tls获取当前线程 return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF); }
查看自动释放池内存结构 由于在ARC模式下,是无法手动调用 autorelease
,所以将Demo切换至MRC模式(Build Settings -> Object-C Automatic Refrence Counting
设置为 NO
)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 //************打印自动释放池结构************ extern void _objc_autoreleasePoolPrint(void); //************运行代码************ int main(int argc, const char * argv[]) { @autoreleasepool { //循环创建对象,并加入自动释放池 for (int i = 0; i < 5; i++) { NSObject *objc = [[NSObject alloc] sutorelease]; } //调用 _objc_autoreleasePoolPrint(); } }
运行结果如下,发现是6个,但是我们压栈的对象其实只有5个,其中的 POOL
表示 哨兵
,即 边界
,其目的是 为了防止越界
查看自动释放池的内存结构,发现,页的首地址与 哨兵对象
相差 0x38
,转换成十进制刚好是 56
,也就是 AutoreleasePoolPage
自己本身的内存大小
将上述的测试代码的数据改为 505
,其内存结构如下,发现第一页满了,存储了 504
个要释放的对象,第二页只存储了一个
在将数据改为 505+506
,来验证第二页是否也是存储了 504
个对象
通过运行发现,第一页存储 504
,第二页存储 505
,第三页存储 2个
结论
所以通过上述测试,可以得出以下结论:
这个结论同样可以通过 AutoreleasePoolPage
中的 SIZE
来得到印证,从其定义中我们可以得出,一页的大小是 4096
字节,而在其构造函数中 对象的压栈位置
,是从 首地址+56
开始的,所以可以一页中实际可以存储 4096 - 56= 4040字节
,转换成对象时 4040/8 = 505
个,即一页最多可以 存储505个对象
,其中 第一页有哨兵对象
,只能存储 504
个对象,其结构图示如下:
面试题:哨兵在一个自动释放池有几个
压栈对象 autoreleaseFast
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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); } }
autoreleaseFullPage 方法 这个方法主要是用于判断当前页是否已经存储满了,如果当前页已经满了,通过 do-while循环
查找 子节点对应的页
,如果不存在,则 新建页
,并 压栈对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 //添加自动释放对象,当页满的时候调用这个方法 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-while遍历循环查找界面是否满了 do { //如果子页面存在,则将页面替换为子页面 if (page->child) page = page->child; //如果子页面不存在,则新建页面 else page = new AutoreleasePoolPage(page); } while (page->full()); //设置为当前操作页面 setHotPage(page); //对象压栈 return page->add(obj); }
从 autoreleasePoolPage
初始化方法中可以看出,主要是通过操作 child
对象,将 当前页的child指向新建页面
,由此可以得出 页是通过双向链表链接
add方法 这个方法主要是 添加释放对象
,其底层的实现时通过 next
指针存储释放对象,并将 next指针递增,表示下一个释放对象存储的位置,从这里可以看出 页
是通过 栈结构存储
1 2 3 4 5 6 7 8 9 10 11 12 //添加释放对象 id *add(id obj) { ASSERT(!full()); unprotect(); //传入对象存储的位置 id *ret = next; // faster than `return next-1` because of aliasing //将obj压栈到next指针位置,然后next进行++,即下一个对象存储的位置 *next++ = obj; protect(); return ret; }
autorelease底层分析 在demo中,我们通过 autorelease
方法,在MRC模式下,将对象压栈到自动释放池,下面分析其底层实现
1 2 3 4 5 6 7 8 9 10 __attribute__((aligned(16), flatten, noinline)) id objc_autorelease(id obj) { //如果不是对象,则直接返回 if (!obj) return obj; //如果是小对象,也直接返回 if (obj->isTaggedPointer()) return obj; return obj->autorelease(); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 👇 inline id objc_object::autorelease() { ASSERT(!isTaggedPointer()); //判断是否是自定义类 if (fastpath(!ISA()->hasCustomRR())) { return rootAutorelease(); } return ((id(*)(objc_object *, SEL))objc_msgSend)(this, @selector(autorelease)); } 👇 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()); //autoreleaseFast 压栈操作 id *dest __unused = autoreleaseFast(obj); ASSERT(!dest || dest == EMPTY_POOL_PLACEHOLDER || *dest == obj); return obj; }
从这里看出,无论是 压栈哨兵对象,还是普通对象
,都会来到 autorelease
方法,只是 区别标识不同
而已
objc_autoreleasePoolPop 源码分析 在 objc_autoreleasePoolPop
方法中有个参数,在clang分析时,发现传入的参数是 push
压栈后返回的哨兵对象,即 ctxt
,其目的是 避免出栈混乱,防止将别的对象出栈
进入 pop
源码实现,主要有以下几步
空页面的处理,并 根据token获取page
容错处理
通过 popPage 出栈页
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 //出栈 static inline void pop(void *token) { AutoreleasePoolPage *page; id *stop; //判断对象是否是空占位符 if (token == (void*)EMPTY_POOL_PLACEHOLDER) { //如果当是空占位符 // Popping the top-level placeholder pool. //获取当前页 page = hotPage(); if (!page) { // Pool was never used. Clear the placeholder. //如果当前页不存在,则清除空占位符 return setHotPage(nil); } // Pool was used. Pop its contents normally. // Pool pages remain allocated for re-use as usual. //如果当前页存在,则将当前页设置为coldPage,token设置为coldPage的开始位置 page = coldPage(); token = page->begin(); } else { //获取token所在的页 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 (slowpath(PrintPoolHiwat || DebugPoolAllocation || DebugMissingPools)) { return popPageDebug(token, page, stop); } //出栈页 return popPage<false>(token, page, stop); }
进入 popPage
源码,其中传入的 allowDebug
为false,则通过 releaseUntil
出栈当前页 stop
位置之前的所有对象,即出栈中的对象 发送release消息
,直到遇到传入的 哨兵对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 //出栈页面 template<bool allowDebug> static void 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 //特殊情况:调试丢失的自动释放池时删除pop(top)的所有内容 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(); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 //释放到stop位置之前的所有对象 void releaseUntil(id *stop) { // Not recursive: we don't want to blow out the stack 不是递归的:我们不想破坏堆栈 // if a thread accumulates a stupendous amount of garbage //判断下一个对象是否等于stop,如果不等于,则进入while循环 while (this->next != stop) { // Restart from hotPage() every time, in case -release // autoreleased more objects 每次从hotPage()重新启动,以防-release自动释放更多对象 //获取当前操作页面,即hot页面 AutoreleasePoolPage *page = hotPage(); // fixme I think this `while` can be `if`, but I can't prove it //如果当前页是空的 while (page->empty()) { //将page赋值为父节点页 page = page->parent; //并设置当前页为父节点页 setHotPage(page); } page->unprotect(); //next进行--操作,即出栈 id obj = *--page->next; //将页索引位置置为SCRIBBLE,表示已经被释放 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 }
进入 kill
实现,主要是销毁当前页,将 当前页赋值为父节点页
,并将 父节点页的child对象指针置为nil
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 //销毁 void kill() { // Not recursive: we don't want to blow out the stack // if a thread accumulates a stupendous amount of garbage AutoreleasePoolPage *page = this; //获取最后一个页 while (page->child) page = page->child; AutoreleasePoolPage *deathptr; do { deathptr = page; //子节点 变成 父节点 page = page->parent; if (page) { page->unprotect(); //子节点为nil page->child = nil; page->protect(); } delete deathptr; } while (deathptr != this); }
总结 通过上面的分析,针对自动释放池的push和pop,总结如下:
在自动释放池的 压栈(即push)操作中
当没有pool,即只有空占位符(存储在tls中)时,则创建页,压栈哨兵对象
在页中 压栈普通对象 主要是通过 next 指针 递增 进行的
当 页满 了时,需要设置页的 child 对象为 新建页
所以,综上所述,autorelease 和 objc_autoreleasePush 的整体底层的流程如下图所示
在自动释放池的 出栈(即 pop
)操作中
在页中 出栈普通对象
主要是通过 next
指针 递减
进行的
当 页空
了时,需要赋值页的 parent
对象为 当前页
综上所述,objc_autoreleasePoolPop
出栈的流程如下所示
Runloop 对于Runloop,主要关心的点有以下几个
runoop是什么?
runloop和线程是什么关系?
runloop是什么时候创建的?
Runloop介绍 Runloop
是时间接收和分发机制的一个实现,是线程相关的基础框架的一部分,一个Runloop就是一个时间处理的循环
,用来不停的调度工作以及处理输入事件。
Runloop
本质是一个 do-while循环
,没事做就休息,来活了就干活。与普通的 while
循环时有区别的,普通的 while循环
会导致CPU进入 忙等待状态
,即一直消耗cpu,而Runloop则不会,Runloop是一种 闲等待
,即Runloop具备 休眠功能
。
Runloop的作用
Runloop源码分析 Runloop源码的下载地址 ,在其中找到最新版本下载即可
Runloop和线程的关系 一般在日常开发中,对于 Runloop的获取
主要有以下两种方式
1 2 3 4 // 主运行循环 CFRunLoopRef mainRunloop = CFRunLoopGetMain(); // 当前运行循环 CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
1 2 3 4 5 6 7 CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed // pthread_main_thread_np 主线程 if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 // should only be called by Foundation // t==0 is a synonym for "main thread" that always works CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { //如果t不存在,则标记为主线程(即默认情况,默认是主线程) if (pthread_equal(t, kNilPthreadT)) { t = pthread_main_thread_np(); } __CFSpinLock(&loopsLock); if (!__CFRunLoops) { __CFSpinUnlock(&loopsLock); //创建全局字典,标记为kCFAllocatorSystemDefault CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); //通过主线程 创建主运行循环 CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); //利用dict,进行key-value绑定操作,即可以说明,线程和runloop是一一对应的 // dict : key value CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFSpinLock(&loopsLock); } //通过其他线程获取runloop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFSpinUnlock(&loopsLock); if (!loop) { //如果没有获取到,则新建一个运行循环 CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFSpinLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); if (!loop) { //将新建的runloop 与 线程进行key-value绑定 CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may end up taking it __CFSpinUnlock(&loopsLock); CFRelease(newLoop); } if (pthread_equal(t, pthread_self())) { _CFSetTSD(__CFTSDKeyRunLoop, (void *)loop, NULL); if (0 == _CFGetTSD(__CFTSDKeyRunLoopCntr)) { _CFSetTSD(__CFTSDKeyRunLoopCntr, (void *)(PTHREAD_DESTRUCTOR_ITERATIONS-1), (void (*)(void *))__CFFinalizeRunLoop); } } return loop; }
从这里可以说明,Runloop
只有 两种
,一种是 主线程的
,一种是 其他线程的
。即Runloop和线程是一一对应的
Runloop的创建
进入 __CFRunLoopCreate
源码,其中主要是对runloop属性的赋值操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 static CFRunLoopRef __CFRunLoopCreate(pthread_t t) { CFRunLoopRef loop = NULL; CFRunLoopModeRef rlm; uint32_t size = sizeof(struct __CFRunLoop) - sizeof(CFRuntimeBase); loop = (CFRunLoopRef)_CFRuntimeCreateInstance(kCFAllocatorSystemDefault, __kCFRunLoopTypeID, size, NULL); //如果loop为空,则直接返回NULL if (NULL == loop) { return NULL; } //runloop属性配置 (void)__CFRunLoopPushPerRunData(loop); __CFRunLoopLockInit(&loop->_lock); loop->_wakeUpPort = __CFPortAllocate(); if (CFPORT_NULL == loop->_wakeUpPort) HALT; __CFRunLoopSetIgnoreWakeUps(loop); loop->_commonModes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); CFSetAddValue(loop->_commonModes, kCFRunLoopDefaultMode); loop->_commonModeItems = NULL; loop->_currentMode = NULL; loop->_modes = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); loop->_blocks_head = NULL; loop->_blocks_tail = NULL; loop->_counterpart = NULL; loop->_pthread = t; #if DEPLOYMENT_TARGET_WINDOWS loop->_winthread = GetCurrentThreadId(); #else loop->_winthread = 0; #endif rlm = __CFRunLoopFindMode(loop, kCFRunLoopDefaultMode, true); if (NULL != rlm) __CFRunLoopModeUnlock(rlm); return loop; }
进入 CFRunLoopRef
的定义,根据定义得知,其实 RunLoop也是一个对象
。是 __CFRunLoop
结构体的 指针类型
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 typedef struct __CFRunLoop * CFRunLoopRef; 👇 struct __CFRunLoop { CFRuntimeBase _base; pthread_mutex_t _lock; /* locked for accessing mode list */ __CFPort _wakeUpPort; // used for CFRunLoopWakeUp Boolean _unused; volatile _per_run_data *_perRunData; // reset for runs of the run loop pthread_t _pthread; uint32_t _winthread; CFMutableSetRef _commonModes; CFMutableSetRef _commonModeItems; CFRunLoopModeRef _currentMode; CFMutableSetRef _modes; struct _block_item *_blocks_head; struct _block_item *_blocks_tail; CFTypeRef _counterpart; };
从定义中可以得出,一个RunLoop依赖于多个Mode
,意味着一个RunLoop需要处理多个事务,即 一个Mode对应多个item
,而一个item包含了 timer、source、observer
,如下图所示:
Mode类型 其中 mode
在苹果文档中提及的有五个,而在iOS中公开暴露出来的只有 NSDefaultRunLoopMode
和 NSRunLoopCommonModes
。NSRunLoopCommonModes
实际上是一个 Mode
的集合,默认包括 NSDefaultRunLoopMode
和 NSEventTrackingRunLoopMode
NSDefaultRunLoopMode
:默认的 mode
,正常情况下都是在这个mode
NSConnectionReplyMode
NSModalPanelRunLoopMode
NSEventTrackingRunLoopMode
:使用这个mode去跟踪来自用户交互的事件(比如UITableView上下滑动)
NSRunLoopCommonModes
:伪模式,灵活性更好
Source & Timer & Observer
source
: 表示可以 唤醒runloop的一些事件
,例如用户点击了屏幕,就会创建一个runloop,主要分为 source0
和 source1
source0
表示 非系统事件
,即用户自定义的事件
source1
表示 系统事件
,主要负责底层的通讯,具备唤醒能力
timer
: 就是常用的 NSTimer
定时器一类
observer
:主要用于监听runloop的状态变化,并作出一定响应,主要有以下一些状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { //进入RunLoop kCFRunLoopEntry = (1UL << 0), //即将处理Timers kCFRunLoopBeforeTimers = (1UL << 1), //即将处理Source kCFRunLoopBeforeSources = (1UL << 2), //即将进入休眠 kCFRunLoopBeforeWaiting = (1UL << 5), //被唤醒 kCFRunLoopAfterWaiting = (1UL << 6), //退出RunLoop kCFRunLoopExit = (1UL << 7), kCFRunLoopAllActivities = 0x0FFFFFFFU };
验证:runloop和mode是一对多 下面,通过上面的代码调式来验证我们上面提及的关系
从这里,可以说明,runloop在运行时的mode只有一个
获取 mainRunloop
的所有模型,即 po CFRunLoopCopyAllModes(mainRunloop)
从结果可以验证 runloop
和 CFRunloopMode
具有 一对多
的关系
验证:mode和item也是一对多
在上述代码中,加断点,通过bt查看堆栈信息,从这里看出timer的item类型如下所示:
在 runloop
源码中查看 item
类型,有以下几种
block应用:CFRUNLOOP_IS_CALLING_OUT_TO_A_BLOCK
调用timer:CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION
响应source0:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION
响应source1:CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION
GCD主队列:CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE
observer:CFRUNLOOP_IS_CALLING_OUT_TO_AN_OBSERVER_CALLBACK_FUNCTION
在这里以 timer
为例,一般初始化timer时,都会将timer通过 addTimer:forMode:
方法添加到runloop中,于是在源码中查找 addTimer
的相关方法,即 CFRunLoopAddTimer
方法,其源码实现如下,其实现主要判断是否是 kCFRunLoopCommonModes
,然后查找runloop的mode进行匹配处理
其中 kCFRunLoopCommonModes
不是一种模式,是一种抽象的 伪模式
,比 defaultMode
更加灵活
通过 CFSetAddValue(rl->_commonModeItems, rlt);
可以得知,runloop
与 mode
是 一对多
的,同时可以得出 mode
与 item
也是 一对多
的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 void CFRunLoopAddTimer(CFRunLoopRef rl, CFRunLoopTimerRef rlt, CFStringRef modeName) { CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return; if (!__CFIsValid(rlt) || (NULL != rlt->_runLoop && rlt->_runLoop != rl)) return; __CFRunLoopLock(rl); // 重点 : kCFRunLoopCommonModes if (modeName == kCFRunLoopCommonModes) { //如果是kCFRunLoopCommonModes 类型 CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL; if (NULL == rl->_commonModeItems) { rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks); } //runloop与mode 是一对多的, mode与item也是一对多的 CFSetAddValue(rl->_commonModeItems, rlt); if (NULL != set) { CFTypeRef context[2] = {rl, rlt}; /* add new item to all common-modes */ //执行 CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context); CFRelease(set); } } else { //如果是非commonMode类型 //查找runloop的模型 CFRunLoopModeRef rlm = __CFRunLoopFindMode(rl, modeName, true); if (NULL != rlm) { if (NULL == rlm->_timers) { CFArrayCallBacks cb = kCFTypeArrayCallBacks; cb.equal = NULL; rlm->_timers = CFArrayCreateMutable(kCFAllocatorSystemDefault, 0, &cb); } } //判断mode是否匹配 if (NULL != rlm && !CFSetContainsValue(rlt->_rlModes, rlm->_name)) { __CFRunLoopTimerLock(rlt); if (NULL == rlt->_runLoop) { rlt->_runLoop = rl; } else if (rl != rlt->_runLoop) { __CFRunLoopTimerUnlock(rlt); __CFRunLoopModeUnlock(rlm); __CFRunLoopUnlock(rl); return; } // 如果匹配,则将runloop加进去,而runloop的执行依赖于 [runloop run] CFSetAddValue(rlt->_rlModes, rlm->_name); __CFRunLoopTimerUnlock(rlt); __CFRunLoopTimerFireTSRLock(); __CFRepositionTimerInMode(rlm, rlt, false); __CFRunLoopTimerFireTSRUnlock(); if (!_CFExecutableLinkedOnOrAfter(CFSystemVersionLion)) { // Normally we don't do this on behalf of clients, but for // backwards compatibility due to the change in timer handling... if (rl != CFRunLoopGetCurrent()) CFRunLoopWakeUp(rl); } } if (NULL != rlm) { __CFRunLoopModeUnlock(rlm); } } __CFRunLoopUnlock(rl); }
RunLoop执行 众所周知,runloop的执行依赖于 run
方法,从下面的堆栈信息中可以看出,其底层执行的是 __CFRunLoopRun
方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 /* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode) { ... do{ ... //通知 Observers: 即将处理timer事件 if (rlm->_observerMask & kCFRunLoopBeforeTimers) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); //通知 Observers: 即将处理Source事件 if (rlm->_observerMask & kCFRunLoopBeforeSources) __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources); //处理Blocks __CFRunLoopDoBlocks(rl, rlm); //处理sources0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); //处理sources0返回为YES if (sourceHandledThisLoop) { // 处理Blocks __CFRunLoopDoBlocks(rl, rlm); } ... //如果是timer else if (modeQueuePort != MACH_PORT_NULL && livePort == modeQueuePort) { CFRUNLOOP_WAKEUP_FOR_TIMER(); if (!__CFRunLoopDoTimers(rl, rlm, mach_absolute_time())) { // Re-arm the next timer, because we apparently fired early __CFArmNextTimerInMode(rlm, rl); } } ... //如果是source1 CFRunLoopSourceRef rls = __CFRunLoopModeFindSourceForMachPort(rl, rlm, livePort); if (rls) { #if DEPLOYMENT_TARGET_MACOSX || DEPLOYMENT_TARGET_EMBEDDED || DEPLOYMENT_TARGET_EMBEDDED_MINI mach_msg_header_t *reply = NULL; sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) || sourceHandledThisLoop; if (NULL != reply) { (void)mach_msg(reply, MACH_SEND_MSG, reply->msgh_size, 0, MACH_PORT_NULL, 0, MACH_PORT_NULL); CFAllocatorDeallocate(kCFAllocatorSystemDefault, reply); } #elif DEPLOYMENT_TARGET_WINDOWS sourceHandledThisLoop = __CFRunLoopDoSource1(rl, rlm, rls) || sourceHandledThisLoop; #endif } ... }while (0 == retVal); ... }
进入 __CFRunLoopDoTimers 源码,主要是通过for循环,对单个timer进行处理
1 2 3 4 5 6 7 8 9 10 static Boolean __CFRunLoopDoTimers(CFRunLoopRef rl, CFRunLoopModeRef rlm, uint64_t limitTSR) { /* DOES CALLOUT */ ... //循环遍历,做下层单个timer的执行 for (CFIndex idx = 0, cnt = timers ? CFArrayGetCount(timers) : 0; idx < cnt; idx++) { CFRunLoopTimerRef rlt = (CFRunLoopTimerRef)CFArrayGetValueAtIndex(timers, idx); Boolean did = __CFRunLoopDoTimer(rl, rlm, rlt); timerHandled = timerHandled || did; } ... }
进入 __CFRunLoopDoTimer
源码,主要逻辑是 timer
执行完毕后,会主动调用 __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__
函数,正好与timer堆栈调用的一致
1 2 3 4 5 6 // mode and rl are locked on entry and exit static Boolean __CFRunLoopDoTimer(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFRunLoopTimerRef rlt) { ... __CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION__(rlt->_callout, rlt, context_info); ... }
timer执行总结
为自定义的timer,设置mode,并将其加入 runloop 中
在runloop的 run 方法执行时,会调用 __CFRunLoopDoTimers 执行所有的timer
在 __CFRunLoopDoTimers 方法中,会通过for循环执行单个timer的操作
在 __CFRunLoopDoTimer 方法中,timer执行完毕后,会执行对应的timer回调函数
以上,是针对 timer
的执行分析,对于 observer、block、source0、source1
,其执行原理与timer是类似的,这里就不再重复说明,一下是 苹果官方文档 针对runloop处理不同源的图示:
RunLoop底层管理 从上述的堆栈信息中可以看出,run在底层的实现路径为 CFRunLoopRun -> CFRunLoopRun -> __CFRunLoopRun
1 2 3 4 5 6 7 8 void CFRunLoopRun(void) { /* DOES CALLOUT */ int32_t result; do { // 1.0e10 : 科学技术 1*10^10 result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false); CHECK_FOR_FORK(); } while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result); }
其伪代码表示如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 SInt32 CFRunLoopRunSpecific(CFRunLoopRef rl, CFStringRef modeName, CFTimeInterval seconds, Boolean returnAfterSourceHandled) { /* DOES CALLOUT */ CHECK_FOR_FORK(); if (__CFRunLoopIsDeallocating(rl)) return kCFRunLoopRunFinished; __CFRunLoopLock(rl); //首先根据modeName找到对应mode CFRunLoopModeRef currentMode = __CFRunLoopFindMode(rl, modeName, false); // 通知 Observers: RunLoop 即将进入 loop。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopEntry); // 内部函数,进入loop result = __CFRunLoopRun(rl, currentMode, seconds, returnAfterSourceHandled, previousMode); // 通知 Observers: RunLoop 即将退出。 __CFRunLoopDoObservers(rl, currentMode, kCFRunLoopExit); return result; }
进入 __CFRunLoopRun
源码,由于这部分代码较多,于是这里用伪代码代替。其主要逻辑是 根据不同的事件源进行不同的处理
,当RunLoop休眠时,可以通过相应的事件唤醒RunLoop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 //核心函数 /* rl, rlm are locked on entrance and exit */ static int32_t __CFRunLoopRun(CFRunLoopRef rl, CFRunLoopModeRef rlm, CFTimeInterval seconds, Boolean stopAfterHandle, CFRunLoopModeRef previousMode){ //通过GCD开启一个定时器,然后开始跑圈 dispatch_source_t timeout_timer = NULL; ... dispatch_resume(timeout_timer); int32_t retVal = 0; //处理事务,即处理items do { // 通知 Observers: 即将处理timer事件 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeTimers); // 通知 Observers: 即将处理Source事件 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeSources) // 处理Blocks __CFRunLoopDoBlocks(rl, rlm); // 处理sources0 Boolean sourceHandledThisLoop = __CFRunLoopDoSources0(rl, rlm, stopAfterHandle); // 处理sources0返回为YES if (sourceHandledThisLoop) { // 处理Blocks __CFRunLoopDoBlocks(rl, rlm); } // 判断有无端口消息(Source1) if (__CFRunLoopWaitForMultipleObjects(NULL, &dispatchPort, 0, 0, &livePort, NULL)) { // 处理消息 goto handle_msg; } // 通知 Observers: 即将进入休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopBeforeWaiting); __CFRunLoopSetSleeping(rl); // 等待被唤醒 __CFRunLoopServiceMachPort(waitSet, &msg, sizeof(msg_buffer), &livePort, poll ? 0 : TIMEOUT_INFINITY); // user callouts now OK again __CFRunLoopUnsetSleeping(rl); // 通知 Observers: 被唤醒,结束休眠 __CFRunLoopDoObservers(rl, rlm, kCFRunLoopAfterWaiting); handle_msg: if (被timer唤醒) { // 处理Timers __CFRunLoopDoTimers(rl, rlm, mach_absolute_time()); }else if (被GCD唤醒){ // 处理gcd __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__(msg); }else if (被source1唤醒){ // 被Source1唤醒,处理Source1 __CFRunLoopDoSource1(rl, rlm, rls, msg, msg->msgh_size, &reply) } // 处理block __CFRunLoopDoBlocks(rl, rlm); if (sourceHandledThisLoop && stopAfterHandle) { retVal = kCFRunLoopRunHandledSource;//处理源 } else if (timeout_context->termTSR < mach_absolute_time()) { retVal = kCFRunLoopRunTimedOut;//超时 } else if (__CFRunLoopIsStopped(rl)) { __CFRunLoopUnsetStopped(rl); retVal = kCFRunLoopRunStopped;//停止 } else if (rlm->_stopped) { rlm->_stopped = false; retVal = kCFRunLoopRunStopped;//停止 } else if (__CFRunLoopModeIsEmpty(rl, rlm, previousMode)) { retVal = kCFRunLoopRunFinished;//结束 } }while (0 == retVal); return retVal; }
所以,综上所述,RunLoop的执行流程,如下所示:
相关面试题 AutoreleasePool相关 面试题1:临时变量什么时候释放
面试题2:AutoreleasePool原理
自动释放池的本质是一个 autoreleasePoolPage结构体对象
,是一个 栈结构存储的页
,每一个 autoreleasePoolPage
都是以 双向链表
的形式链接
自动释放池的 压栈
和 出栈
主要是通过结构体的 构造函数
和 析构函数
调用底层的 objc_autoreleasePoolPush
和 objc_autoreleasePoolPop
,实际上调用的是 autoreleasePoolPage
的 push
和 pop
两个方法
每次调用 push
操作其实就是创建一个新的 autoreleasePoolPage
,而 autoreleasePoolPage
的具体操作就是插入一个 POOL_BOUNDARY
,并返回插入 POOL_BOUNDARY
的内存地址。而 push
内部调用 autoreleaseFast
方法处理,主要有以下三种情况
当 page存在且不满
时,调用add方法将对象添加至page的next指针处,并next递增
当 page存在且已满
时,调用 autoreleaseFullPage
初始化一个新的page,然后调用add方法将对象添加至page栈中
当 page不存在
时,调用 autoreleaseNoPage
创建一个hotPage,然后调用add方法将对象添加至page栈中
当执行 pop
时,会传入一个值,这个值就是push操作的返回值,即 POOL_BOUNDARY
的内存地址 token
,所以pop内部的实现就是根据token找到哨兵对象所处的page中,然后使用 objc_release
释放token之前的对象,并把next指针指到正确的位置
面试题3:autoreleasePool能否嵌套使用?
可以嵌套使用,其目的是可以 控制应用程序的内存峰值,使其不要太高
可以嵌套的原因是因为自动释放池是以栈为节点,通过双向链表的形式链接的,且是和线程一一对应的
自动释放池的 多层嵌套 其实就是不停的 push哨兵对象,在pop时,会先释放里面的,在释放外面的
面试题4:哪些对象可以加入autoreleasePool?alloc创建可以吗?
使用 alloc、new、copy
关键字生成的对象和 retain
了的对象 需要手动释放
,不会被添加到自动释放池
设置为 autorelease
的对象 不需要手动释放,会直接进入自动释放池
所有 autorelease
的对象,在出了作用域之后,会被自动添加到最近创建的自动释放池中
面试题5:autoreleasePool的释放实际是什么时候?
APP启动后,苹果主线程 runloop
里面注册了两个 observer
,其回调都是 _wrapRunLoopWithAutoreleasePoolHandler()
。
第一个 observer
监视的事件是 Entry
(即将键入loop),其回调内会调用 _objc_autoreleasePoolPush()
创建自动释放池,其 order 是 -2147483647,优先级最高,保证创建释放池发生在其他所有回调之前
第二个 observer
监视了两个事件:BeforeWaiting
(准备进入休眠)时 调用_objc_autoreleasePoolPop()
和 _objc_autoreleasePoolPush()
释放旧的池创建新的池子
。 Exit(即退出loop)时调用 _objc_autoreleasePoolPop()来释放自动释放池
。这个observer的order是214748647,优先级最低
,保证其释放池子发生在其他所有回调之后。
面试题6:thread和autoreleasePool的关系? 在 官方文档 中,找到如下说明:
1 Each thread (including the main thread) maintains its own stack of NSAutoreleasePool objects (see Threads). As new pools are created, they get added to the top of the stack. When pools are deallocated, they are removed from the stack. Autoreleased objects are placed into the top autorelease pool for the current thread. When a thread terminates, it automatically drains all of the autorelease pools associated with itself.
大致意思是:
每个线程
,包括 主线程
在内都维护了 自己的自动释放池堆栈结构
新的
自动释放池在被创建时,会被添加到 栈顶
,当自动释放池 销毁时
,会 从栈中移除
对于 当前线程
来说,会 将自动释放池的对象
放入自动释放池的 栈顶
,在线程停止时,会 自动释放掉与该线程相关联的所有自动释放池
总结: 每个线程都有与之关联的自动释放池堆栈结构,新的pool在创建时会被压栈到栈顶,pool销毁时,会被出栈,对于当前线程来说,释放对象会被压栈到栈顶,线程停止时,会自动释放与之相关联的自动释放池
面试题7:runloop和autorelease的关系 在 官方文档 中,找到如下说明
1 The Application Kit creates an autorelease pool on the main thread at the beginning of every cycle of the event loop, and drains it at the end, thereby releasing any autoreleased objects generated while processing an event.
大致意思如下:
RunLoop相关 面试题1:当前有个子线程,子线程中有个timer,timer是否能够执行,并进行持续大打印? 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 ZJThread *thread = [[ZJThread alloc] initWithBlock:^{ // thread.name = nil 因为这个变量只是捕捉 // ZJThread *thread = nil // thread = 初始化 捕捉一个nil进来 NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]); [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"hello word"); // 退出线程--结果runloop也停止了 if (self.isStopping) { [NSThread exit]; } }]; }]; thread.name = @"lgcode.com"; [thread start];
不可以,因为 子线程的runloop默认是不启动的,需要 runloop run 启动,需要将上述代码改成下面:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 ZJThread *thread = [[ZJThread alloc] initWithBlock:^{ // thread.name = nil 因为这个变量只是捕捉 // ZJThread *thread = nil // thread = 初始化 捕捉一个nil进来 NSLog(@"%@---%@",[NSThread currentThread],[[NSThread currentThread] name]); [NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) { NSLog(@"hello word"); // 退出线程--结果runloop也停止了 if (self.isStopping) { [NSThread exit]; } }]; [[NSRunLoop currentRunLoop] run]; }]; thread.name = @"lgcode.com"; [thread start];
面试题2:runloop和线程的关系
每个线程都有一个与之对应的runloop,所以 runloop与线程是一一对应的
,其绑定关系通过一个 全局的Dictionary存储
,线程为 key
,runloop为 value
线程中的runloop主要用来里线程的,当线程runloop开启后,会在执行完任务后进行休眠状态,当有事件触发唤醒时,又开始工作,即 有活时干活,没活就休息
。
主线程
的 runloop
是 默认开启的
,在程序启动之后,会一直运行,不会退出
其他线程的 runloop默认是不开启的
,如果需要,则手动开启
面试题3:NSRunLoop 和 CFRunLoopRef区别
面试题4:RunLoop的mode作用是什么? mode 主要是用于指定runloop中事件优先级的
面试题5:以 +scheduledTimerWithTimeInterval: 的方式触发的timer,在滑动页面上的利润表时,timer会暂停回调,为什么?如何解决?