Re:從零開始的RunLoop實踐
本系列文章,因我在網上看了很多RunLoop的文章之后(先膜拜各路大牛),感覺自己大概懂了,但是說實戰一下,又無從下碼,本著寫不出來代碼,會再多理論好比有槍開不出子彈,所以盡量以解決開發中實際問題為出發點,主要以網絡上的博客和Github找到的代碼為基礎(這都是大牛的功勞),總結出用以實戰的幾個demo,主要為了以后自己使用查找方便(所以此系列提及的理論巨少,基本都是代碼,觀眾老爺也可以直接復制粘貼使用起來),公布在網絡上,歡迎各位指出錯誤,幫助本人及觀看文章的大家成長學習。
Re:從零開始的Runloop實踐01-線程常駐與銷毀
本片博文的代碼地址:
https://github.com/zyzhangyu/RunLoopDemo
本小節部分代碼取自http://weibo.com/1794363822/CvDEHwuvX?type=comment大牛的Github
強烈推薦同行關注此人
實踐目的:保證線程長存,有需求時工作,沒有時則休息,屬于性能優化的一個方法。
先用最簡單的方法實現:
{
_zyThread = [[ZYThread alloc] initWithTarget:self selector:@selector(configRunLoop) object:nil];
[_zyThread setName:@"章魚線程"];
[_zyThread start];
}```
- (void)configRunLoop{
@autoreleasepool {
NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
[runLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[runLoop run];
}
}
使用`- (void)performSelector:(SEL)aSelector onThread`添加任務到RunLoop當中。示例如下:
``` [self performSelector:@selector(test) onThread:_zyThread withObject:nil waitUntilDone:NO];```
以上為最簡單的使用RunLoop實現線程常駐的方法,缺點是使用`[runLoop run];`開啟的RunLoop,在線程中使用`CFRunLoopStop(runLoopRef);`
也無法銷毀。
優點:簡單方便
缺點:難以結束
特點:實現了需要線程長存的目標,并且做到了有任務時執行,無任務時休眠的高效率。
可以改進的地方:
- 看不出哪里表現了,有任務時執行,無任務時休眠的效率
- 刪除困難,到需要結束時,難以結束
針對以上可以改進的兩點,才有了今天的主角—demo1.
核心代碼如下:

使用do while循環來作為一個控制RunLoop是否繼續的一個開關。
核心代碼全貌(30行代碼,重寫在NSThread的子類下面):

注釋1:
- 獲取當前的RunLoop,沒有的話會創建一個。
- 創建一個CFRunLoopObserverContext
- 創建一個CFRunLoopObserverRef
這三個東西都是名詞,百度一搜,解釋的大把大把。
注釋2:為RunLoop添加觀察者
注釋3:如果當前線程有當前設置的runMode下的事件發生,runloop就會啟動,處理對應的事件。如果沒有事件發生,runloop就會在每一次 `NSDate distantFuture到來時,啟動一次當前線程的runloop.`
只要將控制循環是否繼續的Bool類型變量continued設置為No,線程就將會退出。
接下來是證明,Runloop會在有任務時執行,無任務時休眠的部分:
兩段代碼:
void currentRunLoopObserver(CFRunLoopObserverRef observer, CFRunLoopActivity activity, void *info)
{
NSLog(@"Current thread Run Loop activity: %@", printActivity(activity));
}```
static inline NSString* printActivity(CFRunLoopActivity activity)
{
NSString *activityDescription;
switch (activity) {
case kCFRunLoopEntry:
activityDescription = @"kCFRunLoopEntry";
break;
case kCFRunLoopBeforeTimers:
activityDescription = @"kCFRunLoopBeforeTimers";
break;
case kCFRunLoopBeforeSources:
activityDescription = @"kCFRunLoopBeforeSources";
break;
case kCFRunLoopBeforeWaiting:
activityDescription = @"kCFRunLoopBeforeWaiting";
break;
case kCFRunLoopAfterWaiting:
activityDescription = @"kCFRunLoopAfterWaiting";
break;
case kCFRunLoopExit:
activityDescription = @"kCFRunLoopExit";
break;
default:
break;
}
return activityDescription;
}
解釋一下,上面代碼的作用就是—>在之前給RunLoop添加的觀察者,每個 Observer 都包含了一個回調(函數指針),當 RunLoop 的狀態發生變化時,觀察者就能通過回調接受到這個變化。回調內容是打印觀察到的狀態,可以觀測的時間點有以下幾個:
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理 Timer
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理 Source
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
};
這樣根據打印出來的狀態就可以確定,RunLoop到底是不是在有任務時執行,無任務時休眠了:
第一個分隔線之前,為RunLoop添加了第一個任務—射擊,我們知道
線程和 RunLoop 之間是一一對應的,其關系是保存在一個全局的 Dictionary 里。線程剛創建時并沒有 RunLoop,如果你不主動獲取,那它一直都不會有。RunLoop 的創建是發生在第一次獲取時,RunLoop 的銷毀是發生在線程結束時。你只能在一個線程的內部獲取其 RunLoop(主線程除外)。
第一個分隔線之前,我們可以看到RunLoop中,執行第一個任務到任務結束后,進入到kCFRunLoopBeforeWaiting一個完整的狀態變化過程,這也能說明RunLoop在執行完任務之后,休息了。
第一個和第二個分隔線之間,顯示了,RunLoop從休息到喚醒在到休息的過程。
第二個分隔線之后,是RunLoop從喚醒到結束的一個過程。
這個Log可以和這張圖互相輔助來看:
本片博文的代碼地址:
https://github.com/zyzhangyu/RunLoopDemo
參考資料:
http://weibo.com/1794363822/CvDEHwuvX?type=comment大牛的Github
http://blog.ibireme.com/2015/05/18/runloop/ 此篇為RunLoop寫的相當好的一篇 強烈建議大家學習 也希望我早日能寫出這種技術博文