AutoReleasePool 自动释放池
自动释放池 -> 内存自动回收机制 -> 变量release的时机延迟
对象 -> 正常情况下,变量会在超出其作用域的时立即release -> 加入到了自动释放池中 -> 不会立即释放,会等到runloop休眠/超出autoreleasepool作用域{}之后才会被释放
- 从程序启动到加载完成,主线程对应的runloop会处于休眠状态,等待用户交互来唤醒runloop
- 用户的每一次交互都会启动一次runloop,用于处理用户的所有点击、触摸事件等
- runloop在监听到交互事件后,就会创建自动释放池,并将所有延迟释放的对象添加到自动释放池中
- 在一次完整的runloop结束之前,会向自动释放池中所有对象发送release消息,然后销毁自动释放池
Clang分析AutoReleasePool
- __AtAutoreleasePool是一个结构体,有构造函数 + 析构函数,结构体定义的对象在作用域结束后,会自动调用析构函数
- 其中{} 是 作用域 ,优点是结构清晰,可读性强,可以及时创建销毁
//构造函数 + 析构函数举例说明
struct CJLTest{
CJLTest
(){
printf("1123 - %s\n", __func__);
}
~CJLTest(){
printf("5667 - %s\n", __func__);
}
};
int main(int argc, const char * argv[]) {
{
CJLTest test;
}
}
//**********运行结果**********
1123 - CJLTest
5667 - ~CJLTest
CJLTest创建对象时,会自动调用构造函数,在出了{}作用域后,会自动调用析构函数
汇编分析
- autoreleasepool -> 结构体对象,一个自动释放池对象就是页 -> 栈结构存储,符合先进后出的原则即可
- 页的栈底是一个56字节大小的空占位符,一页总大小为4096字节
- 只有第一页有哨兵对象,最多存储504个对象,从第二页开始最多存储505个对象
- autoreleasepool在加入要释放的对象时 -> 底层调用的是
objc_autoreleasePoolPush
方法 - autoreleasepool在调用析构函数释放时 -> 内部的实现是调用objc_autoreleasePoolPop方法
底层分析
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.
线程本地存储指向热页面,该页面存储新自动释放的对象。
对以上的解释说明:
- 自动释放池 是一个 关于指针的栈结构
- 指针是指要释放的对象或者 pool_boundary 哨兵(现在经常被称为 边界)
- 自动释放池是一个页的结构(虚拟内存中提及过) ,而且这个页是一个双向链表(表示有父节点 和 子节点,在类中提及过,即类的继承链)
- 自动释放池和线程有关系
需要注意的地方:
- 自动释放池什么时候创建?
- 对象是如何加入自动释放池的?
- 哪些对象才会加入自动释放池?
AutoreleasePoolPage
//***********push方法***********
void *
objc_autoreleasePoolPush(void)
{
return AutoreleasePoolPage::push();
}
//***********pop方法***********
void
objc_autoreleasePoolPop(void *ctxt)
{
AutoreleasePoolPage::pop(ctxt);
}
源码 -> 调用的AutoreleasePoolPage的push和pop实现,以下是其定义,从定义中可以看出,自动释放池是一个页
,同时也是一个对象
,这个页的大小是4096
字节
//************宏定义************
#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
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(){...}
- AutoreleasePoolPage -> 继承自AutoreleasePoolPageData,且该类的属性也是来自父类,以下是AutoreleasePoolPageData的定义,
- AutoreleasePoolPage对象,所以有以下一个关系链AutoreleasePoolPage -> AutoreleasePoolPageData -> AutoreleasePoolPage,从这里可以说明自动释放池 -> 页 -> 双向链表结构
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类型为uint32_t,实际类型是unsigned int类型,均占4字节(即2*4=8字节)
objc_autoreleasePoolPush 源码分析
- 判断是否为有 pool
- 如果没有,则通过autoreleaseNewPage方法创建
- 如果有,则通过autoreleaseFast压栈哨兵对象
//入栈
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;
}
1. 创建页 autoreleaseNewPage
objc_autoreleasePoolPush -> push -> autoreleaseNewPage
- 通过hotPage`获取当前页,判断当前页是否存在
- 如果存在,则通过autoreleaseFullPage方法压栈对象
- 如果不存在,则通过autoreleaseNoPage方法创建页
//创建新页
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的初始化方法
//**********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)
{
}
- begin()表示压栈的位置(即下一个要释放对象的压栈地址) -> 源码调试begin,发现其具体实现等于页首地址+56,其中的56就是结构体AutoreleasePoolPageData的内存大小
//********begin()********
//页的开始位置
id * begin() {
//等于 首地址+56(AutoreleasePoolPage类所占内存大小)
return (id *) ((uint8_t *)this+sizeof(*this));
}
- objc_thread_self() 表示的是当前线程,而当前线程时通过tls获取的
__attribute__((const))
static inline pthread_t objc_thread_self()
{
//通过tls获取当前线程
return (pthread_t)tls_get_direct(_PTHREAD_TSD_SLOT_PTHREAD_SELF);
}
- newParent表示父节点
- 后续两个参数是通过父节点的深度、最大入栈个数计算depth以及hiwat
查看自动释放池内存结构
ARC模式下 -> 无法手动调用autorelease -> 切换至MRC模式(Build Settings -> Objectice-C Automatic Reference Counting设置为NO)
//************打印自动释放池结构************
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自己本身的内存大小
结论
- 第一页可以存放504个对象,且只有第一页有哨兵,当一页压栈满了,就会开辟新的一页
- 第二页开始,最多可以存放505个对象
- 一页的大小等于 505 * 8 = 4040
这个结论同样可以通过AutoreleasePoolPage中的SIZE来得到印证,从其定义中我们可以得出,一页的大小是4096字节,而在其构造函数中对象的压栈位置,是从首地址+56开始的,所以可以一页中实际可以存储4096-56 = 4040字节,转换成对象是4040 / 8 = 505个,即一页最多可以存储505个对象,其中第一页有哨兵对象只能存储504个
问题: 哨兵在一个自动释放池有几个?
- 只有一个哨兵对象,且哨兵在第一页
- 第一页最多可以存504个对象,第二页开始最多存 505个
压栈对象 autoreleaseFast
- 获取当前操作页,并判断页是否存在以及是否满了
- 如果页存在,且未满,则通过add方法压栈对象
- 如果页存在,且满了,则通过autoreleaseFullPage方法安排新的页面
- 如果页不存在,则通过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);
}
}
autoreleaseFullPage 方法
用于判断当前页是否已经存储满了,如果当前页已经满了,通过do-while循环查找子节点对应的页,如果不存在,则新建页,并压栈对象
//添加自动释放对象,当页满的时候调用这个方法
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指针递增,表示下一个释放对象存储的位置。从这里可以看出页是通过栈结构存储
//添加释放对象
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 底层分析
- 如果不是对象 或者 是小对象,则直接返回
- 如果是对象,则调用对象的autorelease进行释放
__attribute__((aligned(16), flatten, noinline))
id
objc_autorelease(id obj)
{
//如果不是对象,则直接返回
if (!obj) return obj;
//如果是小对象,也直接返回
if (obj->isTaggedPointer()) return obj;
return obj->autorelease();
}
无论是压栈哨兵对象,还是普通对象,都会来到autoreleaseFast方法,只是区别标识不同而以
objc_autoreleasePoolPop 源码分析
objc_autoreleasePoolPop
方法中有个参数,在clang分析时,发现传入的参数是push压栈后返回的哨兵对象,即ctxt
,其目的是避免出栈混乱,防止将别的对象出栈
-
pop源码实现
- 空页面的处理,并根据token获取page
- 容错处理
- 通过popPage出栈页
进入popPage源码,其中传入的allowDebug为false,则通过releaseUntil出栈当前页stop位置之前的所有对象,即向栈中的对象发送release消息,直到遇到传入的哨兵对象
-
进入releaseUntil实现,主要是通过循环遍历,判断对象是否等于stop,其目的是释放stop之前的所有的对象
- 首先通过获取page的next释放对象(即page的最后一个对象),并对next进行递减,获取上一个对象
- 判断是否是哨兵对象,如果不是则自动调用objc_release释放
进入kill实现,主要是销毁当前页,将当前页赋值为父节点页,并将父节点页的child对象指针置为nil
总结
-
在自动释放池的压栈(即push)操作中
- 当没有pool,即只有空占位符(存储在tls中)时,则创建页,
压栈哨兵对象
- 在页中压栈普通对象主要是通过
next指针递增
进行的 - 当页满了时,需要设置页的child对象为新建页
- 当没有pool,即只有空占位符(存储在tls中)时,则创建页,
-
在自动释放池的出栈(即pop)操作中
- 在页中出栈普通对象主要是通过next指针递减进行的
- 当页空了时,需要赋值页的parent对象为当前页
RunLoop
- runloop是什么?
- runloop和线程的关系?
- runloop是什么时候创建的?
1. RunLoop介绍
runloop -> 本质是一个do-while循环 -> 与普通的while循环有区别 -> 普通的while循环会让CPU忙等(一直消耗CPU) -> 而runloop不会 -> 闲等待 -> 具备休眠功能
runloop的作用
- 保持程序的持续运行
- 处理App中的各种事件(触摸、定时器、performSelector)
- 节省cpu资源,提供程序的性能,该做事就做事,该休息就休息
runloop与线程的关系
//RunLoop获取
// 主运行循环
CFRunLoopRef mainRunloop = CFRunLoopGetMain();
// 当前运行循环
CFRunLoopRef currentRunloop = CFRunLoopGetCurrent();
// 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的创建
//其中主要是对runloop属性的赋值操作
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结构体的指针类型
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类型
- NSDefaultRunLoopMode -> 默认的mode,正常情况下都是在这个mode
- NSConnectionReplyMode
- NSModalPanelRunLoopMode
- NSEventTrackingRunLoopMode -> 使用这个Mode去跟踪来自用户交互的事件(比如UITableView上下滑动)
- NSRunLoopCommonModes -> 伪模式,灵活性更好
Source & Time & Observer
- Source -> 可以唤醒RunLoop的一些事件,例如用户点击了屏幕,就会创建一个RunLoop,主要分为Source0和Source1
- Source0 -> 用户自定义的事件
- Source1 -> 表示系统事件,主要负责底层的通讯,具备唤醒能力
- Timer -> NSTimer
- Observer -> 监听RunLoop的状态变化,并作出一定响应
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
};
model & Item
runloop 和 CFRunloopMode 具有 一对多的关系
-
在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__
- block应用:
-
以Timer为例 -> 初始化timer -> addTimer:forMode:方法添加到Runloop中 -> 在源码中查找addTimer的相关方法 -> CFRunLoopAddTimer方法 -> 其实现主要判断是否是kCFRunLoopCommonModes,然后查找runloop的mode进行匹配处理
- kCFRunLoopCommonModes 不是一种模式,是一种抽象的伪模式,比defaultMode更加灵活
- 通过CFSetAddValue(rl->_commonModeItems, rlt);可以得知,runloop与mode 是一对多的,同时可以得出mode 与 item 也是一对多的
RunLoop执行
RunLoop -> run方法 -> 底层执行的是__CFRunLoopRun方法
-
进入__CFRunLoopRun源码,针对不同的对象,有不同的处理
- 如果有observer,则调用 __CFRunLoopDoObservers
- 如果有block,则调用__CFRunLoopDoBlocks
- 如果有timer,则调用 __CFRunLoopDoTimers
- 如果是source0,则调用__CFRunLoopDoSources0
- 如果是source1,则调用__CFRunLoopDoSource1
进入__CFRunLoopDoTimers源码,主要是通过for循环,对单个timer进行处理
进入__CFRunLoopDoTimer源码,主要逻辑是timer执行完毕后,会主动调用CFRUNLOOP_IS_CALLING_OUT_TO_A_TIMER_CALLBACK_FUNCTION函数,正好与timer堆栈调用中的一致
timer执行总结
- 为自定义的timer,设置Mode,并将其加入RunLoop中
- 在RunLoop的run方法执行时,会调用__CFRunLoopDoTimers执行所有timer
- 在__CFRunLoopDoTimers方法中,会通过for循环执行单个timer的操作
- 在__CFRunLoopDoTimer方法中,timer执行完毕后,会执行对应的timer回调函数
RunLoop底层原理
run在底层的实现路径为 CFRunLoopRun -> CFRunLoopRun -> __CFRunLoopRun
- 进入CFRunLoopRun源码,其中传入的参数1.0e10(科学计数) 等于 1* e^10,用于表示超时时间
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);
}
- 进入CFRunLoopRunSpecific源码,,首先根据modeName找到对应的mode,然后主要分为三种情况
- 如果是entry,则通知observer,即将进入runloop
- 如果是exit,则通过observer,即将退出runloop
- 如果是其他中间状态,主要是通过runloop处理各种源
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
//核心函数
/* 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;
}
问题
1. 临时变量什么时候释放?
- 如果在正常情况下,一般是超出其作用域就会立即释放
- 如果将临时变量加入了自动释放池,会延迟释放,即在runloop休眠或者autoreleasepool作用域之后释放
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创建可以吗?
- 使用new、alloc、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 是 2147483647,优先级最低,保证其释放池子发生在其他所有回调之后。
6:thread 和 AutoreleasePool的关系
在官方文档中,找到如下说明
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 和 AutoreleasePool的关系
在官方文档中,找到如下说明
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在每次事件循环之前之前,会自动创建一个 autoreleasePool
- 并且会在事件循环结束时,执行drain操作,释放其中的对象
RunLoop相关
当前有个子线程,子线程中有个timer。timer是否能够执行 并进行持续的打印?
CJLThread *thread = [[CJLThread alloc] initWithBlock:^{
// thread.name = nil 因为这个变量只是捕捉
// CJLThread *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启动,需要将上述代码改成下面这样:
//改成
CJLThread *thread = [[CJLThread alloc] initWithBlock:^{
// thread.name = nil 因为这个变量只是捕捉
// CJLThread *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 区别
- NSRunLoop是基于CFRunLoopRef面向对象的API,是不安全的
- CFRunLoopRef是基于C语言,是线程安全的
4:Runloop的mode作用是什么?
- mode主要是用于指定RunLoop中事件优先级的
5:以+scheduledTimerWithTimeInterval:的方式触发的timer,在滑动页面上的列表时,timer会暂停回调, 为什么?如何解决?
- timer停止的原因是因为滑动scrollView时,主线程的RunLoop会从NSDefaultRunLoopMode切换到UITrackingRunLoopMode,而timer是添加在NSDefaultRunLoopMode。所以timer不会执行
- 将timer放入NSRunLoopCommonModes中执行