最早接觸runloop的概念,是第一次用NSTimer的時候。一個最簡單的例子:
- (void)viewDidLoad
{
[super viewDidLoad];
// Do any additional setup after loading the view, typically from a nib.
NSTimer * timer = [NSTimer scheduledTimerWithTimeInterval:1
target:self
selector:@selector(printMessage:)
userInfo:nil
repeats:YES];
// [[NSRunLoop currentRunLoop] addTimer:timer
// forMode:NSRunLoopCommonModes];
}
如果我們同時在界面上滾動一個scrollview,那麼我們會發現在滾動停止之前,控制檯是不會有輸出的,就好像scrollView在滾動的時候將timer暫停了一樣。通過了解後發現,其實是Cocoa的RunLoop Mode在作怪。
我把runloop理解爲一種cocoa下的一種消息循環的機制,用來處理各種消息事件。我們在開發的時候一般並不需要手動去創建一個runloop,因爲在程序進入mainThread之後其實就爲我們創建了默認的的mainRunLoop,通過[NSRunloop currentRunLoop]我們就可以得到當前線程對應的RunLoop對象,而我們需要留意的是在多個runloop之間消息的通知方式。
接上面說到的,開啓一個NSTimer實質上是開啓了一個新的線程(Runloop)在當前Runloop中註冊了一個新的事件源,也就是說除了MainRunloop之外還有一個Runloop存在。而當scrollView在滾動的時候,當前MainRunLoop是處於UITrackingRunLoopMode,在該模式下,不會處理 NSDefaultRunLoopMode的消息(因爲Runloop Model不一致),而NSTimer在創建後的RunLoop(B)默認會以NSDefaultRunLoopMode與當前context的Runloop(A)發送消息進行通信。要想在scrollView滾動的同時也接受其他runloop的消息,則需要改變兩者之間的RunLoopMode
[[NSRunLoop currentRunLoop] addTimer:timer
forMode:NSRunLoopCommonModes];
類似的問題在前幾天修改一個http異步通信模塊的時候也碰到了,簡單地說是向服務器異步獲取圖片數據後通知主線程刷新tableView中的圖片,但在tableView滾動還沒有停止或用戶手指還停留在屏幕上的時候,圖片一直不會出來。後來發現請求數據的時候用到了NSURLConnection的
- (id)initWithRequest:(NSURLRequest *)request
delegate:(id)delegate;
瞭解後發現該方法創建的異步請求線程和NSTimer一樣,也是NSDefaultRunLoopMode的,與
- (id)initWithRequest:(NSURLRequest *)request
delegate:(id)delegate
startImmediately:(BOOL)startImmediately
不同的是,上面的方法默認創建後默認直接發起請求,並以NSDefaultRunLoopMode與runloop進行消息傳遞,因此我們需要和NSTimer一樣更改他的RunLoopMode。
NSURLConnection *connection = [[NSURLConnection alloc]
initWithRequest:request
delegate:self
startImmediately:NO];
[connection scheduleInRunLoop:[NSRunLoop currentRunLoop]
forMode:NSRunLoopCommonModes];
[connection start];