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的真實情況自然是復雜得多,便萬變不離其宗,你要有興趣,也可以研究研究它的實現。