iOS RunLoop實例代碼

前言

RunLoop在iOS中扮演著很重要的角色屿笼,關(guān)于RunLoop的詳細(xì)講解網(wǎng)上有許多,推薦YYKit作者的 深入理解RunLoop
我們要透徹的理解一樣?xùn)|西翁巍,最好的辦法是use it驴一。關(guān)于原理性的東西本文不在講解,我們在通過一些實例來了解和使用它吧灶壶。代碼在 GitHub Example
相信你看了YYKit作者的文章蛔趴,再把本文照著寫一遍的話理解的也就八九不離十啦??

相關(guān)簡介還是要有

首先我們來看一張圖,包括了RunLoop使用過程中的相關(guān)類
我們將逐個進(jìn)行使用來讓你理解它
PS:其實理解起來很是簡單 我們進(jìn)行簡化

RunLoop ->Models
Model -> Source/Observer/Timer

RunLoop示意圖.png

展示實例代碼包括

Runloop在主次線程的表現(xiàn)【RunLoop存在的條件】
RunLoop的休眠和喚醒停止【使用自定義source來喚醒RunLoop】
RunLoop Timer和Observer監(jiān)聽的使用

Runloop在主次線程的表現(xiàn)

線程和 RunLoop 之間是一一對應(yīng)的例朱,RunLoop 啟動前內(nèi)部必須要有至少一個 Timer/Source孝情,主線程中系統(tǒng)已經(jīng)注冊了,所以不會退出
我們先在storyboard中拖拽3個按鈕

1111.png

主線程RunLoop點(diǎn)擊

- (IBAction)mainRunLoopTest:(UIButton *)sender {

        NSLog(@"主線程RunLoop測試");
    NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
    //當(dāng)前runLoop 一直睡眠 直到被喚醒
    [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
//    主線程有監(jiān)聽等,比如點(diǎn)擊等等  將會喚醒   喚醒后打印
    NSLog(@"主線程RunLoop被喚醒接著跑");
}

次線程 RunLoop點(diǎn)擊


- (IBAction)otherRunLoopTest:(id)sender {
NSLog(@"次線程RunLoop測試");
    __block  NSUInteger count=0; 
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //        我們打印5次方便查看
        while (count<5) {
            NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
            //當(dāng)前runLoop 一直睡眠 直到被喚醒
            [runLoop runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
//             RunLoop 必須要有至少一個 Timer/Observer/Source 否則 runloop直接退出 直接打印
            NSLog(@"次線程沒有被暫停");
            count++;
        }
    });    
}

點(diǎn)擊測試 點(diǎn)擊

- (IBAction)testClick:(UIButton *)sender {
    NSLog(@"你點(diǎn)擊了");
}

我們分別點(diǎn)擊點(diǎn)擊之后查看打印洒嗤,主線程RunLoop在點(diǎn)擊之后并不會執(zhí)行打印NSLog(@"主線程RunLoop被喚醒接著跑");當(dāng)我們點(diǎn)擊測試點(diǎn)擊的時候打印如下

2017-03-31 12:28:31.744 JVRunLoopExample[913:40324] 主線程RunLoop被喚醒接著跑
2017-03-31 12:28:31.824 JVRunLoopExample[913:40324] 你點(diǎn)擊了```
當(dāng)我們點(diǎn)擊次線程RunLoop的時候 會直接執(zhí)行```NSLog(@"次線程沒有被暫停");``` 
**原因在注釋里已經(jīng)解釋**
#####RunLoop的休眠和喚醒
首先我們創(chuàng)建一個ViewController叫JVControlRunLoopViewController
在Main.storyboard中布局

![controlRunLoop.png](http://upload-images.jianshu.io/upload_images/2972133-6480878cab8dc678.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
JVControlRunLoopViewController持有對象

@implementation JVControlRunLoopViewController
{
CFRunLoopSourceRef source;
NSThread *_thread;
}

Create點(diǎn)擊事件
  • (IBAction)createRunLoopInNewThread:(UIButton *)sender {
    //開啟子線程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 持有這個線程
    _thread = [NSThread currentThread];
    [self creatRunLoop];
    });
    }

-(void)creatRunLoop{
NSLog(@"create RunLoop");
@autoreleasepool {
//source上下文
CFRunLoopSourceContext context = {0};
//指定事件回調(diào)
context.perform = DoNothingRunLoopCallback;
// 為source添加事件
source = CFRunLoopSourceCreate(NULL, 0, &context);
// source添加到RunLoop
CFRunLoopAddSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
//監(jiān)聽事件直到RunLoop停止
CFRunLoopRun();

    //停止RunLoop的時候 移除source
    CFRunLoopRemoveSource(CFRunLoopGetCurrent(), source, kCFRunLoopCommonModes);
    CFRelease(source);
    NSLog(@"thread has stopped");
}

}

事件的回調(diào)

static void DoNothingRunLoopCallback(void *info)
{

NSLog(@"do something");

}

喚醒RunLoop 點(diǎn)擊事件
  • (IBAction)wakeUpRunLoop:(id)sender {
    //去RunLoop 的線程取得 RunLoop
    [self performSelector:@selector(wakeUP)
    onThread:_thread
    withObject:nil
    waitUntilDone:NO];
    }

-(void)wakeUP{

CFRunLoopSourceSignal(source);

CFRunLoopWakeUp(CFRunLoopGetCurrent());

}

停止RunLoop
  • (IBAction)stopRunLoop:(UIButton *)sender {

    [self performSelector:@selector(stop)
    onThread:_thread
    withObject:nil
    waitUntilDone:NO];
    _thread = nil;
    NSLog(@"thread should have stopped");
    }

-(void)stop{
CFRunLoopStop(CFRunLoopGetCurrent());
}

運(yùn)行app 在你點(diǎn)擊```Create```按鈕后將打印```create RunLoop```
點(diǎn)擊```wakeUp```的時候?qū)⒋蛴``do something```
點(diǎn)擊```stop```的時候打印
```thread should have stopped
RunLoop has stopped```
**小結(jié):**通過以上箫荡,我們已經(jīng)能夠創(chuàng)建一個常駐的線程,自定義監(jiān)控事件來喚醒它做一些事情 渔隶。接下來就讓各位自己開腦洞吧
####RunLoop Timer和Observer監(jiān)聽的使用
在上面我們使用了source,接下來我們來使用timer,同時我們使用Observer監(jiān)聽來監(jiān)視當(dāng)前RunLoop的狀態(tài)羔挡,這樣我們對于整個RunLoop就有大概的使用方法了。**【Source/Timer/Observer】**
我們創(chuàng)建一個JVTimerWithObserverViewController 如下

![TimerWithObserver.png](http://upload-images.jianshu.io/upload_images/2972133-971efd4490481dfd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
ViewController持有對象

@interface JVTimerWithObserverViewController (){

NSThread *_thread;
CFRunLoopTimerRef timer;

}

添加Timer和監(jiān)聽的點(diǎn)擊事件 代碼和注釋
  • (IBAction)start:(UIButton )sender {
    // 開啟次線程
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
    // 持有線程 為了在此線程執(zhí)行代碼
    _thread = [NSThread currentThread];
    // 創(chuàng)建RunLoop Apple 提供CFRunLoopGetCurrent
    CFRunLoopRef runLoop = CFRunLoopGetCurrent();
    /
    上下文 第二個參數(shù)把自己傳遞過去 在 myCFTimerCallBack 里面的*info參數(shù)可獲取到 /
    CFRunLoopTimerContext context = {0, (__bridge void
    )self, NULL, NULL, NULL};
    // 初始化 timer myCFTimerCallBack為回調(diào)函數(shù)
    timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 1, 1, 0, 0,
    &myCFTimerCallBack, &context);
    // 添加到CommonModes
    CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes);

      /* Run Loop Observer Activities 中文注釋 */
      NSDictionary *keyDict = @{
                                @(kCFRunLoopEntry) : @"即將進(jìn)入Loop",
                                @(kCFRunLoopBeforeTimers) : @"即將處理 Timer",
                                @(kCFRunLoopBeforeSources) : @"即將處理 Source",
                                @(kCFRunLoopBeforeWaiting) : @"即將進(jìn)入休眠",
                                @(kCFRunLoopAfterWaiting) : @"剛從休眠中喚醒",
                                @(kCFRunLoopExit) : @"即將退出Loop",
                                };
      /*我們需要監(jiān)控的狀態(tài) 這里我們監(jiān)控所有狀態(tài)*/
      CFRunLoopActivity flags =
      kCFRunLoopEntry |
      kCFRunLoopBeforeTimers |
      kCFRunLoopBeforeSources |
      kCFRunLoopBeforeWaiting |
      kCFRunLoopAfterWaiting |
      kCFRunLoopExit |
      kCFRunLoopAllActivities;
    

// 為了美觀分成2行
CFRunLoopObserverRef runloopObserver;
/*第一個參數(shù)一般都是kCFAllocatorDefault间唉,
第二個參數(shù)是我們要監(jiān)控的狀態(tài)
第三個參數(shù)是否重復(fù)
第四個優(yōu)先級
第五個是回調(diào)block
*/
runloopObserver = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault,
flags,
YES,
0,
^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
//通過key取出對應(yīng)中文注釋
NSLog(@"RunLoop狀態(tài):%@", keyDict[@(activity)]);
});
CFRunLoopAddObserver(CFRunLoopGetCurrent(), runloopObserver, kCFRunLoopCommonModes);
CFRunLoopRun();

    /*RunLoop退出才會執(zhí)行*/
    CFRelease(timer);
    NSLog(@"thread has stopped");
});

}

回調(diào)函數(shù)

static void myCFTimerCallBack(CFRunLoopTimerRef timer, void *info){
NSLog(@"hello %@",info);
}


停止事件
  • (IBAction)stop:(UIButton *)sender {
    [self performSelector:@selector(_stop)
    onThread:_thread
    withObject:nil
    waitUntilDone:NO];
    _thread = nil;
    }

-(void)_stop{

CFRunLoopRemoveTimer(CFRunLoopGetCurrent(), timer, kCFRunLoopCommonModes);
CFRunLoopStop(CFRunLoopGetCurrent());

}

接下來我們運(yùn)行 ```add Timer With Observer```和```stop```绞灼,將看到如下打印【部分截取】: 

RunLoop狀態(tài):即將進(jìn)入Loop
RunLoop狀態(tài):即將處理 Timer
RunLoop狀態(tài):即將處理 Source
RunLoop狀態(tài):即將進(jìn)入休眠
RunLoop狀態(tài):剛從休眠中喚醒
hello <JVTimerWithObserverViewController: 0x7fc50d42fa40>
RunLoop狀態(tài):即將處理 Timer
RunLoop狀態(tài):即將處理 Source
RunLoop狀態(tài):即將進(jìn)入休眠
RunLoop狀態(tài):剛從休眠中喚醒
hello <JVTimerWithObserverViewController: 0x7fc50d42fa40>
RunLoop狀態(tài):即將處理 Timer
RunLoop狀態(tài):即將處理 Source
RunLoop狀態(tài):即將進(jìn)入休眠
RunLoop狀態(tài):剛從休眠中喚醒
RunLoop狀態(tài):即將處理 Timer
RunLoop狀態(tài):即將處理 Source
RunLoop狀態(tài):即將退出Loop
thread has stopped

基本和[ibireme 大神](http://blog.ibireme.com/)的總結(jié)的一致

![RunLoop_1.png](http://upload-images.jianshu.io/upload_images/2972133-468ad08d022e38cd.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)


##總結(jié):什么時候使用RunLoop
>需要和其他線程通信時**【使用 mach port 】本文沒有給實例**
>需要在子線程中使用timer
>需要線程持續(xù)執(zhí)行某個周期性的任務(wù)

 如AFNetWorking 2.X 保持線程 在比如AsyncDisplayKit 在主線程的 RunLoop 中添加一個 Observer,使用異步繪制提高性能
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末呈野,一起剝皮案震驚了整個濱河市低矮,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌被冒,老刑警劉巖军掂,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異昨悼,居然都是意外死亡蝗锥,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門率触,熙熙樓的掌柜王于貴愁眉苦臉地迎上來终议,“玉大人,你說我怎么就攤上這事⊙ㄕ牛” “怎么了细燎?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長陆馁。 經(jīng)常有香客問我,道長合愈,這世上最難降的妖魔是什么叮贩? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮佛析,結(jié)果婚禮上益老,老公的妹妹穿的比我還像新娘。我一直安慰自己寸莫,他們只是感情好捺萌,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膘茎,像睡著了一般桃纯。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上披坏,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天态坦,我揣著相機(jī)與錄音,去河邊找鬼棒拂。 笑死伞梯,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的帚屉。 我是一名探鬼主播谜诫,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼攻旦!你這毒婦竟也來了喻旷?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤牢屋,失蹤者是張志新(化名)和其女友劉穎掰邢,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體伟阔,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡辣之,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了皱炉。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片怀估。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出多搀,到底是詐尸還是另有隱情歧蕉,我是刑警寧澤,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布康铭,位于F島的核電站惯退,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏从藤。R本人自食惡果不足惜催跪,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望夷野。 院中可真熱鬧懊蒸,春花似錦、人聲如沸悯搔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽妒貌。三九已至通危,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間灌曙,已是汗流浹背黄鳍。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留平匈,地道東北人框沟。 一個月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像增炭,于是被迫代替她去往敵國和親忍燥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

推薦閱讀更多精彩內(nèi)容