RunLoop:是通过内部维护的事件循环来对事件/消息进行管理的一个对象,能在程序中循环做些事情,可以让开发者间接的控制内核态能进行的操作(休眠线程,保持线程存活的前提而不占用系统的资源)。
事件循环:
- 维护的事件循环可以用来不断处理消息事件,对他们管理,没有消息时会从用户态转到内核态,由此可以让当前线程休眠,避免资源占用。
- 有消息时再被唤醒处理事件,由内核态转至用户态
应用范畴:定时器,PerformSelector,GCD Async Main Queue,网络请求,手势识别,界面刷新。
如果UIApplicationMain里的Runloop,程序执行完后就会退出,而不会像我们打开程序那样,保持着运行状态
基本作用,保持程序的持续运行,处理App中各种事件(触摸事件,定时器事件等)。节省CPU资源,提高程序性能,可以从操作系统层面,让线程睡眠,不占用CPU资源。
int main(int argc, char * argv[]) {
@autoreleasepool {
//这里创建了一个RunLoop
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
RunLoop与线程:
每个线程都有一个唯一的RunLoop,存在于一个全局的字典里,线程作为Key,RunLoop作为value。线程刚刚创建时并没有RunLoop,RunLoop会在第一次获取它时创建。在线程结束时销毁。下面是获取RunLoop的源码,CFRunLoops是全局的字典。
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,
pthreadPointer(t));
iOS有两套API访问RunLoop
Foundation: NSRunLoop(封装的CFRunLoopRef,使用更方便)
Core Foundation: CFRunLoopRef(可控制的参数更多,更自由)
Foundation: [NSRunLoop currentRunLoop];//获取当前线程的RunLoop
[NSRunLoop mainRunLoop];//获取住线程的RunLoop
Core Foundation:CFRunLoopGetCurrent();//获取当前线程的RunLoop
CFRunLoopGetMain();//获取主线程的RunLoop对象
RunLoop相关的类与结构
- CFRunLoopRef
- CFRunLoopModeRef
- CFRunLoopSourceRef
- CFRunLoopTimerRef
- CFRunLoopObserverRef
typedef struct __CFRunLoop *CFRunLoopRef;
struct __CFRunLoop {
pthread_t _pthread;//所处线程
CFMutableSetRef _commonModes;//字符串集合
CFMutableSetRef _commonModeItems;
CFRunLoopModeRef _currentMode;
CFMutableSetRef _modes;//装着CFRunLoopModeRef
}
typedef struct CFRunLoopMode *CFRunLoopModeRef;
struce __CFRunLoopMode {
CFStringRef _name;
CFMutableSetRef _source0;//CFRunLoopSourceRef
CFMutableSetRef _source1;//CFRunLoopSourceRef
CFMutableArrayRef _observers;//CFRunLoopObserverRef
CFMutableArrayRef _timers;
};
RunLoop数据结构
- pthread:
每个线程都有一个唯一的RunLoop,存在于一个全局的字典里,线程作为Key,RunLoop作为value。线程刚刚创建时并没有RunLoop,RunLoop会在第一次获取它时创建,在线程结束时销毁。
CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops,
pthreadPointer(t));//__CFRunLoops全局的一个字典。
- currentModel:
CFRunLoopMode类型 - modes:
NSMutableSet元素是CFRunLoopMode - commonModes:
NSMutableSet元素是字符串集合 - commonModeltems:
NSMutableSet(多个obserber,多个timer,多个source)
RunLoopModel数据结构
-
name
mode名字或者也可以说是类型
1.NSDefaulyRunLoopMode
默认状态、空闲状态
2.UITrackingRunLoopMode
滑动态mode -追踪 ScrollView 滑动时的状态
3.UIInitializationRunLoopMode
私有,启动APP时切换进入,完成启动就被抛弃不再使用
4.GSEventReceiveRunLoopMode
接受系统事件的内部Mode,通常用不到。
5.NSRunLoopCommonModes
这个mode比较特殊,包含了DefaultMode和UITrackingRunLoopMode 的状态,一 般作占位用,结构也比较特殊 -
source0
- 需要手动唤醒线程
- 触摸事件 或者 - performSelector: onThread:
-
source1
通过port不同线程间的通信。具有唤醒线程的能力 -
observers
- 监听者
-
timer
- 定时器、performSeletor:withObject:afterDelat:
RunLoop和mode的关系
- 一个RunLoop包含了若干mode,一对多的关系。
- 每个mode包含了若干个source0,source1,observer,timer。
- RunLoop启动时只能选择一个mode作为当前currentMode,切换mode只能退出当前mode,再重新进入一个mode。如果mode没有任何source0和source1,timer,observer。RunLoop会退出。
注意点:observer
用于监听Runloop状态-特定类型,不能随便传;
创建方式:
CFRunLoopObserver observer = CFRunLoopObserverCreate(CFALLocatorRef(创建方式,通常kCFAllocatorDefault),activities(监听什么状态,kCFRunLoopAllctives所有状态),repeats(是否重复监听),顺序 填0代表不需要考虑,CFRunLoopObserverCallBack(监听返回的会调的函数),context(会把我们传进去的回调函数放到这来,可以填NULL));
//对某个RunLoop监听
CFRunLoopAddObserver(CFRunLoopGetMain(),observer,kCFRunLoopCommonModes);//kCFRunLoopCommonModes并不是真正的mode,而是一个标记,_commonModeItems存的mode都是这个标记的,代表监听所有的mode。
CFRelease(observer);
观测时间点
- kCFRunLoopEntry //即将进入Loop
- kCFRunLoopBeforeTimers //即将处理Timer
- kCFRunLoopSources //即将处理Source
- kCFRunLoopBeforeWaiting//即将进入休眠 (从用户态切换到内核态)
- kCFRunLoopAfterWaiting //即将从休眠中唤醒
- kCFRunLoopExit //即将退出
- kCFRunLoopAllActivities //所有状态。
Runloop运行逻辑
- 通知Observers:进入Loop
- 通知Observers:即将处理Timers
- 通知Observers::即将处理Sources
- 处理Blocks(CFRunLoopPerformBlock(CFRunLoopRef rl,CFTypeRef mode,^{})类似这种可以往Loop里添加block)
- 处理Source0(期间可能会处理Blocks)
- 如果存在source1,就跳转到第8步
- 通知OBservers:开始休眠(等待消息唤醒)
- 通知Observers:开始结束休眠(被某个消息唤醒)
- 处理Timer
- 处理GCD Async To Main Queue
- 处理source1
- 处理Blocks
- 根据前面的执行结果,决定如何操作
- 退回到02步
- 退出Loop
- 通知Observers:退出Loop。
RunLoop应用
1. 解决Timer在某些控件滑动时不执行不计时。
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
NSRunLoopCommonModes:
- 不是实际存在的一种Mode
- 是同步Source/Timer/Observer到多个Mode 中的一种技术解决方案。
把一些Mode打上一个标记,表示能运行到RunLoop结构体里的_commonModeItems所装的所有模式,而NSDefaultRunLoopMode和UITrackKingRunLoopMode都是装在_commonModeItems里。
底层原理: - CFRunLoopAddTimer()把timer添加到commondMode下下的所有真实mode
- 判断是否是commondMode,是的话调用CFSetApplyFunction,为每个commondModes里每个mode调用__CFRunLoopAddItemToCommonModes()函数
- __CFRunLoopAddItemToCommonModes()函数里执行CFRunLoopAddTimer()函数。
- CFRunLoopAddTimer()函数这是接收到的是真实的mode,就会进入另一个逻辑,把timer添加到真实的mode下
CFRunLoopAddTimer(CFRunLoopRef rl//RunLoop,CFRunLoopTimerRef rtl//要添加的timer,CFStringRef modeName//模式名称){
//如果传进来的是CommondMode而不是真的mode
if (modeName == kCFRunLoopCommonModes) {
CFSetRef set = rl->_commonModes ;//获取所有rl下_commonModes里所有的model
CFSetAddValue(rl->_commonModeItems, rlt);
CFTypeRef context[2] = {rl, rlt};//把定时器和RunLoop封装成一个Context
//调用__CFRunLoopAddItemToCommonModes,参数所有mode名字的集合,当前RunLoop和context封装的context
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
}else{
//执行具体mode下的timer
}
}
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
CFStringRef modeName = (CFStringRef)value;//获取mode名字
CFRunLoopRef rl = ctx[0];//取出RunLoop
CFTypeRef item = ctx[1];//取出timer
if(source类型){
CFRunLoopAddSource(rl,(CFRunLoopSourceRef)item,modeName)
}else if(Observer类型){
CFRunLoopAddObserver(rl,(CFRunLoopObserverRef)item,modeName)
}else if(timer类型){
CFRunLoopAddTimer(rl,(CFRunLoopTimerRef)item,modeName)
}
}
2.线程保活
self.innerThread = [[NSThread alloc]initWithBlock:^{
CFRunLoopSourceContext context = {0};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(CFAllocatorGetDefault(), 0, &context);
//往runloop添加source,保证RunLoop不会退出。
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
//ture:执行完source就退出,这里false,程序一直阻塞在这里,线程就不会死。C语言提供参数更多,能控制是否执行完source退出mode,不用加判断一直添加mode
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, false);
}];
当我们不想用这个线程时,需要停止Loop,不然Thread会一直在,指针清空也没有用,线程里执行的任务卡着,线程停不掉,所以需要先停止RunLoop。
CFRunLoopStop(CFRunLoopGetCurrent());
self.innerThread = nil;
总结:
RunLoop是处理事件循环消息管理的对象
RunLoop有五个重要成员
- pthread
- currentMode
- modes
- commondModes
- commondModesItems(Observers,timers,sources)
Mode重要成员
- name
- source0s(需要手动唤醒线程)
- source1s(具备唤醒线程的能力)
- blocks
- timers
RunLoop观测时间点(Entry,beforeTimer,Source,beforeWaiting,AfterWating,Exit)AllActivitives
RunLoop运行机制
Runloop应用(timer不同mode下都有效,线程保活)
注意点:加入到某个Mode下的timer在别的Mode下是不生效的,如果要所有Mode都生效,需要添加到NSRunLoopCommonModes下
NSRunLoopCommonModes并不是真正的Mode
网友评论