RunLoop

Run loop接收輸入事件來自兩種不同的來源:輸入源(input source)和定時源(timer source)。
輸入源又分為基于端口、基于自定義、基于perform selector。

Paste_Image.png

一.NSRunLoop
在Cocoa中,每個線程(NSThread)對象中內部都有一個run loop(NSRunLoop)對象用來循環處理輸入事件,處理的事件包括兩類,一是來自Input sources的異步事件,一是來自Timer sources的同步事件。run Loop在處理輸入事件時會產生通知,可以通過向線程中添加run-loop observers來監聽特定事件,以在監聽的事件發生時做附加的處理工作。

每個run loop可運行在不同的模式下,一個run loop mode是一個集合,其中包含其監聽的若干輸入事件源、定時器、以及在事件發生時需要通知的run loop observers。運行在一種mode下的run loop只會處理其run loop mode中包含的輸入源事件、定時器事件、以及通知run loop mode中包含的observers。

Cocoa中的預定義模式有:
Default模式
定義:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)
描述:默認模式中幾乎包含了所有輸入源(NSConnection除外),一般情況下應使用此模式。
Connection模式
定義:NSConnectionReplyMode(Cocoa)
描述:處理NSConnection對象相關事件,系統內部使用,用戶基本不會使用。
Modal模式
定義:NSModalPanelRunLoopMode(Cocoa)
描述:OS X的Modal面板事件。
Event tracking模式
定義:UITrackingRunLoopMode(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];

二.NSTimer、NSURLConnectionUITrackingRunLoopMode
NSTimer與NSURLConnection默認運行在default mode下,這樣當用戶在拖動UITableView處于UITrackingRunLoopMode模式時,NSTimer不能fire,NSURLConnection的數據也無法處理。NSTimer的例子:在一個UITableViewController中啟動一個0.2s的循環定時器,在定時器到期時更新一個計數器,并顯示在label上。

-(void)viewDidLoad{
 label =[[UILabel alloc]initWithFrame:CGRectMake(10, 100, 100, 50)]; 
[self.view addSubview:label]; 
count = 0; 
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval: 1 target: self selector: @selector(incrementCounter:) userInfo: nil repeats: YES];
}
- (void)incrementCounter:(NSTimer *)theTimer{ 
count++; 
label.text = [NSString stringWithFormat:@"%d",count];
}

在正常情況下,可看到每隔0.2s,label上顯示的數字+1,但當你拖動或按住tableView時,label上的數字不再更新,當你手指離開時,label上的數字繼續更新。當你拖動UITableView時,當前線程run loop處于UIEventTrackingRunLoopMode模式,在這種模式下,不處理定時器事件,即定時器無法fire,label上的數字也就無法更新。
解決方法:
一種方法是在另外的線程中處理定時器事件,可把Timer加入到NSOperation中在另一個線程中調度;
還有一種方法是修改Timer運行的run loop模式,將其加入到UITrackingRunLoopMode模式或NSRunLoopCommonModes模式中。即[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];或[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

- (void)viewDidLoad{  
    [super viewDidLoad];  
    NSLog(@"主線程 %@", [NSThread currentThread]);  
    //創建并執行新的線程  
    NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(newThread) object:nil];  
    [thread start];  
}  
- (void)newThread{  
   @autoreleasepool{  
    //在當前Run Loop中添加timer,模式是默認的NSDefaultRunLoopMode  
    [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(timer_callback) userInfo:nil repeats:YES];  
    //開始執行新線程的Run Loop,如果不啟動run loop,timer的事件是不會響應的  
    [[NSRunLoop currentRunLoop] run];  
    }  
}  
- (void)timer_callback{  
    NSLog(@"Timer %@", [NSThread currentThread]);  
}  

NSURLConnection也是如此,見SDWebImage中的描述,以及SDWebImageDownloader.m代碼中的實現。修改NSURLConnection的運行模式可使用scheduleInRunLoop:forMode:方法。

NSURLRequest *request = [[NSURLRequest alloc] initWithURL:url cachePolicy:NSURLRequestReloadIgnoringLocalCacheData timeoutInterval:15]; 
NSURLConnection *connection = [[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes];
[connection start];

我們一直在使用RunLoop,卻很少見到它。并且,我們在大多數情況下,都不需要顯式的創建或者啟動RunLoop,有兩種情況,我們卻必須手動設置它:
1、在分線程中使用定時器
定時器的實現便是基于runloop的,平時我們使用定時器你或許并沒有對runloop做什么操作,那是因為主線程的runloop默認是開啟運行的,如果我們在分線程中也需要重復執行某一動作,如下:

- (void)viewDidLoad {  
  [super viewDidLoad];  
  // Do any additional setup after loading the view, typically from a nib.    
queue = dispatch_queue_create("myQueue", DISPATCH_QUEUE_CONCURRENT);    
dispatch_async(queue, ^{       
 NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(time) userInfo:nil repeats:YES];   
 });   
 }
-(void)time{  
  NSLog(@"run");
}

你會發現,程序運行后并沒有打印任何信息,方法并沒有被調用,我們必須在線程中手動的執行如下代碼:

   [[NSRunLoop currentRunLoop] run];

定時器才能正常工作。
2、當你在線程中使用如下方法時
某些延時函數和選擇器在分線程中的使用,我們也必須手動開啟runloop,這些方法如下:

@interface NSObject (NSDelayedPerforming)
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;
- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget selector:(SEL)aSelector object:(id)anArgument;
+ (void)cancelPreviousPerformRequestsWithTarget:(id)aTarget;
@end

@interface NSRunLoop (NSOrderedPerform)
- (void)performSelector:(SEL)aSelector target:(id)target argument:(id)arg order:(NSUInteger)order modes:(NSArray *)modes;
- (void)cancelPerformSelector:(SEL)aSelector target:(id)target argument:(id)arg;
- (void)cancelPerformSelectorsWithTarget:(id)target;
@end

Source有兩個版本:Source0 和 Source1。
Source0 只包含了一個回調(函數指針),它并不能主動觸發事件。使用時,你需要先調用 CFRunLoopSourceSignal(source),將這個 Source 標記為待處理,然后手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop,讓其處理這個事件。
Source1 包含了一個 mach_port 和一個回調(函數指針),被用于通過內核和其他線程相互發送消息。這種 Source 能主動喚醒 RunLoop 的線程。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,983評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,772評論 3 422
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,947評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,201評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,960評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,350評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,406評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,549評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,104評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,914評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,089評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,647評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,340評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,753評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,007評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,834評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,106評論 2 375

推薦閱讀更多精彩內容