runloop 虽然是与线程想关的重要概念,但 cocoa 中的 runloop 终是用得不多,观相关博文却也未得入门其“why”。所以浅习几日,得一粗陋分享浅文,作为笔记,写下其所以然。有不对或错误的地方,还望指教,不甚感激。
线程在执行完后,会被销毁。为了使线程能一直运行,咱们可以在线程里边弄个运行循环(run loop),让线程一直执行:
- (void)myThread:(id)sender
{
while (TRUE) {
@autoreleasepool {
//do some jobs
//break in some condition
usleep(10000);
}
}
}
好了,现在线程能够一直运行了。新任务来了:在这个线程运行的同时,还可以从其它线程里往它里面随意增加或去掉不同的计算任务。这就是 NSRunloop 强大的地方了。
咱们现在来简单地进化一下:
NSMutableArray *targetQueue;
NSMutableArray *actionQueue;
- (void)myThread:(id)sender
{
while (TRUE) {
@autoreleasepool {
//do some jobs
//break in some condition
NSUInteger targetCount = [targetQueue count];
for(NSUInteger index = 0; index < targetCount; ++index){
id target = targetQueue[index];
SEL action = NSSelectorFromString(actionQueue[index]);
if ([target respondsToSelector:action]) {
[target performSelector:action withObject:nil];
}
}
usleep(10000);
}
}
}
从这里,我们可以在其它线程中向 targetQueue和 actionQueue同时加入对象和方法时,这线程可以执行动态添加的代码了。
所谓runloop,就是下面这个结构:
while (TRUE) {
//break in some condition
}
这个结构就是线程的 runloop,它和 NSRunloop这个类的名字很像,但实际上完全不是一个 东西。那 NSRunloop是个啥东西呢?咱们再看以下代码:
@interface MyNSTimer : NSObject
{
id target;
SEL action;
float interval;
CFAbsoluteTime lasttime;
}
- (void)invoke;
@end
@implementation MyNSTimer
- (void)invoke;
{
if ([target respondsToSelector:action]) {
[target performSelector:action withObject:nil];
}
}
@end
#!objc
@interface MyNSRunloop : NSObject
{
NSMutableArray *timerQueue;
}
- (void)addTimer:(MyNSTimer*)t;
- (void)executeOnce;
@end
@implementation MyNSRunloop
- (void)addTimer:(MyNSTimer*)t;
{
@synchronized(timerQueue){
[timerQueue addObject:t];
}
}
- (void)executeOnce;
{
CFAbsoluteTime currentTime=CFAbsoluteTimeGetCurrent();
@synchronized(timerQueue){
for(MyNSTimer *t in timerQueue){
if(currentTime-t.lasttime>t.interval){
t.lasttime=currentTime;
[t invoke];
}
}
}
}
@end
@interface MyNSThread : NSObject
{
MyNSRunloop *runloop;
}
- (void)main:(id)sender;
@end
@implementation MyNSThread
- (void)main:(id)sender
{
while (TRUE) {
@autoreleasepool {
//do some jobs
//break in some condition
[runloop executeOnce];
usleep(10000);
}
}
}
@end
走到这里,我们就算是基本把Runloop结构抽象出来了。例如我有一个MyNSThread实例,myThread1。我可以给这个实例的线程添加需要的任务,而myThread1内部的MyNSRunloop对象会管理好这些任务。
MyNSTimer *timer1=[MyNSTimer scheduledTimerWithTimeInterval:1 target:obj1 selector:@selector(download1:)];
[myThread1.runloop addTimer:timer1];
MyNSTimer *timer2=[MyNSTimer scheduledTimerWithTimeInterval:2 target:obj2 selector:@selector(download2:)];
[myThread1.runloop addTimer:timer2];
咱们知道,在 iOS中,用户体验是极其重要的。比如 UITableView在滑动时极为顺畅,界面的更新是由主线程负责的,但即使咱们以默认的方式加再多的 NSTimer定时任务到主线程中,即也不会对 UITableView的滑动造成影响。主线程是怎么做到这点的?这就跟 run loop model相关了。
咱们再来改进一下代码:
@interface MyNSRunloopMode : NSObject {
NSMutableArray *_timerQueue;
NSString *_name;
}
- (void)addTimer:(MyNSTimer *)timer;
- (void)executeOnce;
@end
@interface MyNSRunloop : NSObject
{
NSMutableSet *_modes;
MyNSRunloopMode *_currentMode;
}
- (void)addTimer:(MyNSTimer*)t;
- (void)addTimer:(MyNSTimer *)t forMode:(NSString *)mode;
- (void)executeOnce;
@end
// 实现文件
@implementation MyNSRunloopMode
- (void)executeOnce {
CFAbsoluteTime currentTime=CFAbsoluteTimeGetCurrent();
@synchronized(timerQueue){
for(MyNSTimer *t in timerQueue){
if(currentTime-t.lasttime>t.interval){
t.lasttime=currentTime;
[t invoke];
}
}
}
}
@end
@implementation MyNSRunloop
- (void)addTimer:(MyNSTimer *)timer {
[self addTimer:timer forMode:@"NSDefaultRunLoopMode"];
}
- (void)addTimer:(MyNSTimer *)timer forMode:(NSString *)modeName {
MyNSRunloopMode *mode = nil;
for (mode in _modes) {
if ([mode.name isEqualToString:modeName]) {
break;
}
}
[mode addTimer:timer];
}
- (void)executeOnce {
[_currentMode executeOnce];
}
@end
咱们又添加了一个类:MyNSRunloopMode,把原本在 NSRunloop的执行任务放到这个类的对象里面去了。而 NSRunloop则有一个mode的集合,并有一个 currentMode。runloop在任何时候,都只可能运行在currentMode下,也就是说,它此时只会执行该 mode下的任务。咱们可以指定当前 NSRunloop的 mode:[NSRunLoop runMode:beforeDate:]
。
系统给定义了好几个mode,而每个 model都有自己的名字,其中有一个就是NSDefaultRunLoopMode。咱们在添加任务到run loop中时,只需要指定相应model的名字,就会把任务添加到相应的model中去了。
我想你也猜到了吧,正是因为 NSTimer默认会加到NSDefaultRunLoopMode中,而在 UITableView滑动时,主线程却不在这个 mode之下(而是切到NSEventTrackingRunLoopMode中),所以 NSTimer的任务压根儿就不会被执行,当然也就不会对滑动有丝毫影响啦。在滑动完后,主线程又会切到NSDefaultRunLoopMode中,此时 NSTimer的任务又可以执行了。当然咱们还是可以将 NSTimer加到任何 mode之中:[NSRunLoop addTimer:forModel:]
。
怎么样,这个 mode的引入还是蛮给力的吧!
本文介绍了 run loop的基本的原理,runloop的真实情况自然是复杂得多,便万变不离其宗,你要有兴趣,也可以研究研究它的实现。
参考:
1、http://lianxu.me/2012/11/10-cocoa-objc-newbie-problems/
2、https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html
原文地址:http://blog.csdn.net/womendeaiwoming/article/details/44968501