CFRunLoop 從CF層面了解由于CFRunLoopMode機制iOS程序ScrollView的滑動為何如此平滑的原因

簡介

簡單的說run loop是事件驅(qū)動的一個大循環(huán)猾蒂,如下代碼所示

int main(int argc, char * argv[]) {
     //程序一直運行狀態(tài)
     while (AppIsRunning) {
          //睡眠狀態(tài)炕倘,等待喚醒事件
          id whoWakesMe = SleepForWakingUp();
          //得到喚醒事件
          id event = GetEvent(whoWakesMe);
          //開始處理事件
          HandleEvent(event);
     }
     return 0;
}

Cocoa會涉及到Run Loops的

  • 系統(tǒng)級:GCD斋陪,mach kernel侠草,block辱挥,pthread
  • 應(yīng)用層:NSTimer,UIEvent边涕,Autorelease晤碘,NSObject(NSDelayedPerforming),NSObject(NSThreadPerformAddition)功蜓,CADisplayLink园爷,CATransition,CAAnimation式撼,dispatch_get_main_queue()(GCD中dispatch到main queue的block會被dispatch到main RunLoop執(zhí)行)童社,NSPort,NSURLConnection著隆,AFNetworking(這個第三方網(wǎng)絡(luò)請求框架使用在開啟新線程中添加自己的run loop監(jiān)聽事件)

在Main thread堆棧中所處位置

堆棧最底層是start(dyld)扰楼,往上依次是main,UIApplication(main.m) -> GSEventRunModal(Graphic Services) -> RunLoop(包含CFRunLoopRunSpecific美浦,__CFRunLoopRun弦赖,__CFRunLoopDoSouces0,CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE0_PERFORM_FUNCTION) -> Handle Touch Event

RunLoop原理

CFRunLoop開源代碼:http://opensource.apple.com/source/CF/CF-855.17/

執(zhí)行順序的偽代碼

SetupThisRunLoopRunTimeoutTimer(); // by GCD timer
do {
     __CFRunLoopDoObservers(kCFRunLoopBeforeTimers);
     __CFRunLoopDoObservers(kCFRunLoopBeforeSources);

     __CFRunLoopDoBlocks();
     __CFRunLoopDoSource0();

     CheckIfExistMessagesInMainDispatchQueue(); // GCD

     __CFRunLoopDoObservers(kCFRunLoopBeforeWaiting);
     var wakeUpPort = SleepAndWaitForWakingUpPorts();
     // mach_msg_trap
     // Zzz...
     // Received mach_msg, wake up
     __CFRunLoopDoObservers(kCFRunLoopAfterWaiting);
     // Handle msgs
     if (wakeUpPort == timerPort) {
          __CFRunLoopDoTimers();
     } else if (wakeUpPort == mainDispatchQueuePort) {
          // GCD
          __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__()
     } else {
          __CFRunLoopDoSource1();
     }
     __CFRunLoopDoBlocks();
} while (!stop && !timeout);

構(gòu)成

Thread包含一個CFRunLoop浦辨,一個CFRunLoop包含一種CFRunLoopMode蹬竖,mode包含CFRunLoopSource,CFRunLoopTimer和CFRunLoopObserver荤牍。

CFRunLoopMode

RunLoop只能運行在一種mode下案腺,如果要換mode當前的loop也需要停下重啟成新的。利用這個機制康吵,ScrollView過程中NSDefaultRunLoopMode的mode會切換UITrackingRunLoopMode來保證ScrollView的流暢滑動不受只能在NSDefaultRunLoopMode時處理的事件影響滑動劈榨。同時mode還是可定制的。

  • NSDefaultRunLoopMode:默認晦嵌,空閑狀態(tài)
  • UITrackingRunLoopMode:ScrollView滑動時
  • UIInitializationRunLoopMode:啟動時
  • NSRunLoopCommonModes:Mode集合
    Timer計時會被scrollView的滑動影響的問題可以通過將timer添加到NSRunLoopCommonModes來解決
//將timer添加到NSDefaultRunLoopMode中
[NSTimer scheduledTimerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
//然后再添加到NSRunLoopCommonModes里
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0
     target:self
     selector:@selector(timerTick:)
     userInfo:nil
     repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];

CFRunLoopTimer

NSTimer是對RunLoopTimer的封裝

+ (NSTimer *)timerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)ti invocation:(NSInvocation *)invocation repeats:(BOOL)yesOrNo;

- (void)performSelector:(SEL)aSelector withObject:(id)anArgument afterDelay:(NSTimeInterval)delay inModes:(NSArray *)modes;

+ (CADisplayLink *)displayLinkWithTarget:(id)target selector:(SEL)sel;
- (void)addToRunLoop:(NSRunLoop *)runloop forMode:(NSString *)mode;

CFRunLoopSource

  • source0:處理如UIEvent同辣,CFSocket這樣的事件
  • source1:Mach port驅(qū)動拷姿,CFMachport,CFMessagePort

CFRunLoopObserver

Cocoa框架中很多機制比如CAAnimation等都是由RunLoopObserver觸發(fā)的旱函。observer到當前狀態(tài)的變化進行通知响巢。

typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
     kCFRunLoopEntry = (1UL << 0),
     kCFRunLoopBeforeTimers = (1UL << 1),
     kCFRunLoopBeforeSources = (1UL << 2),
     kCFRunLoopBeforeWaiting = (1UL << 5),
     kCFRunLoopAfterWaiting = (1UL << 6),
     kCFRunLoopExit = (1UL << 7),
     kCFRunLoopAllActivities = 0x0FFFFFFFU
};

使用RunLoop的案例

AFNetworking

使用NSOperation+NSURLConnection并發(fā)模型都會面臨NSURLConnection下載完成前線程退出導(dǎo)致NSOperation對象接收不到回調(diào)的問題。AFNetWorking解決這個問題的方法是按照官方的guidhttps://developer.apple.com/library/mac/documentation/Cocoa/Reference/Foundation/Classes/NSURLConnection_Class/Reference/Reference.html#//apple_ref/occ/instm/NSURLConnection/initWithRequest:delegate:startImmediately:上寫的NSURLConnection的delegate方法需要在connection發(fā)起的線程runloop中調(diào)用棒妨,于是AFNetWorking直接借鑒了Apple自己的一個Demohttps://developer.apple.com/LIBRARY/IOS/samplecode/MVCNetworking/Introduction/Intro.html的實現(xiàn)方法單獨起一個global thread踪古,內(nèi)置一個runloop,所有的connection都由這個runloop發(fā)起券腔,回調(diào)也是它接收伏穆,不占用主線程,也不耗CPU資源纷纫。

+ (void)networkRequestThreadEntryPoint:(id)__unused object {
     @autoreleasepool {
          [[NSThread currentThread] setName:@"AFNetworking"];

          NSRunLoop *runLoop = [NSRunLoop currentRunLoop];
          [runLoop addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
          [runLoop run];
     }
}

+ (NSThread *)networkRequestThread {
     static NSThread *_networkRequestThread = nil;
     static dispatch_once_t oncePredicate;
     dispatch_once(&oncePredicate, ^{
          _networkRequestThread =
          [[NSThread alloc] initWithTarget:self
               selector:@selector(networkRequestThreadEntryPoint:)
               object:nil];
          [_networkRequestThread start];
     });

     return _networkRequestThread;
}

類似的可以用這個方法創(chuàng)建一個常駐服務(wù)的線程枕扫。

TableView中實現(xiàn)平滑滾動延遲加載圖片

利用CFRunLoopMode的特性,可以將圖片的加載放到NSDefaultRunLoopMode的mode里辱魁,這樣在滾動UITrackingRunLoopMode這個mode時不會被加載而影響到烟瞧。

UIImage *downloadedImage = ...;
[self.avatarImageView performSelector:@selector(setImage:)
     withObject:downloadedImage
     afterDelay:0
     inModes:@[NSDefaultRunLoopMode]];

接到程序崩潰時的信號進行自主處理例如彈出提示等

CFRunLoopRef runLoop = CFRunLoopGetCurrent();
NSArray *allModes = CFBridgingRelease(CFRunLoopCopyAllModes(runLoop));
while (1) {
     for (NSString *mode in allModes) {
          CFRunLoopRunInMode((CFStringRef)mode, 0.001, false);
     }
}

異步測試

- (BOOL)runUntilBlock:(BOOL(^)())block timeout:(NSTimeInterval)timeout
{
     __block Boolean fulfilled = NO;
     void (^beforeWaiting) (CFRunLoopObserverRef observer, CFRunLoopActivity activity) =
     ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
          fulfilled = block();
          if (fulfilled) {
               CFRunLoopStop(CFRunLoopGetCurrent());
          }
     };

     CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(NULL, kCFRunLoopBeforeWaiting, true, 0, beforeWaiting);
     CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);

     // Run!
     CFRunLoopRunInMode(kCFRunLoopDefaultMode, timeout, false);

     CFRunLoopRemoveObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
     CFRelease(observer);

     return fulfilled;
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市染簇,隨后出現(xiàn)的幾起案子参滴,更是在濱河造成了極大的恐慌,老刑警劉巖锻弓,帶你破解...
    沈念sama閱讀 212,383評論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件卵洗,死亡現(xiàn)場離奇詭異,居然都是意外死亡弥咪,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,522評論 3 385
  • 文/潘曉璐 我一進店門十绑,熙熙樓的掌柜王于貴愁眉苦臉地迎上來聚至,“玉大人,你說我怎么就攤上這事本橙“夤” “怎么了?”我有些...
    開封第一講書人閱讀 157,852評論 0 348
  • 文/不壞的土叔 我叫張陵甚亭,是天一觀的道長贷币。 經(jīng)常有香客問我,道長亏狰,這世上最難降的妖魔是什么役纹? 我笑而不...
    開封第一講書人閱讀 56,621評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮暇唾,結(jié)果婚禮上促脉,老公的妹妹穿的比我還像新娘辰斋。我一直安慰自己,他們只是感情好瘸味,可當我...
    茶點故事閱讀 65,741評論 6 386
  • 文/花漫 我一把揭開白布宫仗。 她就那樣靜靜地躺著,像睡著了一般旁仿。 火紅的嫁衣襯著肌膚如雪藕夫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,929評論 1 290
  • 那天枯冈,我揣著相機與錄音毅贮,去河邊找鬼。 笑死霜幼,一個胖子當著我的面吹牛嫩码,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播罪既,決...
    沈念sama閱讀 39,076評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼铸题,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了琢感?” 一聲冷哼從身側(cè)響起丢间,我...
    開封第一講書人閱讀 37,803評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎驹针,沒想到半個月后烘挫,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,265評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡柬甥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,582評論 2 327
  • 正文 我和宋清朗相戀三年饮六,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片苛蒲。...
    茶點故事閱讀 38,716評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡卤橄,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出臂外,到底是詐尸還是另有隱情窟扑,我是刑警寧澤,帶...
    沈念sama閱讀 34,395評論 4 333
  • 正文 年R本政府宣布漏健,位于F島的核電站嚎货,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏蔫浆。R本人自食惡果不足惜殖属,卻給世界環(huán)境...
    茶點故事閱讀 40,039評論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望克懊。 院中可真熱鬧忱辅,春花似錦七蜘、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,798評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至损搬,卻和暖如春碧库,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背巧勤。 一陣腳步聲響...
    開封第一講書人閱讀 32,027評論 1 266
  • 我被黑心中介騙來泰國打工嵌灰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人颅悉。 一個月前我還...
    沈念sama閱讀 46,488評論 2 361
  • 正文 我出身青樓沽瞭,卻偏偏與公主長得像,于是被迫代替她去往敵國和親剩瓶。 傳聞我的和親對象是個殘疾皇子驹溃,可洞房花燭夜當晚...
    茶點故事閱讀 43,612評論 2 350

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

  • Runloop是iOS和OSX開發(fā)中非常基礎(chǔ)的一個概念延曙,從概念開始學(xué)習(xí)豌鹤。 RunLoop的概念 -般說,一個線程一...
    小貓仔閱讀 985評論 0 1
  • 原文地址:http://blog.ibireme.com/2015/05/18/runloop/ RunLoop ...
    大餅炒雞蛋閱讀 1,152評論 0 6
  • 深入理解RunLoop 由ibireme| 2015-05-18 |iOS,技術(shù) RunLoop 是 iOS 和 ...
    橙娃閱讀 849評論 1 2
  • 轉(zhuǎn)自http://blog.ibireme.com/2015/05/18/runloop 深入理解RunLoop ...
    飄金閱讀 976評論 0 4
  • RunLoop 是 iOS 和 OS X 開發(fā)中非持Φ蓿基礎(chǔ)的一個概念布疙,這篇文章將從 CFRunLoop 的源碼入手,...
    iOS_Alex閱讀 900評論 0 10