BAT 批处理程序 2017-03-29
运行循环,一个 run loop 就是一个事件处理的循环,用来不停的调度工作以及处理事件
作用
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); }}
UIApplicationMain()此函数内部就启动了一个RunLoop,所以此函数一直没有返回,保持了程序的持续运行,这个默认启动的RunLoop是跟主线程相关的
RunLoop对象
RunLoop与线程
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];//主线程对应的RunLoop [NSRunLoop mainRunLoop];//当前线程对应的RunLoop currentRunloop.getCFRunLoop;//转化为CFRunLoop CFRunLoopGetMain(); CFRunLoopGetCurrent(); //开启一个子线程 [[[NSThread alloc]initWithTarget:self selector:@selector(run) object:nil] start]; -(void)run { //创建子线程对应的RunLoop,currentRunLoop 懒加载的 [NSRunLoop currentRunLoop]; }
以下是苹果官方源码,通过分析源代码可以看出利用pthread作为全局字典中的key,并创建与之对应的RunLoop作为Value,RunLoop在我们获取的时候创建,不获取不创建,主线程的RunLoop在一开始就自动创建。线程与RunLoop是一一对应的关系
CFRunLoopRef CFRunLoopGetMain(void) { CHECK_FOR_FORK(); static CFRunLoopRef __main = NULL; // no retain needed if (!__main) __main = _CFRunLoopGet0(pthread_main_thread_np()); // no CAS needed return __main; } CFRunLoopRef CFRunLoopGetCurrent(void) { CHECK_FOR_FORK(); CFRunLoopRef rl = (CFRunLoopRef)_CFGetTSD(__CFTSDKeyRunLoop); if (rl) return rl; return _CFRunLoopGet0(pthread_self()); } //全局的Dictionary,key 是 pthread_t, value 是 CFRunLoopRef static CFMutableDictionaryRef __CFRunLoops = NULL; //访问 Dictionary 时的锁 static CFLock_t loopsLock = CFLockInit; //获取一个 pthread 对应的 RunLoop CF_EXPORT CFRunLoopRef _CFRunLoopGet0(pthread_t t) { //如果传进来的线程等于 0 if (pthread_equal(t, kNilPthreadT)) { //当前线程等于主线程 t = pthread_main_thread_np(); } //给操作加锁 __CFLock(&loopsLock); //如果当前RunLoop为空,创建。 if (!__CFRunLoops) { __CFUnlock(&loopsLock); // 创建字典 CFMutableDictionaryRef dict = CFDictionaryCreateMutable(kCFAllocatorSystemDefault, 0, NULL, &kCFTypeDictionaryValueCallBacks); // 创建主线程 CFRunLoopRef mainLoop = __CFRunLoopCreate(pthread_main_thread_np()); // 保存主线程 CFDictionarySetValue(dict, pthreadPointer(pthread_main_thread_np()), mainLoop); if (!OSAtomicCompareAndSwapPtrBarrier(NULL, dict, (void * volatile *)&__CFRunLoops)) { CFRelease(dict); } CFRelease(mainLoop); __CFLock(&loopsLock); } // 从字典中获取当前线程的RunLoop CFRunLoopRef loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); __CFUnlock(&loopsLock); if (!loop) { // 如果当前线程的runloop不存在,那么就为该线程创建一个对应的runloop CFRunLoopRef newLoop = __CFRunLoopCreate(t); __CFLock(&loopsLock); loop = (CFRunLoopRef)CFDictionaryGetValue(__CFRunLoops, pthreadPointer(t)); // 把当前子线程和对应的runloop保存到字典中 if (!loop) { CFDictionarySetValue(__CFRunLoops, pthreadPointer(t), newLoop); loop = newLoop; } // don't release run loops inside the loopsLock, because CFRunLoopDeallocate may 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); } } return loop; }
Core Foundation中关于RunLoop的5个类
说明:一个RunLoop包含若干个Mode,每个Mode又包含若干个Source、Timer、Observer,每次RunLoop启动时,只能指定一个Mode,这个Mode被称作CurrentMode,如果需要切换Mode,只能退出Loop,在重新指定一个Mode进入,这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。
1.CFRunLoopSourceRef :事件源(输入源)
- Source0:非基于Port的 (用户主动触发的事件)
- Source1:基于Port的 (系统内部的消息事件)
(Port是线程间通信的一种方式,如果两个线程之间想通信,可以通过Port来通信。)
2.CFRunLoopTimerRef
基于时间触发器,当其加入RunLoop时,RunLoop会注册对应的时间点,当时间点到,RunLoop会被唤醒执行里面的回调。
3.CFRunLoopObserverRef
观察者,能够监听RunLoop的状态改变。
/* Run Loop Observer Activities */ typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0),//即将进入Loop kCFRunLoopBeforeTimers = (1UL << 1),//即将处理Timer kCFRunLoopBeforeSources = (1UL << 2),//即将处理Source kCFRunLoopBeforeWaiting = (1UL << 5),//即将进入休眠 kCFRunLoopAfterWaiting = (1UL << 6),//刚从休眠中唤醒 kCFRunLoopExit = (1UL << 7),//即将退出Loop kCFRunLoopAllActivities = 0x0FFFFFFFU };
4.CFRunLoopModeRef :RunLoop的运行模式
在RunLoop中有多个运行模式,但是RunLoop只能选择一种模式运行,Mode里面至少要有Timer或者Source
系统默认注册了5个Mode:
1.Timer与滑动控件的问题
问题:当在不停地拖动滑动控件的时候,定时器不工作了
错误回答:runloop的优先级
分析:runloop的几种常用模式:DefaultMode默认模式,以及UITrackingMode模式,CommonModes占位模式,runloop进入一种模式的时候,另一种模式的事件不会去处理,当Timer在运行的时候处于DefaultMode模式,当拖动滑动控件的时候,runloop会立即处理UI事件,切换到UITrackingRunLoopMode模式后,此时DefaultMode模式下的Timer就不工作了
解决:将Timer置于NSRunLoopCommonModes占位模式下
- (void)viewDidLoad { [super viewDidLoad]; //1.创建定时器 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //2.将Timer添加到RunLoop中 //[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode]; //[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode]; [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; } - (void)run { NSLog(@"-------- %@",[NSThread currentThread]); }View Code
问题:在多线程开发中,耗时操作我们一般会放在子线程中执行,请问这种线程有什么特点?
实例:假如在上面的定时器的run方法中,执行一个耗时操作,此时会卡住主线程,拖动滑动控件会很不流畅,应该如何解决?
分析:子线程中默认不会开启RunLoop循环,所以子线程在执行完任务之后就会被回收
- (void)viewDidLoad { [super viewDidLoad]; dispatch_async(dispatch_get_global_queue(0, 0), ^{ //1.创建定时器 NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //2.将Timer添加到RunLoop中 [[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes]; //3.让RunLoop运行起来 [[NSRunLoop currentRunLoop] run];//死循环,后面的代码不会执行 NSLog(@"+++++++++"); }); } - (void)run { //耗时操作 [NSThread sleepForTimeInterval:1.0]; NSLog(@"-------- %@",[NSThread currentThread]); }View Code
或者:
- (void)viewDidLoad { [super viewDidLoad]; [NSThread detachNewThreadSelector:@selector(time2) toTarget:self withObject:nil]; } -(void)time2{ //创建当前线程的RunLoop NSRunLoop *currentLoop = [NSRunLoop currentRunLoop]; //该方法内部自动添加到RunLoop中,并且运行模式是默认模式 [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; //开启RunLoop [currentLoop run]; } - (void)run { //耗时操作 [NSThread sleepForTimeInterval:1.0]; NSLog(@"run ----- %@ ---- %@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode); }View Code
此博文会继续不断完善及更新关于涉及到runloop的知识,如果有理解不正确或者有涉及到runloop的应用实例,请留言吧,欢迎讨论
参考文章:http://blog.ibireme.com/2015/05/18/runloop/