iOS 中 run loop 淺析
iOS 中 run loop 淺析
runloop 雖然是與線程想關的重要概念,但 cocoa 中的 runloop 終是用得不多,觀相關博文卻也未得入門其“why”。所以淺習幾日,得一粗陋分享淺文,作為筆記,寫下其所以然。有不對或錯誤的地方,還望指教,不甚感激。
run loop解惑
線程在執行完後,會被銷毀。為了使線程能一直運行,咱們可以線上程裡邊弄個運行迴圈(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];
run loop model解惑
咱們知道,在 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的真實情況自然是複雜得多,便萬變不離其宗,你要有興趣,也可以研究研究它的實現。