RunLoop的理解与使用

什么情况下使用runloop?

runloop好比就是跑圈,就是一个线程一直在做某一件事情。

一般主线程会自动运行runloop,我们一般情况下不用管。而在子线程中,我们需要手动去运行它。你可以把它想象成一个循环,一般情况下我们没有必要去启动线程的runloop,例如:在需要一个单独的线程长时间的去检测某一个事件,具体看需求而定了。如果没有这个循环,子线程完成任务后,这个线程就结束了。所以这个时候我们就要运行一个runloop,用于处理种种事件,而让它不结束。而没有事件发生的时候, 会处于休眠状态,以节省电量。

那么一般在什么情况下用到呢:

1.需要使用Port(基于端口)或者自定义(事件源)Input Source与其他线程进行通讯。

2.需要在线程中使用Timer。

3.需要在线程上使用performSelector*****方法。

4.需要让线程执行周期性的工作。

例:常驻线程

创建一个线程来处理耗时且频繁的操作,例如即时聊天音频的压缩,或者经常下载,避免频繁开启线程以便提高性能, AFNetWorking就是如此。

[[NSThreadcurrentThread] setName:@"AFNetworking"];

NSRunLoop*runLoop = [NSRunLoopcurrentRunLoop];

 [runLoop addPort:[NSMachPortport] forMode:NSDefaultRunLoopMode]; 

 [runLoop run];

RunLoop 对外的接口

在 CoreFoundation 里面关于 RunLoop 有5个类:

CFRunLoopRef

CFRunLoopModeRef

CFRunLoopSourceRef

CFRunLoopTimerRef

CFRunLoopObserverRef

其中 CFRunLoopModeRef 类并没有对外暴露,只是通过 CFRunLoopRef 的接口进行了封装。他们的关系如下:

一个 RunLoop 包含若干个 Mode,每个 Mode 又包含若干个 Source/Timer/Observer。每次调用 RunLoop 的主函数时,只能指定其中一个 Mode,这个Mode被称作 CurrentMode。如果需要切换 Mode,只能退出 Loop,再重新指定一个 Mode 进入。这样做主要是为了分隔开不同组的 Source/Timer/Observer,让其互不影响。

CFRunLoopSourceRef是事件产生的地方。Source有两个版本:Source0 和 Source1。

Source0 只包含了一个回调(函数指针),它并不能主动触发事件。使用时,你需要先调用 CFRunLoopSourceSignal(source),将这个 Source 标记为待处理,然后手动调用 CFRunLoopWakeUp(runloop) 来唤醒 RunLoop,让其处理这个事件。

Source1 包含了一个 mach_port 和一个回调(函数指针),被用于通过内核和其他线程相互发送消息。这种 Source 能主动唤醒 RunLoop 的线程,其原理在下面会讲到。

CFRunLoopTimerRef是基于时间的触发器,它和 NSTimer 是toll-free bridged 的,可以混用。其包含一个时间长度和一个回调(函数指针)。当其加入到 RunLoop 时,RunLoop会注册对应的时间点,当时间点到时,RunLoop会被唤醒以执行那个回调。

CFRunLoopObserverRef是观察者,每个 Observer 都包含了一个回调(函数指针),当 RunLoop 的状态发生变化时,观察者就能通过回调接受到这个变化。

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

};

Source/Timer/Observer 被统称为 mode item,一个 item 可以被同时加入多个 mode。但一个 item 被重复加入同一个 mode 时是不会有效果的。如果一个 mode 中一个 item 都没有,则 RunLoop 会直接退出,不进入循环。

RunLoop各种模式(CFRunLoopModeRef)

每个run loop可运行在不同的模式下,一个run loop mode是一个集合,其中包含其监听的若干输入事件源,定时器,以及在事件发生时需要通知的run loop observers。运行在一种mode下的run loop只会处理其run loop mode中包含的输入源事件,定时器事件,以及通知run loop mode中包含的observers。

CFRunLoopModeRef 代表 RunLoop 的运行模式

一个 RunLoop 包含若干个 Mode,每个Mode 又包含若干个 Source/Timer、Observer

每次 RunLoop 启动时,只能制定其中一个 Mode,这个 Mode 被称作 CurrentMode

如果需要切换 Mode,只能退出 Loop,再重新制定一个 Mode 进入

这样做主要是为了分割开不同组的 Source/Timer/Observer,让其互不影响

Cocoa中的预定义模式有:

Default模式

定义:NSDefaultRunLoopMode(Cocoa)kCFRunLoopDefaultMode(Core Foundation)

描述:默认模式中几乎包含了所有输入源(NSConnection除外),一般情况下应使用此模式。

Connection模式

定义:NSConnectionReplyMode(Cocoa)

描述:处理NSConnection对象相关事件,系统内部使用,用户基本不会使用。

Modal模式

定义:NSModalPanelRunLoopMode(Cocoa)

描述:处理modal panels事件。

Event tracking模式

定义:UITrackingRunLoopMode(iOS) NSEventTrackingRunLoopMode(cocoa)

描述:在拖动loop或其他user interface tracking loops时处于此种模式下,在此模式下会限制输入事件的处理。例如,当手指按住UITableView拖动时就会处于此模式。

Common模式

定义:NSRunLoopCommonModes(Cocoa)kCFRunLoopCommonModes(Core Foundation)

描述:这是一个伪模式,其为一组run loop mode的集合,将输入源加入此模式意味着在Common Modes中包含的所有模式下都可以处理。在Cocoa应用程序中,默认情况下Common Modes包含default modes,modal modes,event Tracking modes.可使用CFRunLoopAddCommonMode方法想Common Modes中添加自定义modes。

获取当前线程的run loop mode

NSString* runLoopMode = [[NSRunLoop currentRunLoop] currentMode];

RunLooop与Source

正如其名所示,是线程进入和被线程用来响应事件以及调用事件处理函数的地方。需要在代码中使用控制语句实现run loop的循环,也就是说,需要代码提供while 或者 for循环来驱动run loop。

在这个循环中,使用一个Runloop对象[NSRunloop currentRunloop]执行接收消息,调用对应的处理函数。

Runloop接收两种源事件:input sources和timer sources。

input sources传递异步事件,通常是来自其他线程和不同的程序中的消息;

timer sources(定时器)传递同步事件(重复执行或者在特定时间上触发)。

除了处理input sources,Runloop 也会产生一些关于本身行为的notificaiton。注册成为Runloop的observer,可以接收到这些notification,做一些额外的处理。(使用CoreFoundation来成为runloop的observer)。

定时源在预设的时间点同步方式传递消息,这些消息都会发生在特定时间或者重复的时间间隔。定时源则直接传递消息给处理例程,不会立即退出run loop。

需要注意的是,尽管定时器可以产生基于时间的通知,但它并不是实时机制。和输入源一样,定时器也和你的run loop的特定模式相关。如果定时器所在的模式当前未被run loop监视,那么定时器将不会开始直到run loop运行在相应的模式下。类似的,如果定时器在run loop处理某一事件期间开始,定时器会一直等待直到下次run loop开始相应的处理程序。如果run loop不再运行,那定时器也将永远不启动。

创建定时器源有两种方法,

方法一:

NSTimer *timer = [NSTimerscheduledTimerWithTimeInterval:4.0 target:self selector:@selector(backgroundThreadFire:) userInfo:nil  repeats:YES];

[[NSRunLoop currentRunLoop]addTimer:timerforMode:NSDefaultRunLoopMode];

方法二:

[NSTimerscheduledTimerWithTimeInterval:10  target:self  selector:@selector(backgroundThreadFire:)

userInfo:nil  repeats:YES];

RunLooop与NSTimer(CFRunLoopTimerRef)

NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

// 定时器只运行在NSDefaultRunLoopMode下,一旦RunLoop进入其他模式,这个定时器就不会工作

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];

// 定时器只运行在UITrackingRunLoopMode下(拖拽ScrollView会来到这个模式),一旦RunLoop进入其他模式,这个定时器就不会工作

[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];

// 标记为NSRunLoopCommonModes的模式:会运行在UITrackingRunLoopMode或NSDefaultRunLoopMode模式下(注意只能运行在一种模式下,“或”)

[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

----------

// 调用了scheduledTimer返回的定时器,已经自动被添加到当前runLoop中,而且是NSDefaultRunLoopMode

NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];

RunLoop的事件队列

每次运行run loop,你线程的run loop对会自动处理之前未处理的消息,并通知相关的观察者。具体的顺序如下:

因为定时器和输入源的观察者是在相应的事件发生之前传递消息,所以通知的时间和实际事件发生的时间之间可能存在误差。如果需要精确时间控制,你可以使用休眠和唤醒通知来帮助你校对实际发生事件的时间。

因为当你运行run loop时定时器和其它周期性事件经常需要被传递,撤销run loop也会终止消息传递。典型的例子就是鼠标路径追踪。因为你的代码直接获取到消息而不是经由程序传递,因此活跃的定时器不会开始直到鼠标追踪结束并将控制权交给程序。

Run loop可以由run loop对象显式唤醒。其它消息也可以唤醒run loop。例如,添加新的非基于端口的源会唤醒run loop从而可以立即处理输入源而不需要等待其他事件发生后再处理。

从这个事件队列中可以看出:

①如果是事件到达,消息会被传递给相应的处理程序来处理, runloop处理完当次事件后,run loop会退出,而不管之前预定的时间到了没有。你可以重新启动run loop来等待下一事件。

②如果线程中有需要处理的源,但是响应的事件没有到来的时候,线程就会休眠等待相应事件的发生。这就是为什么run loop可以做到让线程有工作的时候忙于工作,而没工作的时候处于休眠状态。

RunLoop观察者

源是在合适的同步或异步事件发生时触发,而run loop观察者则是在run loop本身运行的特定时候触发。你可以使用run loop观察者来为处理某一特定事件或是进入休眠的线程做准备。你可以将run loop观察者和以下事件关联:

1.  Runloop入口

2.  Runloop何时处理一个定时器

3.  Runloop何时处理一个输入源

4.  Runloop何时进入睡眠状态

5.  Runloop何时被唤醒,但在唤醒之前要处理的事件

6.  Runloop终止

和定时器类似,在创建的时候你可以指定run loop观察者可以只用一次或循环使用。若只用一次,那么在它启动后,会把它自己从run loop里面移除,而循环的观察者则不会。定义观察者并把它添加到run loop,只能使用Core Fundation。下面的例子演示了如何创建run loop的观察者:

- (void)addObserverToCurrentRunloop

{

// The application uses garbage collection, so noautorelease pool is needed.

NSRunLoop*myRunLoop = [NSRunLoop currentRunLoop];

// Create a run loop observer and attach it to the runloop.

CFRunLoopObserverContextcontext = {0,self,NULL,NULL,NULL};

CFRunLoopObserverRef    observer =CFRunLoopObserverCreate(kCFAllocatorDefault,

kCFRunLoopBeforeTimers,YES,0, &myRunLoopObserver, &context);

if(observer)

{

CFRunLoopRefcfLoop = [myRunLoopgetCFRunLoop];

CFRunLoopAddObserver(cfLoop, observer, kCFRunLoopDefaultMode);

}

}

其中,kCFRunLoopBeforeTimers表示选择监听定时器触发前处理事件,后面的YES表示循环监听。

Runloop工作的特点:

1> 当有事件发生时,Runloop会根据具体的事件类型通知应用程序作出响应;

2> 当没有事件发生时,Runloop会进入休眠状态,从而达到省电的目的;

3> 当事件再次发生时,Runloop会被重新唤醒,处理事件。

runloop与线程阻塞:

举个例子:

定义一个NSTimer来隔一会调用某个方法  但这时你在拖动textVIew不放手  主线程就被占用了。 timer的监听方法就不调用  直到你松手 ,,这时吧nstimer加到 runloop里  就相当于告诉主循环 腾出点时间来给timer  ,再拖动textView就不会因主线程被占用而不调用了。

runloop也可以阻塞线程,等待其他线程执行后再执行。

@implementation ViewController{

BOOL end;

}

– (void)viewDidLoad

{

[super viewDidLoad];

NSLog(@”start new thread …”);

// 创建一个新的线程去执行runOnNewThread方法。

[NSThread detachNewThreadSelector:@selector(runOnNewThread) toTarget:self withObject:nil];

while (!end) {

NSLog(@”runloop…”);

[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];

NSLog(@”runloop end.”);

}

NSLog(@”ok.”);

}

-(void)runOnNewThread{

NSLog(@”run for new thread …”);

sleep(1);

//向主线程发送消息(让主线程执行setEnd方法,改变end值)

[self performSelectorOnMainThread:@selector(setEnd) withObject:nil waitUntilDone:NO];

NSLog(@”end.”);

}

-(void)setEnd{

end=YES;

}

runloop 的API理解:

+ (NSRunLoop*)currentRunLoop;//获得当前线程的run loop

+ (NSRunLoop *)mainRunLoop;//获得主线程的run loop

- (void)run;//进入处理事件循环,如果没有事件则立刻返回。注意:主线程上调用这个方法会导致无法返回(进入无限循环,虽然不会阻塞主线程),因为主线程一般总是会有事件处理。

- (void)runUntilDate:(NSDate*)limitDate;//同run方法,增加超时参数limitDate,避免进入无限循环。使用在UI线程(亦即主线程)上,可以达到暂停的效果。

- (BOOL)runMode:(NSString*)mode beforeDate:(NSDate*)limitDate;//等待消息处理,好比在PC终端窗口上等待键盘输入。一旦有合适事件(mode相当于定义了事件的类型)被处理了,则立刻返回;类同run方法,如果没有事件处理也立刻返回;有否事件处理由返回布尔值判断。同样limitDate为超时参数。

- (void)acceptInputForMode:(NSString*)mode beforeDate:(NSDate*)limitDate;//似乎和runMode:差不多(测试过是这种结果,但确定是否有其它特殊情况下的不同),没有BOOL返回值。

下面罗列调用主线程的run loop的各种方式,读者可以加深理解:

[[NSRunLoop mainRunLoop] run];//主线程永远等待,但让出主线程时间片

[[NSRunLoopmainRunLoop]runUntilDate:[NSDatedistantFuture]];//等同上面调用

[[NSRunLoopmainRunLoop]runUntilDate:[NSDatedate]];//立即返回

[[NSRunLoop mainRunLoop] runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10.0]];//主线程等待,但让出主线程时间片,然后过10秒后返回

[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate distantFuture]];//主线程等待,但让出主线程时间片;有事件到达就返回,比如点击UI等。

[[NSRunLoopmainRunLoop]runMode:NSDefaultRunLoopModebeforeDate: [NSDatedate]];//立即返回

[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow:10.0]];//主线程等待,但让出主线程时间片;有事件到达就返回,如果没有则过10秒返回。

参考:http://blog.csdn.net/mideveloper/article/details/46310067

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容