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__
- 以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中执行