RunLoop學(xué)習(xí)與總結(jié)

  • RunLoop

    • 一個運行循環(huán)
    • 保持程序的持續(xù)運行
    • 監(jiān)聽處理 APP 各種事件(觸摸,定時器,selector)
    • 節(jié)省 CPU 資源,提高程序性能(有事做的時候做事,沒事做就休息)
  • main函數(shù)中的RunLoop

int main(int argc, char * argv[]) {
    @autoreleasepool {
        int a =  UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
        NSLog(@"哈哈哈哈");
        return a;
    }
}

如果我么在 main.m 文件中添加一句輸出
運行以后"哈哈哈哈"是不會打印的
因為系統(tǒng)默認(rèn)在 UIApplicationMain 函數(shù)中為主線程開啟了 RunLoop
RunLoop 一直在運行處理各種事件或者等待事件的到來
UIApplicationMain 就不會返回

  • RunLoop結(jié)構(gòu):

RunLoop

iOS中有2套API來訪問和使用 RunLoop
分別是:
Foundation 框架中的 NSRunLoop
Core Foundation 框架中的 CFRunLoopRef
NSRunLoop和CFRunLoopRef 都代表著 RunLoop對象
NSRunLoop 是基于 CFRunLoopRef的一層 OC 包裝
所以要了解 RunLoop 內(nèi)部結(jié)構(gòu)
需要多研究 CFRunLoopRef 層面的 API(Core Foundation 層面)

  • RunLoop中的Mode

kCFRunLoopDefaultMode:App的默認(rèn) Mode,通常主線程是在這個 Mode 下運行

UITrackingRunLoopMode:界面跟蹤 Mode,用于 ScrollView 追蹤觸摸滑動,當(dāng)界面有scrollView滾動, 切入到該模式, 保證界面滑動時不受其他 Mode 影響, 

UIInitializationRunLoopMode: 在剛啟動 App 時第進(jìn)入的第一個 Mode,啟動完成后就不再使用

GSEventReceiveRunLoopMode: 接受系統(tǒng)事件的內(nèi)部 Mode个初,通常用不到

kCFRunLoopCommonModes: 這是一個占位用的Mode,不是一種真正的Mode

上代碼 :
創(chuàng)建項目, 在控制器中添加定時器:

- (void)viewDidLoad {
    [super viewDidLoad];
  NSTimer *time = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
  [[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode];
}

- (void)run{
    NSLog(@"run");
}

運行程序后
run是正常打印的
可是當(dāng)我們在控制器的 view 添加一個TextView再運行


添加TextView

run 還是正常打印
一旦開始滾動TextView
run 就停止打印了
因為這時候定時器被暫停了
為什么呢?
我們來打印一下 TextView 在滾動和不滾動兩種狀態(tài)下的RunLoopMode:

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"一開始我的模式是: %@",[[NSRunLoop currentRunLoop] currentMode]);
}
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
    NSLog(@"當(dāng)我開始滾動時的模式: %@",[[NSRunLoop currentRunLoop] currentMode]);
}

打印結(jié)果如下:


屏幕快照 2017-11-05 下午2.19.53.png

打印中可以看出
TextView 一開始滾動
RunLoop 的 Mode 就由 kCFRunLoopDefaultMode 切換到 UITrackingRunLoopMode
由于定時器是添加到 NSDefaultRunLoopMode
所以 TextView 滾動進(jìn)入 UITrackingRunLoopMode 模式后
定時器就停止了

如果我們想在scrollView滾動的時候做一些事情
可以使用這中方法

如果我們想讓定時器在兩種模式下都工作
就要讓定時器加入到 kCFRunLoopCommonModes 模式下
kCFRunLoopCommonModes 模式不是一個具體的模式
他只是一個標(biāo)記
加入該模式后, 會運行在所有標(biāo)記為 kCFRunLoopCommonModes 模式的模式下


Jietu20171105-141117.png

打印一下 RunLoo 可以看出
標(biāo)記為 kCFRunLoopCommonModes 的模式分別是
kCFRunLoopDefaultMode 和 UITrackingRunLoopMode
就是默認(rèn)模式和滾動模式


  • RunLoop中的Source

CFRunLoopSourceRef是事件源(輸入源)

根據(jù)蘋果文檔的分類 :
Port-Based Sources : 來自內(nèi)核或者其他線程的一些事件
Custom Input Sources : 自定義輸入源
Cocoa Perform Selector Sources : 處理performSelector方法中的事件(觸摸 , 點擊等等)

根據(jù)函數(shù)調(diào)用棧的分類 :
Source0:非基于Port的
Source1:基于Port的, 接收內(nèi)核或者其他線程發(fā)過來的事件, 分發(fā)給Source0處理


  • RunLoop中的Observer

CFRunLoopObserverRef 是觀察者
能夠監(jiān)聽 RunLoop 的狀態(tài)改變
可以監(jiān)聽的時間點有一下幾點:

/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
    // 即將進(jìn)入 RunLoop
    kCFRunLoopEntry = (1UL << 0), 

    //即將處理 Timer
    kCFRunLoopBeforeTimers = (1UL << 1),

    //即將處理Source
    kCFRunLoopBeforeSources = (1UL << 2),

    //即將進(jìn)入休眠
    kCFRunLoopBeforeWaiting = (1UL << 5),

    //即將被喚醒
    kCFRunLoopAfterWaiting = (1UL << 6),

    //即將退出
    kCFRunLoopExit = (1UL << 7),

    // 所有都監(jiān)聽
    kCFRunLoopAllActivities = 0x0FFFFFFFU 
};

上代碼 :

    // 創(chuàng)建observer
    CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
        NSLog(@"----監(jiān)聽到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
    });
    
    // 添加觀察者:監(jiān)聽RunLoop的狀態(tài)
    CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

最后需要自己釋放:

    CFRelease(observer);

  • RunLoop運轉(zhuǎn)流程 :

運轉(zhuǎn)流程

首先,進(jìn)入 RunLoop (順便通知Observer我要進(jìn)入了)
然后就去處理 Timer (順便通知 Observer 我要處理 Timer)
然后處理 Source (順便通知 Observer 我要處理 Source)
然后判斷 Source1 還有沒有沒有分發(fā)的任務(wù)?
--- 有的話就去處理 Source1 收到的任務(wù)包括 Timer 和Source
--- 沒有的話, 就要去睡覺了
任務(wù)都處理完了,進(jìn)入休眠(順便通知 Observer 我要休眠)
休眠的時候如果有新的任務(wù)進(jìn)入, RunLoop被喚醒....


  • RunLoop實踐 :

  • 1:tableView滾動的時候不進(jìn)行耗時事件 :

例如進(jìn)行大圖顯示的時候,可能系統(tǒng)會渲染事件過長, 如果這時候用戶正在拖動tableView,會造成卡頓
可以利用RunLoop來處理
只在 NSDefaultRunLoopMode 模式下顯示圖片
tableView滾動的時候是 UITrackingRunLoopMode 模式
不會產(chǎn)生沖突

    [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
  • 2:常駐線程 :

子線程默認(rèn)情況下, 在任務(wù)執(zhí)行結(jié)束后, 線程就會死掉
如果想開啟新的任務(wù), 就要重新創(chuàng)建線程
如果想經(jīng)常在子線程處理一些耗時操作
頻繁的創(chuàng)建線程是不可取的
那么就需要一條不會死掉的常駐線程
上代碼 :


- (void)viewDidLoad {
    [super viewDidLoad];
    //保住線程的命
    self.thread = [[XMGThread alloc] initWithTarget:self selector:@selector(run) object:nil];
    [self.thread start];
}
//在子線程讓Runloop跑起來 保住線程的命
- (void)run
{
    [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
    NSLog(@"------------");
}

在子線程中的 RunLoop 跑起來以后
RunLoop 會一直監(jiān)聽子線程的事件
"------------" 這句是不會打印的
因為 RunLoop 中的do-while循環(huán)一直在運行
需要注意的是 : 線程一定要加一些東西, 例如Port
Port 就是 RunLoop 中的 Source
有了 Source 以后
RunLoop就會一直等待 Source 給他事件
如果不添加的話
RunLoop 中沒有 Source 也沒有 Timer
RunLoop就會自動退出


  • 3:子線程添加定時器 :

上代碼 :

- (void)viewDidLoad {
    [super viewDidLoad];
    //子線程添加定時器
    self.thread = [[XMGThread alloc] initWithTarget:self selector:@selector(execute2) object:nil];
    [self.thread start];
}
- (void)execute2
{
    NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
    [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
    [[NSRunLoop currentRunLoop] run];
}

和第二種實踐方法一樣
在子線程中啟動 RunLoop
因為 RunLoop 中已經(jīng)有了 Timer
所有 RunLoop 不會死掉
這樣線程不會死, 定時器也正常運行

  • 4:自動釋放池 :

作用 :
將一些對象扔到池子中去
當(dāng)池子釋放的時候
讓池中所有對象調(diào)用一次 release 方法

自動釋放池什么時候死 :
在 RunLoop 睡覺之前死
因為睡覺可能睡很久
如果不讓自動釋放池死掉
會占用很多內(nèi)存
當(dāng) RunLoop 被再次喚醒的時候
剛剛所有調(diào)用 release 的對象又會放到釋放池中

在子線程開 RunLoop 的時候
可以添加釋放池 :

//在子線程添加定時器
- (void)execute
{
    @autoreleasepool {
        [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
        [[NSRunLoop currentRunLoop] run];
    }
}

還有一個問題需要記錄一下
就是當(dāng)舊的釋放池在銷毀以后,新的釋放池什么時候創(chuàng)建呢?
我們可以打印一下主線程 RunLoop 看釋放池都監(jiān)聽了 RunLoop 那些狀態(tài)

- (void)viewDidLoad {
    [super viewDidLoad];
    NSLog(@"%@",[NSRunLoop currentRunLoop]);
}

找出相關(guān) autoreleasepool 的部分 :

    observers = (
    "<CFRunLoopObserver 0x600000134320 [0x10e23a9b0]>{valid = Yes, activities = 0x1, repeats = Yes, order = -2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e3e6ede), context = <CFArray 0x6000004558d0 [0x10e23a9b0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f8a7c802048>\n)}}",

    "<CFRunLoopObserver 0x6000001343c0 [0x10e23a9b0]>{valid = Yes, activities = 0xa0, repeats = Yes, order = 2147483647, callout = _wrapRunLoopWithAutoreleasePoolHandler (0x10e3e6ede), context = <CFArray 0x6000004558d0 [0x10e23a9b0]>{type = mutable-small, count = 1, values = (\n\t0 : <0x7f8a7c802048>\n)}}"
),

autoreleasepool 利用 observers 監(jiān)聽了以下狀態(tài):
activities = 0x1 (相當(dāng)于十進(jìn)制的 1 )
activities = 0xa0 (相當(dāng)于十進(jìn)制的 160)
這是 C 語言的位移枚舉 (位移枚舉的解釋)
這兩個 activities 對應(yīng) RunLoop 以下三個狀態(tài):
kCFRunLoopEntry = (1UL << 0), //即將進(jìn)入
kCFRunLoopBeforeWaiting = (1UL << 5), //即將休眠
kCFRunLoopExit = (1UL << 7), //即將推出
可以看出 autoreeaseepool 利用 observers 監(jiān)聽了RunLoop三個狀態(tài)
所以工作流程應(yīng)該是這樣 :
剛進(jìn)入 RunLoop 的時候創(chuàng)建釋放池
監(jiān)聽到即將進(jìn)入休眠銷毀釋放池釋放變量,并創(chuàng)建新的釋放池以供下次被喚醒時使用
因為 autoreleasepool 沒有監(jiān)聽 RunLoop 即將喚醒的狀態(tài)
所以在休眠之前創(chuàng)建好
但是如果 RunLoop 不被喚醒了 , 最后一次創(chuàng)建的釋放池就不會被銷毀
所以監(jiān)聽 kCFRunLoopExit 狀態(tài)
在最后退出的時候銷毀最后一次創(chuàng)建的釋放吃


感謝閱讀
你的支持是我寫作的唯一動力

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市铸屉,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌切端,老刑警劉巖彻坛,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡昌屉,警方通過查閱死者的電腦和手機(jī)钙蒙,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怠益,“玉大人仪搔,你說我怎么就攤上這事◎呃危” “怎么了烤咧?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長抢呆。 經(jīng)常有香客問我煮嫌,道長,這世上最難降的妖魔是什么抱虐? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任昌阿,我火速辦了婚禮,結(jié)果婚禮上恳邀,老公的妹妹穿的比我還像新娘懦冰。我一直安慰自己,他們只是感情好谣沸,可當(dāng)我...
    茶點故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布刷钢。 她就那樣靜靜地躺著,像睡著了一般乳附。 火紅的嫁衣襯著肌膚如雪内地。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天赋除,我揣著相機(jī)與錄音阱缓,去河邊找鬼。 笑死举农,一個胖子當(dāng)著我的面吹牛荆针,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播颁糟,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼祭犯,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了滚停?” 一聲冷哼從身側(cè)響起沃粗,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎键畴,沒想到半個月后最盅,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體突雪,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年涡贱,在試婚紗的時候發(fā)現(xiàn)自己被綠了咏删。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡问词,死狀恐怖督函,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情激挪,我是刑警寧澤辰狡,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站垄分,受9級特大地震影響宛篇,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜薄湿,卻給世界環(huán)境...
    茶點故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一叫倍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧豺瘤,春花似錦吆倦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至瞻赶,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間派任,已是汗流浹背砸逊。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留掌逛,地道東北人师逸。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像豆混,于是被迫代替她去往敵國和親篓像。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,901評論 2 355

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

  • 最近在學(xué)習(xí)一些OC底層的東西, 下面是學(xué)習(xí)了RunLoop的一些總結(jié)和感受^^ 首先,RunLoop的作用 從字面...
    kikido閱讀 458評論 0 1
  • 1皿伺,runloop 的概念是什么员辩? 一般來講,一個線程一次只能執(zhí)行一個任務(wù)鸵鸥,執(zhí)行完成后線程就會退出奠滑。如果我們需要一...
    攜YOU手同行閱讀 191評論 0 0
  • 上一節(jié)主要講了RunLoop的理論的基礎(chǔ)知識, 這一節(jié)講一講實踐:修正一點: 根據(jù)源碼,runloop要跑起來先判...
    東方_未明閱讀 810評論 1 5
  • 在一般情況下丹皱,一個線程在執(zhí)行完了一個任務(wù)后就會自動退出。我們想要有這樣一個機(jī)制宋税,讓線程隨時可以處理事件但是不退出摊崭。...
    雪山飛狐_91ae閱讀 1,027評論 0 8
  • 什么是Runloop · 一般來講嗅榕,一個線程一次只能執(zhí)行一個任務(wù)冈闭,執(zhí)行完成后線程就會退出。如果我們需要一個機(jī)制漓概,讓...
    806349745123閱讀 301評論 0 1