RunLoop

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

Paste_Image.png

一.NSRunLoop
在Cocoa中,每個(gè)線程(NSThread)對(duì)象中內(nèi)部都有一個(gè)run loop(NSRunLoop)對(duì)象用來(lái)循環(huán)處理輸入事件,處理的事件包括兩類,一是來(lái)自Input sources的異步事件,一是來(lái)自Timer sources的同步事件。run Loop在處理輸入事件時(shí)會(huì)產(chǎn)生通知,可以通過(guò)向線程中添加run-loop observers來(lái)監(jiān)聽特定事件,以在監(jiān)聽的事件發(fā)生時(shí)做附加的處理工作。

每個(gè)run loop可運(yùn)行在不同的模式下,一個(gè)run loop mode是一個(gè)集合,其中包含其監(jiān)聽的若干輸入事件源、定時(shí)器、以及在事件發(fā)生時(shí)需要通知的run loop observers。運(yùn)行在一種mode下的run loop只會(huì)處理其run loop mode中包含的輸入源事件、定時(shí)器事件、以及通知run loop mode中包含的observers。

Cocoa中的預(yù)定義模式有:
Default模式
定義:NSDefaultRunLoopMode (Cocoa) kCFRunLoopDefaultMode (Core Foundation)
描述:默認(rèn)模式中幾乎包含了所有輸入源(NSConnection除外),一般情況下應(yīng)使用此模式。
Connection模式
定義:NSConnectionReplyMode(Cocoa)
描述:處理NSConnection對(duì)象相關(guān)事件,系統(tǒng)內(nèi)部使用,用戶基本不會(huì)使用。
Modal模式
定義:NSModalPanelRunLoopMode(Cocoa)
描述:OS X的Modal面板事件。
Event tracking模式
定義:UITrackingRunLoopMode(cocoa)
描述:在拖動(dòng)loop或其他user interface tracking loops時(shí)處于此種模式下,在此模式下會(huì)限制輸入事件的處理。例如,當(dāng)手指按住UITableView拖動(dòng)時(shí)就會(huì)處于此模式。
Common模式
定義:NSRunLoopCommonModes (Cocoa) kCFRunLoopCommonModes (Core Foundation)
描述:這是一個(gè)偽模式,其為一組run loop mode的集合,將輸入源加入此模式意味著在Common Modes中包含的所有模式下都可以處理。在Cocoa應(yīng)用程序中,默認(rèn)情況下Common Modes包含Default Modes、Modal Modes、Event Tracking Modes.可使用CFRunLoopAddCommonMode方法向Common Modes中添加自定義modes。

獲取當(dāng)前線程的run loop mode
NSString* runLoopMode = [[NSRunLoop currentRunLoop] currentMode];

二.NSTimerNSURLConnectionUITrackingRunLoopMode
NSTimer與NSURLConnection默認(rèn)運(yùn)行在default mode下,這樣當(dāng)用戶在拖動(dòng)UITableView處于UITrackingRunLoopMode模式時(shí),NSTimer不能fire,NSURLConnection的數(shù)據(jù)也無(wú)法處理。NSTimer的例子:在一個(gè)UITableViewController中啟動(dòng)一個(gè)0.2s的循環(huán)定時(shí)器,在定時(shí)器到期時(shí)更新一個(gè)計(jì)數(shù)器,并顯示在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上顯示的數(shù)字+1,但當(dāng)你拖動(dòng)或按住tableView時(shí),label上的數(shù)字不再更新,當(dāng)你手指離開時(shí),label上的數(shù)字繼續(xù)更新。當(dāng)你拖動(dòng)UITableView時(shí),當(dāng)前線程run loop處于UIEventTrackingRunLoopMode模式,在這種模式下,不處理定時(shí)器事件,即定時(shí)器無(wú)法fire,label上的數(shù)字也就無(wú)法更新。
解決方法:
一種方法是在另外的線程中處理定時(shí)器事件,可把Timer加入到NSOperation中在另一個(gè)線程中調(diào)度;
還有一種方法是修改Timer運(yùn)行的run loop模式,將其加入到UITrackingRunLoopMode模式或NSRunLoopCommonModes模式中。即[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];或[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

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

NSURLConnection也是如此,見SDWebImage中的描述,以及SDWebImageDownloader.m代碼中的實(shí)現(xiàn)。修改NSURLConnection的運(yùn)行模式可使用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,卻很少見到它。并且,我們?cè)诖蠖鄶?shù)情況下,都不需要顯式的創(chuàng)建或者啟動(dòng)RunLoop,有兩種情況,我們卻必須手動(dòng)設(shè)置它:
1、在分線程中使用定時(shí)器
定時(shí)器的實(shí)現(xiàn)便是基于runloop的,平時(shí)我們使用定時(shí)器你或許并沒(méi)有對(duì)runloop做什么操作,那是因?yàn)橹骶€程的runloop默認(rèn)是開啟運(yùn)行的,如果我們?cè)诜志€程中也需要重復(fù)執(zhí)行某一動(dòng)作,如下:

- (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");
}

你會(huì)發(fā)現(xiàn),程序運(yùn)行后并沒(méi)有打印任何信息,方法并沒(méi)有被調(diào)用,我們必須在線程中手動(dòng)的執(zhí)行如下代碼:

   [[NSRunLoop currentRunLoop] run];

定時(shí)器才能正常工作。
2、當(dāng)你在線程中使用如下方法時(shí)
某些延時(shí)函數(shù)和選擇器在分線程中的使用,我們也必須手動(dòng)開啟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有兩個(gè)版本:Source0 和 Source1。
Source0 只包含了一個(gè)回調(diào)(函數(shù)指針),它并不能主動(dòng)觸發(fā)事件。使用時(shí),你需要先調(diào)用 CFRunLoopSourceSignal(source),將這個(gè) Source 標(biāo)記為待處理,然后手動(dòng)調(diào)用 CFRunLoopWakeUp(runloop) 來(lái)喚醒 RunLoop,讓其處理這個(gè)事件。
Source1 包含了一個(gè) mach_port 和一個(gè)回調(diào)(函數(shù)指針),被用于通過(guò)內(nèi)核和其他線程相互發(fā)送消息。這種 Source 能主動(dòng)喚醒 RunLoop 的線程。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容