RunLoop基本作用:
1. 保持程序持续运行
2. 处理App中的各种事件
3. 节省CPU资源,提高程序性能
Runloop, 直译过来就是运行循环。
那到底Runloop在哪?
在不做任何手动开启Runloop操作,其实你也能感受到Runloop的存在。
可能有小伙伴立马跳出来说,你扯蛋,没看见过!!!!!!!
质疑是对的,那请问如果没有Runloop,当你开启App后,怎样保证主线程不会被销毁,使程序持续运行?
或者,我们来做个小测试。
1. 保持程序持续运行
main.m原文件
先做小小改动:
猜猜控制台打印出什么?
只有第一个NSLog是执行了,另外一个没响应,这能说明UIApplicationMain函数中,开启了一个和主线程相关的RunLoop,导致UIApplicationMain不会返回,一直在运行中,也就保证了程序的持续运行。
2. 处理App中的各种事件
例如: 触摸事件,定时器事件,Selector事件等。
为什么这么说? App启动完成后,应该就完事了!!!
开玩笑,不可能完,还有很多要执行呢,即使没新开线程,在主线程完成,主线程还有任务,那么Runloop就会继续执行其他事件,直到任务完成。
3. 节省CPU资源,提高程序性能
程序运行起来时,当什么操作都没有做的时候,RunLoop就告诉CUP,现在没有事情做,我要去休息,这时CUP就会将其资源释放出来去做其他的事情,当有事情做的时候RunLoop就会立马起来去做事情
从上图看,如果把黑色箭头去掉,那么Runloop不提供Source, Timer, 那么Runloop就休息,线程无需要执行的任务,线程不工作。
RunLoop对象
Fundation框架 (基于CFRunLoopRef的封装)
NSRunLoop对象
CoreFoundation
CFRunLoopRef对象
因为Fundation框架是基于CFRunLoopRef的一层OC封装,这里我们主要研究CFRunLoopRef源码
如何获得RunLoop对象, 以下前2个使用频率相对较高。
[NSRunLoop currentRunLoop];// 获得当前线程的RunLoop对象
[NSRunLoop mainRunLoop];// 获得主线程的RunLoop对象
Core FoundationCFRunLoopGetCurrent();// 获得当前线程的RunLoop对象
CFRunLoopGetMain();// 获得主线程的RunLoop对象
RunLoop和线程间的关系
// 拿到当前Runloop 调用_CFRunLoopGet0
CFRunLoopRef CFRunLoopGetCurrent(void) {
CHECK_FOR_FORK();
CFRunLoopRefrl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop);
if(rl) return rl;
return_CFRunLoopGet0(pthread_self());
}
// 查看_CFRunLoopGet0方法内部
CF_EXPORT CFRunLoopRef_CFRunLoopGet0(pthread_t t) {
if(pthread_equal(t, kNilPthreadT)) {
t= pthread_main_thread_np();
}
__CFLock(&loopsLock);
if(!__CFRunLoops) {
__CFUnlock(&loopsLock);
CFMutableDictionaryRefdict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL,&kCFTypeDictionaryValueCallBacks);
//根据传入的主线程获取主线程对应的RunLoop
CFRunLoopRefmainLoop = __CFRunLoopCreate(pthread_main_thread_np());
//保存主线程 将主线程-key和RunLoop-Value保存到字典中
CFDictionarySetValue(dict,pthreadPointer(pthread_main_thread_np()), mainLoop);
if(!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)){
CFRelease(dict);
}
CFRelease(mainLoop);
__CFLock(&loopsLock);
}
//从字典里面拿,将线程作为key从字典里获取一个loop
CFRunLoopRefloop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t));
__CFUnlock(&loopsLock);
//如果loop为空,则创建一个新的loop,所以runloop会在第一次获取的时候创建
if(!loop) {
CFRunLoopRefnewLoop = __CFRunLoopCreate(t);
__CFLock(&loopsLock);
loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,pthreadPointer(t));
//创建好之后,以线程为key runloop为value,一对一存储在字典中,下次获取的时候,则直接返回字典内的runloop
if(!loop) {
CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop);
loop = newLoop;
}
// don't release run loops inside the loopsLock, because CFRunLoopDeallocatemay end up taking it
__CFUnlock(&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);
}
}
returnloop;
}
1. 每条线程都有唯一的一个与之对应的RunLoop对象
2. RunLoop保存在一个全局的Dictionary里,线程作为key,RunLoop作为value
3. 主线程的RunLoop已经自动创建好了,子线程的RunLoop需要主动创建
4. RunLoop在第一次获取时创建,在线程结束时销毁
RunLoop结构体
__CFRunLoop结构体
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 forruns 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;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
两个成员变量,可能是大家见得比较多的。
CFRunLoopModeRef_currentMode;
CFMutableSetRef_modes;
CFRunLoopModeRef 指向__CFRunLoopMode结构体的指针,
__CFRunLoopMode结构体源码如下:
typedef struct __CFRunLoopMode*CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* musthave the run loop locked before locking this */
CFStringRef _name;
Boolean _stopped;
char _padding[3];
CFMutableSetRef _sources0;
CFMutableSetRef _sources1;
CFMutableArrayRef _observers;
CFMutableArrayRef _timers;
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet;
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
主要查看以下成员变量
CFMutableSetRef_sources0;
CFMutableSetRef_sources1;
CFMutableArrayRef_observers;
CFMutableArrayRef_timers;
通过上面分析我们知道,CFRunLoopModeRef代表RunLoop的运行模式,一个RunLoop包含若干个Mode,每个Mode又包含若干个Source0/Source1/Timer/Observer,而RunLoop启动时只能选择其中一个Mode作为currentMode。
Source1/Source0/Timers/Observer
1. Source1 : 基于Port的线程间通信
2. Source0 : 触摸事件,PerformSelectors
frame #29: 0x000000010d085721 CoreFoundation`__CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION__ + 17,
SOURCE0:控制台通过“bt”指令打印完整的堆栈信息,由堆栈信息中可以发现,触摸事件,确实是会触发Source0事件。
NSTimer
Observer :监听器,用于监听RunLoop的状态
RunLoop相关类及作用
CFRunLoopRef - 获得当前RunLoop和主RunLoop
CFRunLoopModeRef - RunLoop 运行模式,只能选择一种,在不同模式中做不同的操作
CFRunLoopSourceRef - 事件源,输入源
CFRunLoopTimerRef - 定时器时间
CFRunLoopObserverRef - 观察者
1. CFRunLoopModeRef
CFRunLoopModeRef代表RunLoop的运行模式
Source <Set> 在OC里, Set是无序,唯一的多数据集合。
Observer <Array> 数组
Timer <Array> 数组
RunLoop 与 Mode的关系是?
答:RunLoop一对Mode多;
Mode里有什么?
答:Mode包含若干个Source、Timer、Observer
每次RunLoop启动时,会做什么操作?
答:指定其中一个 Mode;
如果需要切换Mode, 能直接切换吗?
答:不能。只能退出RunLoop,再重新指定一个Mode进入,这样做主要是为了分隔开不同组的Source、Timer、Observer,让其互不影响。如果Mode里没有任何Source0/Source1/Timer/Observer,RunLoop会立马退出。
RunLoop能否没有Source,Timer?
答:不能。一种Mode中可以有多个Source(事件源,输入源,基于端口事件源例键盘触摸等) Observer(观察者,观察当前RunLoop运行状态) 和Timer(定时器事件源)。但是必须至少有一个Source或者Timer,因为如果Mode为空,RunLoop运行到空模式不会进行空转,就会立刻退出。
系统默认注册的5个Mode:
RunLoop 有五种运行模式,第一,第二种比较常用
1. kCFRunLoopDefaultMode:App的默认Mode,通常主线程是在这个Mode下运行
2. UITrackingRunLoopMode:界面跟踪 Mode,用于 ScrollView 追踪触摸滑动,保证界面滑动时不受其他 Mode 影响
3. UIInitializationRunLoopMode: 在刚启动 App 时第进入的第一个 Mode,启动完成后就不再使用,会切换到kCFRunLoopDefaultMode
4. GSEventReceiveRunLoopMode: 接受系统事件的内部 Mode,通常用不到
5. kCFRunLoopCommonModes: 这是一个占位用的Mode,作为标记kCFRunLoopDefaultMode和UITrackingRunLoopMode用,并不是一种真正的Mode
Mode间的切换
开发中,经常需要做轮播图,都会加上定时器,当滑动轮播图的时候,NSTimer就会暂停,当我们停止滑动以后,NSTimer又会重新恢复的情况, 这是为什么呢?
答:NSTimer不管用是因为Mode的切换,因为如果我们在主线程使用定时器,此时RunLoop的Mode为kCFRunLoopDefaultMode,即定时器属于kCFRunLoopDefaultMode,那么此时我们滑动ScrollView时,RunLoop的Mode会切换到UITrackingRunLoopMode,因此在主线程的定时器就不在管用了,调用的方法也就不再执行了,当我们停止滑动时,RunLoop的Mode切换回kCFRunLoopDefaultMode,所以NSTimer就又管用了。
CFRunLoopSourceRef事件源(输入源)
Source分为两种
Source0:非基于Port的 用于用户主动触发的事件(点击button 或点击屏幕)
Source1:基于Port的 通过内核和其他线程相互发送消息(与内核相关)
CFRunLoopObserverRef
能够监听RunLoop的状态改变
至此,可以看出Observer确实用来监听RunLoop的状态,包括唤醒,休息,以及处理各种事件。
RunLoop处理逻辑
RunLoop退出
1. 主线程销毁RunLoop退出
2. Mode中有一些Timer 、Source、 Observer,这些保证Mode不为空时,保证RunLoop没有空转并且是在运行的,当Mode中为空的时候,RunLoop会立刻退出
3. 启动RunLoop的时设置停止时间
[NSRunLoopcurrentRunLoop]runUntilDate:<#(nonnull NSDate *)#>
[NSRunLoopcurrentRunLoop]runMode:<#(nonnull NSString *)#> beforeDate:<#(nonnull NSDate *)#>
RunLoop应用
1.常驻线程 ---> 给子线程开启一个RunLoop
注意:
1) 子线程执行完操作之后就会立即释放,即使我们使用强引用引用子线程使子线程不被释放,也不能给子线程再次添加操作,或者再次开启。
2)创建子线程相关的RunLoop,在子线程中创建即可,并且RunLoop中要至少有一个Timer 或 一个Source 保证RunLoop不会因为空转而退出,因此在创建的时候直接加入,如果没有加入Timer或者Source,或者只加入一个监听者,运行程序会崩溃
自动释放池
Timer和Source是什么?
答:Timer和Source也是一些变量,需要占用一部分存储空间。
Timer和Source能否一直保留?
答:要释放掉,如果不释放掉,就会一直积累,占用的内存也就越来越大,这显然不是我们想要的。
什么时候释放,怎么释放呢?
答:RunLoop内部有一个自动释放池,当RunLoop开启时,就会自动创建一个自动释放池,当RunLoop在休息之前会释放掉自动释放池的东西,然后重新创建一个新的空的自动释放池,当RunLoop被唤醒重新开始时,Timer,Source等新的事件就会放到新的自动释放池中,当RunLoop退出的时候也会被释放。
注意:只有主线程的RunLoop会默认启动。也就意味着会自动创建自动释放池,子线程需要在线程调度方法中手动添加自动释放池。
@autorelease{// 执行代码
}
此文部分引用链接: https://www.jianshu.com/p/de752066d0ad
网友评论