RunLoop编程官方文档翻译地址
下面是里面提到的使用RunLoop的案例.官方提到下面的场景应该考虑使用RunLoop.(具体见翻译文档的 5.什么时候会用一个run loop)
继承NSThread创建一个MyThread类,重写dealloc方法,打印线程结束的信息。如果程序存在下面的代码,startNewThread方法里面的代码执行完后就会提示线程退出了。没有办法控制线程的运行时间。仅仅能开启辅助线程执行代码,一般情况下这样也足够了,所以苹果官方文档说明run loop的开启是运用在需要和线程有更多交互的场合上的。
- (void)viewDidLoad { [super viewDidLoad]; MyThread * thread = [[MyThread alloc]initWithTarget:self selector:@selector(startNewThread) object:nil]; [thread start]; } -(void)startNewThread{ NSLog(@"辅助线程执行的代码"); }
1.使用端口或者自定义的输入源和其他线程通信
这种类型的没有遇到过,文档后面有一些相应地代码,但是感觉比较复杂。不知道什么时候用这种场景,以后用到了补充。
2.线程上使用定时器
一般情况下,自己使用定时器都是在主线程上,主线程的runloop默认是开启的。定时器提供了一个可以直接将定时器添加到当前线程的run loop的NSDefaultRunLoopMode构造方法,所以很多时候什么都不需要做,就能正常使用。但是如果在非主线程上添加定时器就需要手动开启run loop了。如果不开启的话和上面的代码没有什么区别,线程执行完startNewThread的方法后就退出了,会导致周期性执行定时器任务根本实现不了。那如果我在startNewThread方法里面加上一个do {;} while(1);虽然虽然能防止线程退出,但是线程会进入死循环同样无法执行定时器任务。所以run loop开启的意义可以使得和线程有更多的交互,让线程在忙碌的时候忙碌,不忙碌的时候休眠。对比主线程是一样的,如果主线程有任务主线程会执行任务,没有任务的话就不耗费资源。但是并没有退出(否则app就退出了);
-(void)startNewThread{ //获取当前线程的runloop对象 NSRunLoop* myRunLoop = [NSRunLoop currentRunLoop]; //结构体 CFRunLoopObserverContext context = {0, (__bridge void *)(self), NULL, NULL, NULL}; //创建runloop观察者,绑定run loop。 CFRunLoopObserverRef observer = CFRunLoopObserverCreate(kCFAllocatorDefault, kCFRunLoopAllActivities, YES, 0,myRunLoopObserver, &context); if (observer) { CFRunLoopRef cfLoop = [myRunLoop getCFRunLoop];//获取Core Foundation形式的runloop引用 //为run loop的默认的模式添加观察者 CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode); } // Create and schedule the timer. [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(doFireTimer:) userInfo:nil repeats:YES]; // NSTimer * timer = [NSTimer timerWithTimeInterval:0.1 repeats:YES block:^(NSTimer * _Nonnull timer) { // NSLog(@"纯净的定时器"); //}]; //[myRunLoop addTimer:timer forMode:NSDefaultRunLoopMode]; [myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]]; }
定时器.png
上面的代码在辅助线程中,开启了run loop。并为run loop添加了观察者和定时器源,循环里面让run loop运行3秒钟。[myRunLoop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:3]];。RunLoop运行3秒,然后线程退出,在这3秒期间,正确的在该线程上处理了定时器的事件,并且观察者在回调函数中观察到了run loop运行过程需要通知给观察者的状态信息。该案例中如果没有为run loop添加定时器和观察者,线程会如文档描述的那样立刻退出。
run loop运行过程.png
中文翻译可以看翻译文档的4. run loop一些列的事件,fire这个单词一直想不出好的中文词对应。下面的枚举观察者可以观察到的run loop的状态。
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0),//进入runloop,对应步骤1 kCFRunLoopBeforeTimers = (1UL << 1),//将要处理一个定时器,对应步骤2 kCFRunLoopBeforeSources = (1UL << 2),//将要处理一个输入源,对应步骤3 kCFRunLoopBeforeWaiting = (1UL << 5),//将要进入睡眠,对应步骤6 kCFRunLoopAfterWaiting = (1UL << 6),//已经唤醒,但是还没有处理唤醒run loop的事件,对应步骤8 kCFRunLoopExit = (1UL << 7),//将要退出,对应步骤10 kCFRunLoopAllActivities = 0x0FFFFFFFU //表示监控所有的活动 };
3. 在cocoa应用中使用任意一个performSelector…方法
比较常见的是performSelectorOnMainThread,非主线程中不能更新UI,开启额外线程进行网络请求后有时可以调用这个方法在主线程中更新UI。因为主线程中runloop已经开启了,所以很自然就成功了。但是如果在非主线程执行某个方法。就需要开启线程的runloop了。系统提供了一系列类似的方法。
performSelectorOnMainThread:withObject:waitUntilDone: performSelectorOnMainThread:withObject:waitUntilDone:modes:
执行特定的selector在主线程的下一个run loop回路。这两个方法给你提供了选项来阻断当前线程直到selector被执行完毕。
performSelector:onThread:withObject:waitUntilDone: performSelector:onThread:withObject:waitUntilDone:modes:
执行特定的selector在任意线程上,这些线程通过NSThread对象表示。同样提供了阻断当前线程直到selector被执行。
performSelector:withObject:afterDelay: performSelector:withObject:afterDelay:inModes:
在当前线程上下一个run loop回路中执行selector,并附加了延迟选项。因为它等待下一个run loop回路到来才执行selector,这些方法从当前执行的代码中提供了一个自动的微小延迟。多个排队的selector会按照顺序一个一个的执行。
cancelPreviousPerformRequestsWithTarget: cancelPreviousPerformRequestsWithTarget:selector:object:
让你取消一个通过performSelector:withObject:afterDelay: or performSelector:withObject:afterDelay:inModes: method方法发送到当前线程的消息。
这段代码让图片在默认的模式下加载,如果用户在进行滑动操作,不会进入这个模式,可以解决部分滑动卡顿问题
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
4.使得线程不被杀死去做周期性任务
可以根据业务需求,设置runloop运行时间。可以类似于上面的案例。周期性的在新线程上执行runmethod方法。
下面对应了3和4两种情况.
- (void)viewDidLoad { [super viewDidLoad]; MyThread * thread = [[MyThread alloc]initWithTarget:self selector:@selector(longrun) object:nil]; [thread start]; [NSTimer scheduledTimerWithTimeInterval:0.5 repeats:YES block:^(NSTimer * _Nonnull timer) { [self performSelector:@selector(runmethod) onThread:thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]]; }]; } -(void)longrun{ NSRunLoop * runLoop = [NSRunLoop currentRunLoop]; //为了防止runloop退出,添加一个端口。 [runLoop addPort:[[NSPort alloc]init] forMode:NSDefaultRunLoopMode]; [runLoop runUntilDate:[NSDate distantFuture]]; } -(void)runmethod { NSLog(@"%@ 辅助线程上执行的代码",[NSThread currentThread]); }
如果需要某个线程一直执行,等待某个条件具备时触发可以参考苹果提供的案例
- (void)skeletonThreadMain { // Set up an autorelease pool here if not using garbage collection. BOOL done = NO; // Add your sources or timers to the run loop and do any other setup. do { // Start the run loop but return after each source is handled. SInt32 result = CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, YES); // If a source explicitly stopped the run loop, or if there are no // sources or timers, go ahead and exit. if ((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished)) done = YES; // Check for any other exit conditions here and set the // done variable as needed. } while (!done); // Clean up code here. Be sure to release any allocated autorelease pools. }
CFRunLoopRunInMode在这里表示在kCFRunLoopDefaultMode模式下启动,时间长度为10秒,如果源的事件被处理runloop应该退出,而不是非要等到10秒耗尽。返回结果如下
时间耗尽(kCFRunLoopRunTimedOut)
被 CFRunLoopStop函数调用导致runloop(kCFRunLoopRunStopped)
源事件被处理了(kCFRunLoopRunHandledSource)
没有了任何源或定时器(kCFRunLoopRunFinished)
代码设定了一个标志位。并且开启runloop事件为10秒,当然也可以设定为其他的值。在每个源得到处理后返回结果,根据结果,状态设定标志位状态。最后可以进行一些线程退出前的操作。在这里可以依然像上面那些方法一样为runloop添加定时器和其他输入源和观察者。