配圖html
一直以來RunLoop就是個神祕的領域,好多2.3年的開發者都不能準確的表述它的做用,說它神祕,其實RunLoop並無你們想象中的那麼神祕,那麼很差理解,本文就帶你們好好剖析一下"神祕的RunLoop"面試
循環app
沒有RunLoop框架
有RunLoop函數
主運行循環oop
iOS中有2套API來訪問和使用RunLoop性能
Foundationui
Core Foundationatom
NSRunLoop和CFRunLoopRef都表明着RunLoop對象spa
NSRunLoop是基於CFRunLoopRef的一層OC包裝,因此要了解RunLoop內部結構,須要多研究CFRunLoopRef層面的API(Core Foundation層面)
CFRunLoopRef是開源的
http://opensource.apple.com/source/CF/CF-1151.16/
每條線程都有惟一的一個與之對應的RunLoop對象
主線程的RunLoop已經自動建立好了,子線程的RunLoop須要主動建立
RunLoop在第一次獲取時建立,在線程結束時銷燬
[NSRunLoop currentRunLoop]; // 得到當前線程的RunLoop對象 [NSRunLoop mainRunLoop]; // 得到主線程的RunLoop對象
CFRunLoopGetCurrent(); // 得到當前線程的RunLoop對象 CFRunLoopGetMain(); // 得到主線程的RunLoop對象
注:RunLoop若是沒有這些東西 會直接退出
CFRunLoopModeRef表明RunLoop的運行模式
一個 RunLoop 包含若干個 Mode,每一個Mode又包含若干個Source/Timer/Observer
每次RunLoop啓動時,只能指定其中一個 Mode,這個Mode被稱做 CurrentMode
若是須要切換Mode,只能退出Loop,再從新指定一個Mode進入
這樣作主要是爲了分隔開不一樣組的Source/Timer/Observer,讓其互不影響
相關類
kCFRunLoopDefaultMode:App的默認Mode,一般主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤 Mode,用於 ScrollView 追蹤觸摸滑動,保證界面滑動時不受其餘 Mode 影響
UIInitializationRunLoopMode: 在剛啓動 App 時第進入的第一個 Mode,啓動完成後就再也不使用
GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,一般用不到
kCFRunLoopCommonModes: 這是一個佔位用的Mode,不是一種真正的Mode
CFRunLoopSourceRef是事件源(輸入源)
按照官方文檔的分類
按照函數調用棧的分類
Source0: event事件,只含有回調,須要先調用CFRunLoopSourceSignal(source),將這個 Source 標記爲待處理,而後手動調用 CFRunLoopWakeUp(runloop) 來喚醒 RunLoop。
Source1: 包含了一個 mach_port 和一個回調,被用於經過內核和其餘線程相互發送消息,能主動喚醒 RunLoop 的線程。
函數調用棧
CFRunLoopTimerRef是基於時間的觸發器
基本上說的就是NSTimer(CADisplayLink也是加到RunLoop),它受RunLoop的Mode影響
GCD的定時器不受RunLoop的Mode影響
CFRunLoopObserverRef是觀察者,可以監聽RunLoop的狀態改變
能夠監聽的時間點有如下幾個
可監聽狀態
- (void)observer { // 建立observer CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { NSLog(@"----監聽到RunLoop狀態發生改變---%zd", activity); }); // 添加觀察者:監聽RunLoop的狀態 CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode); // 釋放Observer CFRelease(observer); }
/* CF的內存管理(Core Foundation) 1.凡是帶有Create、Copy、Retain等字眼的函數,建立出來的對象,都須要在最後作一次release * 好比CFRunLoopObserverCreate 2.release函數:CFRelease(對象); */
官方版
邏輯
網友版
注:進入RunLoop前 會判斷模式是否爲空,爲空直接退出
- (void)timer { NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; // 定時器只運行在NSDefaultRunLoopMode下,一旦RunLoop進入其餘模式,這個定時器就不會工做 // [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; // 定時器只運行在UITrackingRunLoopMode下,一旦RunLoop進入其餘模式,這個定時器就不會工做 // [[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode]; // 定時器會跑在標記爲common modes的模式下 // 標記爲common modes的模式:UITrackingRunLoopMode和NSDefaultRunLoopMode兼容 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; }
- (void)timer2 { // 調用了scheduledTimer返回的定時器,已經自動被添加到當前runLoop中,並且是NSDefaultRunLoopMode NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; // 修改模式 [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes]; }
此時以下圖: NSTimer 再也不響應 圖片中止輪播
NSDefaultRunLoopMode模式
此時以下圖: NSTimer 在兩個模式下均可正常運行
new.gif
// 只在NSDefaultRunLoopMode模式下顯示圖片 [self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"placeholder"] afterDelay:3.0 inModes:@[NSDefaultRunLoopMode]];
PerformSelector
inModes:設置運行模式
- (void)run { //addPort:添加端口(就是source) forMode:設置模式 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; //啓動RunLoop [[NSRunLoop currentRunLoop] run]; /* //另外兩種啓動方式 [NSDate distantFuture]:遙遠的將來 這種寫法跟上面的run是一個意思 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; 不設置模式 [[NSRunLoop currentRunLoop] runUntilDate:[NSDate distantFuture]]; */ }
[NSThread exit];
- (void)run { while (1) { [[NSRunLoop currentRunLoop] run]; } }
在休眠前(kCFRunLoopBeforeWaiting)進行釋放,處理事件前建立釋放池,中間建立的對象會放入釋放池
在啓動RunLoop以前建議用 @autoreleasepool {...}包裹
意義:建立一個大釋放池,釋放{}期間建立的臨時對象,通常好的框架的做者都會這麼作
- (void)execute { @autoreleasepool { NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES]; [[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; } }
之後爲了增長用戶體驗 在用戶UI交互的時候 不作事件處理 咱們能夠把須要作的操做放到NSDefaultRunLoopMode
通常的NSTimer定時器由於受到RunLoop,會存在時間不許時的狀況.
上文有提到GCD不受RunLoop影響,下面簡單的說一下它的使用
/** 定時器(這裏不用帶*,由於dispatch_source_t就是個類,內部已經包含了*) */ @property (nonatomic, strong) dispatch_source_t timer; int count = 0; - (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { // 得到隊列 // dispatch_queue_t queue = dispatch_get_global_queue(0, 0); dispatch_queue_t queue = dispatch_get_main_queue(); // 建立一個定時器(dispatch_source_t本質仍是個OC對象) self.timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue); // 設置定時器的各類屬性(幾時開始任務,每隔多長時間執行一次) // GCD的時間參數,通常是納秒 NSEC_PER_SEC(1秒 == 10的9次方納秒) // 什麼時候開始執行第一個任務 // dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC) 比當前時間晚3秒 dispatch_time_t start = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1.0 * NSEC_PER_SEC)); uint64_t interval = (uint64_t)(1.0 * NSEC_PER_SEC); dispatch_source_set_timer(self.timer, start, interval, 0); // 設置回調 dispatch_source_set_event_handler(self.timer, ^{ NSLog(@"------------%@", [NSThread currentThread]); count++; // if (count == 4) { // // 取消定時器 // dispatch_cancel(self.timer); // self.timer = nil; // } }); // 啓動定時器 dispatch_resume(self.timer); }
常常會有喜歡裝B的面試官,面試的時候就喜歡問RunLoop,其實他真的會嗎? 說不定他本身都不太理解
下面我對有關RunLoop的面試作一個簡單的總結,也算是對全文一個總結
什麼是RunLoop?
在開發中如何使用RunLoop?什麼應用場景?
能夠控制定時器在特定模式下執行
可讓某些事件(行爲、任務)在特定模式下執行
能夠添加Observer監聽RunLoop的狀態,好比監聽點擊事件的處理(在全部點擊事件以前作一些事情)