在Runloop启动后,首先会发出一个通知.告知观察者即将启动,之后将要处理Timer / Source0事件,然后进入正式的source0处理,如果有source1要处理,会通过一条goto语句实现,进行代码逻辑跳转,去处理唤醒时收到的消息,如果没有source1处理,此时线程即将进入休眠,同时也会发送通知给Observer,然后发生从用户态到内核态的切换,然后线程正式进入休眠,等待唤醒
唤醒线程或者RunLoop的条件有三个
- 通过Source1进行相关事件的唤醒
- Timer事件的回调到了
- 外部手动唤醒
当一个处于休眠状态的RunLoop,可以通过上面三个方式唤醒它
当线程被唤醒后,也会发送通知告诉观察者
然后处理唤醒时收到的消息
然后又会回到将要处理Timer以及Source0事件的代码逻辑
然后通知observer,之后顺次向下执行
线程再次进入休眠,等待唤醒
程序从点击图标到程序启动,到程序被杀死这个过程中,系统是怎样实现的?
当调用Main函数之后,会调用UIApplicationMain函数,在内部启动主线程的RunLoop,经过一系列的处理,最终主线程的RunLoop处于休眠状态,之后如果点击了屏幕,会转成Source1, 就会把主线程唤醒,进行后续处理,当我们把线程杀死的时候,会发生RunLoop的退出通知,退出之后,线程就被销毁掉了
用户态和核心态是怎么切换的
main函数经过一系列处理后,系统内部会调用mach_msg函数,发生系统调用,经过这个调用,当前用户线程就把控制权转交给核心态
mach_mag在一定条件下会返回给调用方,触发返回的逻辑就是唤醒线程的逻辑
1,通过Source1进行相关事件的唤醒
2,Timer事件的回调到了
3,外部手动唤醒
然后就从核心态回到用户态的切换,当前APP的主线程循环就会被唤醒
RunLoop与NSTimer
滑动tableView时,定时器还会生效吗?
当前tableView正常情况下,是运行在kCFRunLoopDefaultMode模式下的,当我们对tableView进行滑动的时候,会发生mode切换,切换到UITrackingRunLoopMode模式下,
之前说,当我们把source / Timer / Observer添加到某个mode上面之后,如果当前Runloop是运行在另一个Mode上面的话,对应的这些Timer和Source和Observer是没有办法进行后续处理回调的,因为Mode有多个解决的问题就是Timer和Source和Observer的隔离,这种隔离产生的问题就是创建的Timer默认情况下添加到kCFRunLoopDefaultMode模式下,切换到UITrackingRunLoopMode后,定时器就不会再生效了
解决的办法是:
- void CFRunLoopAddTimer(runLoop, timer, commonMode)
通过这个函数,将NSTimer添加到当前RunLoop的commonMode中, commonMode不是一个世纪的mode,只是把一些mode打上common的标记,然后可以把某个事件源比如说是Timer同步到多个mode中
//参数: runloop,准备要添加的参数,模式名称
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);
//如果当前模式名称是CommonModes
if (modeName == kCFRunLoopCommonModes) {
//首先会提取RunLoop的commonModes
CFSetRef set = rl->_commonModes ? CFSetCreateCopy(kCFAllocatorSystemDefault, rl->_commonModes) : NULL;
//同时判断runloop对应的commonItems是否为空,若为空,会重新创建集合
if (NULL == rl->_commonModeItems) {
rl->_commonModeItems = CFSetCreateMutable(kCFAllocatorSystemDefault, 0, &kCFTypeSetCallBacks);
}
//把timer添加到commonModeItems数组中
CFSetAddValue(rl->_commonModeItems, rlt);
if (NULL != set) {
//再把timer和Runloop封装成一个context,
CFTypeRef context[2] = {rl, rlt};
//之后对集合中的每一个元素,都调用__CFRunLoopAddItemToCommonModes函数(对象元素,context)
CFSetApplyFunction(set, (__CFRunLoopAddItemToCommonModes), (void *)context);
CFRelease(set);
}
static void __CFRunLoopAddItemToCommonModes(const void *value, void *ctx) {
//取当前mode的名字,然后吧Runloop和Item取出来
CFStringRef modeName = (CFStringRef)value;
CFRunLoopRef rl = (CFRunLoopRef)(((CFTypeRef *)ctx)[0]);
CFTypeRef item = (CFTypeRef)(((CFTypeRef *)ctx)[1]);
//根据当前item类型判断,来决定调用CFRunLoopAddSource还是CFRunLoopAddObserver还是CFRunLoopAddTimer,并不是循环调用,因为传进来的modeName 参数已经从commonMode变成被打上了标记的具体实际的mode
if (CFGetTypeID(item) == __kCFRunLoopSourceTypeID) {
CFRunLoopAddSource(rl, (CFRunLoopSourceRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopObserverTypeID) {
CFRunLoopAddObserver(rl, (CFRunLoopObserverRef)item, modeName);
} else if (CFGetTypeID(item) == __kCFRunLoopTimerTypeID) {
CFRunLoopAddTimer(rl, (CFRunLoopTimerRef)item, modeName);
}
}
如果CFRunLoopAddTimer传递进来的是个具体的mode,那么在后续就会把timer最终添加到对应的RunLoop Mode下面的timers数组中,就可以实现:通过把多个mode中都添加同一个Timer
因为调用CFSetApplyFunction函数,实际上是对于集合当中每个元素都调用了CFRunLoopAddItemToCommonMode函数,就可以实现把多个timer同步到多个mode 下面
的效果
所以,可以通过addTimer到一个commonMode上面来实现把一个Timer添加到多个mode下面,同时UITrackingRunLoopMode是被打上common标记的,就可以把timer同步到了UITrackingRunLoopMode上,如果列表继续滑动,timer是可以正常响应的
所以,如果我们在设置Timer模式时
[[NSRunLoopcurrentRunLoop] addTimer:self.timerforMode:NSDefaultRunLoopMode];
当我们不与UI进行交互时NSTimer有效
当我们与UI交互那么主线程runloop就会转到UITrackingRunLoopMode模式下,不能处理timer,就失效了.
所以我们可以
- [[NSRunLoopcurrentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
- 开启新线程,在新线程中定义timer,这样就会被新线程的runloop处理。
怎样保证子线程数据回来更新UI的时候不打断用户的滑动操作
在用户进行滑动的过程中,当前的RunLoop运行在UITrackingRunLoopMode模式下,而我们一般对网络请求是放在子线程中,子线程返回给主线程的数据要抛给主线程用来更新UI,可以把这部分逻辑包装起来,提交到主线程的default模式下,这样的话,当用户滑动时,default模式下的任务不会执行,当用户手停止时,mode就切换到了default模式下,就会处理子线程的数据了,这样就不会打断用户的滑动操作了
RunLoop与多线程
-
线程和Runloop是一一对应的
-
自己创建的线程默认是没有RunLoop的,需要自己手动创建某个线程的RunLoop
实现常驻线程:
- 为当前线程开启一个RunLoop
可以通过[CFRunLoop getCurrent]或者 [NSRunLoop currentRunLoop]来创建,因为获取RunLoop这个方法本身会查找,如果当前线程没有runloop,会在系统内部为我们创建 - 向该RunLoop中添加一个port / Source等维护RunLoop的事件循环
RunLoop如果没有事件需要处理的话,默认情况下,是不能自己维持事件循环,会直接退出,所以需要添加port / Source来维持事件循环机制 - 启动该RunLoop
调用run方法
#import "MCObject.h"
@implementation MCObject
//定义两个k静态全局变量
// 自定义线程
static NSThread *thread = nil;
// 标记当前线程是否要继续事件循环
static BOOL runAlways = YES;
+ (NSThread *)threadForDispatch{
if (thread == nil) {
@synchronized(self) {
if (thread == nil) {//采用线程安全的方式去创建thread,入口方法为runRequest
// 线程的创建
thread = [[NSThread alloc] initWithTarget:self selector:@selector(runRequest) object:nil];
[thread setName:@"com.imooc.thread"];
// 启动
[thread start];
}
}
}
return thread;
}
+ (void)runRequest
{
// 创建一个Source
CFRunLoopSourceContext context = {0, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL};
CFRunLoopSourceRef source = CFRunLoopSourceCreate(kCFAllocatorDefault, 0, &context);
// 为thread线程创建RunLoop,同时向RunLoop的DefaultMode下面添加Source
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
//while循环维持RunLoop的事件循环
// 如果可以运行
while (runAlways) {
//确保每次yRunLoop运行一圈的时候能够对内存进行释放
@autoreleasepool {
/* 令当前RunLoop运行在DefaultMode下面,注意这个运行的mode和上面添加资源的mode必须是同一个mode
否则把事件源添加到另一个mode上,而运行的defaultMode下,是无法维持运行的
函数内部会调用mach_msg,发生由用户态到核心态的切换,当前线程就会休眠,就停在里面,不是死循环
1.0e10是让循环运行到指定时间退出,这个代表很久远的时间
true代表资源被处理后是否马上返回
*/
CFRunLoopRunInMode(kCFRunLoopDefaultMode, 1.0e10, true);
}
}
// 如果Runloop的mode中没有对应的事件源可以处理,runloop就会自动退出
// 所以我们在某一时机 将source移除,静态变量runAlways = NO时 可以保证跳出RunLoop,线程退出并s释放掉
CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopDefaultMode);
CFRelease(source);
}
@end
网友评论