跨執行緒更新UI
在客戶端開發的過程中,我們經常碰到的問題有可能就是 IO 請求完成後,在主執行緒中更新 UI 這件事了,看見這個問題,我們一般會直接想到 Handler
這個大殺器,
Android
中我們知道有Looper
和Handler
這兩種神器幫我們完成不同執行緒間的排程,那麼在iOS
中如何實現不同執行緒間的切換呢?答案就是NSOperationQueue
和NSOperation
。變數名字直接翻譯就是操作佇列
和操作
,那麼,更新 UI 就是一個操作
。 因為Objective-C
帶了block
和selector
兩個神器,使用閉包比在java
中使用Runnable
方便許多,所以我們的NSOperation
更像是一個Runnable
,而NSOperationQueue
就像Looper
和Handler
的結合體,我們來看看如何建立一個 UI 執行緒上的訊息佇列吧。
主執行緒上的訊息佇列
建立一個主執行緒上的訊息佇列,只用一條函式
NSOperationQueue *queue = [NSOperationQueue mainQueue];
這和java
建立一個mainLooper
上的Handler
如出一轍:
Handler uiHandler = new Handler(Looper.mainLooper());
從客戶端的層面上來說,這兩句話是等價的。
然後我們呼叫
[queue addOperation:...]
傳入一個Operation
,OS就會在適當的時候,在主執行緒上回撥我們的Operation
,這和Handler
呼叫handlerMessage
是如何的一致啊~
子執行緒上的訊息佇列
那麼,如何在子執行緒上呼叫一個NSOperation
呢?
我們知道在Android
中,需要將Handler
的初始化執行在子執行緒上,因為這樣才能用Looper.myLooper()
獲取執行緒本地變數例項。
但是在iOS中,我們不需要顯示的在一條新的執行緒中完成我們的工作,我們只需要使用傳統的分配物件的方法:
NSOperationQueue *queue = [NSOperationQueue mainQueue];
OS這時候已經自動幫我們分配了一個訊息佇列(但是不同的是,它並不像Android OS上一樣,是繫結到執行緒上的,也就是說,這個訊息佇列,可以併發),我們只需要像上面一樣,使用[queue addOperation:...]
即可。
實驗
我們來看看例項好了,先看mainQueue
上的實驗:
self.queue = [NSOperationQueue mainQueue];
[self.queue addOperation:[NSBlockOperation blockOperationWithBlock:^() {
NSLog(@"%@", [NSThread currentThread]);
}]];
Output:
2016-02-28 15:10:38.813 OperationQueue[1277:52212] <NSThread: 0x7fd44a505670>{number = 1, name = main}
看到我們這個block
的執行的確是在主執行緒上的。
然後看新的訊息佇列:
self.queue = [[NSOperationQueue alloc] init];
[self.queue addOperation:[NSBlockOperation blockOperationWithBlock:^() {
NSLog(@"%@", [NSThread currentThread]);
}]];
Output:
2016-02-28 15:23:03.954 OperationQueue[1375:64592] <NSThread: 0x7fe10b5136d0>{number = 2, name = (null)}
這就不是在主執行緒了,那麼在iOS
上使用”Handler
“或者說訊息佇列的方法就是這麼簡單啦~
One more thing —— 佇列和執行緒的繫結
上一個章節我們說,在iOS中,執行緒和佇列並不是一一對應繫結的,我們可以簡單的理解成在iOS
中,除了main queue
以外,自己生成的佇列是可以併發的,簡單的操作就是在生成佇列的時候,指定maxConcurrentOperationCount
屬性即可,我們的操作佇列
就具有了併發的功能(不過這樣嚴格意義上就不是FIFO,也就是說,它其實不應該被稱作”佇列”了)