RunLoop
NSRunLoop是IOS消息機制的處理模式
主要作用
一條線程對應一個RunLoop,主線程的RunLoop默認已經創建好了, 而子線程的需要我們自己手動創建
獲取主線程對應的RunLoop對象mainRunLoop/CFRunLoopGetMain
獲取當前線程對應的RunLoop對象currentRunLoop/CFRunLoopGetCurrent
RunLoop會一直循環檢測,從線程start到線程end,檢測檢測到事件源(CFRunLoopSourceRef)執行處理函數,首先會產生通知,corefunction向線程添加runloopObservers來監聽事件,并控制NSRunLoop里面線程的執行和休眠,在有事情做的時候使當前NSRunLoop控制的線程工作,沒有事情做讓當前NSRunLoop的控制的線程休眠。
RunLoop的運行模式(CFRunLoopModeRef)
一個 RunLoop包含若干個Mode,每個Mode又包含若干個Source/Timer/Observer
每次RunLoop啟動時,只能指定其中一個Mode,這個Mode被稱作 CurrentMode
如果需要切換Mode,只能退出Loop,再重新指定一個Mode進入
系統默認模式
NSDefaultRunLoopMode:App的默認Mode,通常主線程是在這個Mode下運行
UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView 追蹤觸摸滑動,保證界面滑動時不受其他 Mode 影響
UIInitializationRunLoopMode:在剛啟動App時第進入的第一個 Mode,啟動完成后就不再使用
GSEventReceiveRunLoopMode: 接受系統事件的內部 Mode,通常用不到
NSRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode
RunLoop結構圖
RunLoop結構圖
CFRunLoopTimerRef-基于時間的觸發器
CFRunLoopTimerRef基本上說的就是NSTimer
它受RunLoop的Mode影響
// 新建NSTimer對象
NSTimer *timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 將NSTimer添加到RunLoop中,并且告訴系統,當前Tiemr只有在RunLoop的默認模式下才有效
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
// 所以在UITrackingRunLoopMode模式下,定時器的方法不會執行,但定時器仍計時
NSTimer
將NSTimer添加到RunLoop中,并且告訴系統,當前Tiemr只有在Tracking的默認模式下才有效
[[NSRunLoop currentRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
將NSTimer添加到RunLoop中,并且告訴系統,在所有被"標記"common的模式都可以運行,UITrackingRunLoopMode和kCFRunLoopDefaultMode都被標記為了common模式,所以只需要將timer的模式設置為forMode:NSRunLoopCommonModes,就可以在默認模式和追蹤模式都能夠運行
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
如果是通過scheduledTimerWithTimeInterval創建的NSTimer, 默認就會添加到RunLoop得DefaultMode中 , 所以它會自動運行
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
// 雖然默認已經添加到DefaultMode中,但是我們也可以自己修改它的模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
GCD定時器
不受RunLoop的Mode影響
// 1.創建一個定時器
// 獲取一個全局并發隊列
dispatch_queue_t queue = dispatch_get_global_queue(0, 0);
// 第四個參數:傳遞一個隊列,該隊列對應了將來的回調方法在哪個線程中執行
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue);
// 2.指定定時器開始的時間和間隔的時間, 以及精準度
// 開始時間
dispatch_time_t startTime = dispatch_time(DISPATCH_TIME_NOW, 3.0 * NSEC_PER_SEC);
// 間隔時間
uint64_t interval = 1.0 * NSEC_PER_SEC;
// 設置定時器
/*
第1個參數: 需要給哪個定時器設置
第2個參數: 定時器開始的時間/DISPATCH_TIME_NOW立即執行
第3個參數: 定時器開始之后的間隔時間
第4個參數: 定時器間隔執行的精準度, 傳入0代表最精準(盡量的讓定時器精準), 傳入一個大于0的值, 代表多少秒的范圍是可以接受的
第四個參數存在的意義: 主要是為了提高程序的性能
注意點: Dispatch的定時器接收的時間是納秒
*/
dispatch_source_set_timer(timer, startTime, interval, 0 * NSEC_PER_SEC);
// 3.指定定時器的回調方法
/*
第1個參數: 需要給哪個定時器設置
第2個參數: 需要回調的block
*/
dispatch_source_set_event_handler(timer, ^{
NSLog(@"++++++++++++++ %@", [NSThread currentThread]);
});
// 4.開啟定時器
dispatch_resume(timer);
GCD定時器
CFRunLoopObserverRef
添加Observer
*******viewDidLoad********
// 創建Observer
/*
第1個參數: 指定如何給observer分配存儲空間
第2個參數: 需要監聽的狀態類型/ kCFRunLoopAllActivities監聽所有狀態
第3個參數: 是否每次都需要監聽
第4個參數: 優先級
第5個參數: 監聽到狀態改變之后的回調
*/
CFRunLoopObserverRef? observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
switch (activity) {
case kCFRunLoopEntry:
NSLog(@"即將進入runloop");
break;
case kCFRunLoopBeforeTimers:
NSLog(@"即將處理timer");
break;
case kCFRunLoopBeforeSources:
NSLog(@"即將處理source");
break;
case kCFRunLoopBeforeWaiting:
NSLog(@"即將進入睡眠");
break;
case kCFRunLoopAfterWaiting:
NSLog(@"剛從睡眠中喚醒");
break;
case kCFRunLoopExit:
NSLog(@"即將退出");
break;
default:
break;
}
});
// 給主線程的RunLoop添加一個觀察者
/*
第1個參數: 需要給哪個RunLoop添加觀察者
第2個參數: 需要添加的Observer對象
第3個參數: 在哪種模式下可以可以監聽
*/
CFRunLoopAddObserver(CFRunLoopGetMain(), observer, kCFRunLoopDefaultMode);
// 釋放對象
CFRelease(observer);
[NSTimer scheduledTimerWithTimeInterval:5.0 target:self selector:@selector(show) userInfo:nil repeats:YES];
********show********
- (void)show{
NSLog(@"%s",__func__);
}
![Observer狀態變化 . . .]
CF的內存管理(Core Foundation)
在Core Foundation框架中,凡是帶有Create、Copy、Retain等字眼的函數,創建出來的對象,都需要在最后做一次release
RunLoop運行邏輯
邏輯
蘋果官方
網友整理
1、一個線程對應一個RunLoop,程序一啟動,就會默認給主線程創建一個RunLoop,并給這個RunLoop添加一些默認的模式,每個模式有一些timer、source、observer;
2、然后observer就可以監聽當前線程的RunLoop,進入RunLoop后就開始處理事件,
3、先處理Timer,再處理source0,然后source1,當把這些事件反復的處理完后,如果沒有事件,runLoop就會進入休眠
4、當用戶又觸發一些新事件,就會喚醒runLoop,回到3,重新處理新的Timer、source0、source1,處理完又回到休眠狀態,一直反復操作
5、當程序關閉或強制關閉RunLoop時,observer就會監聽到RunLoop退出,流程結束
observer
主要為監聽RunLoop狀態
timer
即CFRunLoopTimerRef,也就是NSTimer,當創建一個timer時,必須把它添加到runLoop,它才會執行,在添加時還必須指定模式,決定timer在哪種情況下執行;
常見模式有
kCFRunLoopDefaultMode,程序一啟動,默認為此模式,當用戶不對程序做任何操作時,RunLoop在此模式下運行,當用戶開始操作,就會切換為相應模式
因為RunLoop同一時刻只能執行一種模式,就會關閉上一種模式,重新打開一種模式,則此時timer無效,timer只能在設定的模式中有效
kCFRunLoopTrackingMode是界面跟蹤模式,用于ScrollView追蹤觸摸滑動,保證界面滑動時不受其他Mode影響
kCFRunLoopCommonMode不是一種真正的模式,是一種標記,系統把kCFRunLoopDefaultMode和kCFRunLoopTrackingMode標記為kCFRunLoopCommonMode,所以指定kCFRunLoopCommonMode,相當于既添加到kCFRunLoopDefaultMode,又添加到kCFRunLoopTrackingMode
source
一般情況下不會進行操作,官方文檔上Source分為三種,分別為基于端口、基于自定義、基于perform selector,但是也可以通過函數調用棧對source進行分類,分為source0和source1,source0是非基于端口的,也就是用戶觸發的事件,,source1是系統內部的端口觸發的一些事件
RunLoop應用
NSTimer
需指定模式,若GCD則不用
ImageView顯示
在特定模式下執行某些操作,圖片設置與拖拽分別在不同模式
PerformSelector
常駐線程
某些操作,需要重復開辟子線程,重復開辟內存過于消耗性能,可以設定子線程常駐
自動釋放池
創建和釋放
1.第一次創建, 是在runloop進入的時候創建,對應的狀態 = kCFRunLoopEntry
2.最后一次釋放, 是在runloop退出的時候? 對應的狀態 = kCFRunLoopExit
3.其它創建和釋放
每次睡覺的時候都會釋放前自動釋放池,然后再創建一個新的
可以添加Observer監聽RunLoop的狀態
比如監聽點擊事件的處理(在所有點擊事件之前做一些事情)
注意點
線程只能執行一個操作
```objc
(void)viewDidLoad
{
XMGThread *thread = [[XMGThread alloc] initWithTarget:self selector:@selector(show) object:nil];
self.thread = thread;
[thread start];
}
(void)touchesBegan:(NSSet)touches withEvent:(UIEvent)event
{
// 默認情況下一個線程只能使用一次, 也就是說只能執行一個操作, 執行完畢之后就不能使用了
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES];
}
// test方法不會執行
```
子線程RunLoop常駐
// 1.子線程的NSRunLoop需要手動創建
// 2.子線程的NSRunLoop需要手動開啟
// 3.如果子線程的NSRunLoop沒有設置source or timer, 那么子線程的NSRunLoop會立刻關閉
// 無含義,設置子線程為常住線程,讓子線程不關閉
// [[NSRunLoop currentRunLoop] addPort:[NSMachPort port] forMode:NSDefaultRunLoopMode];
NSTimer *timer = [NSTimer timerWithTimeInterval:5.0 target:self selector:@selector(test) userInfo:nil repeats:YES];
// 會添加到當前子線程
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
[[NSRunLoop currentRunLoop] run];
注意
NSRunLoop只會檢查有沒有source和timer, 沒有就關閉, 不會檢查observer
主線程沒有到期時間,子線程有
作者:牽著毛驢去趕集
鏈接:http://www.lxweimin.com/p/e4fc6aceec49
來源:簡書
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請注明出處。