Runloop
- 多線程編程指南
- 資料:
- 1.開源網(wǎng)址中下載CF開頭的包谦炒,CF是CoreFoundation的縮寫置逻,CFRnLoop.c是實(shí)現(xiàn)文件
- 2.官方文檔
Runloop與線程
- 每條線程都有唯一的一個與之對應(yīng)的Runloop對象
- CFRunLoopGet0(pthread_t t)
- 主線程的Runloop已經(jīng)創(chuàng)建好了钩骇,子線程的Runloop需要主動創(chuàng)建
- Runloop在第一次獲取時創(chuàng)建酪刀,在線程結(jié)束的時候銷毀
獲得Runloop對象
- Foundation(OC)
- [NSRunloop currentRunloop]
- [NSRunloop mainRunloop]
- Core Foundation(C)
- CFRunLoopRef
- CFRunLoopGetCurrent();
- CFRunLoopGetMain();
- 轉(zhuǎn)換OC-C
- mainRunloop.getCFRunLoop
- RunLoop和線程的關(guān)系 一一對應(yīng)
- [NSThread detachNewThreadSelector:toTarget:withObject:]
- 主線程對應(yīng)的RunLoop默認(rèn)已經(jīng)創(chuàng)建了眨猎,子線程對應(yīng)的runloop需要我們手動創(chuàng)建
- [NSRunloop currentRunloop]是懶加載的卜高,只會初始化一次弥姻,會判斷當(dāng)前線程對應(yīng)的runloop是否存在南片,如果存在,直接返回庭敦,如果不存在會創(chuàng)建一個疼进,返回給我們,該方法就是用來創(chuàng)建Runloop的秧廉。
Runloop相關(guān)類
-
CoreFoundation中關(guān)于RunLoop的五個類:
- CFRunLoopRef
- CFRunLoopModeRef:運(yùn)行模式
- CFRunLoopSourceRef:事件源伞广、輸入源
- CFRunLoopTimerRef:定時器事件
- CFRunLoopObserverRef:監(jiān)聽者、觀察者
-
關(guān)系:
- Runloop要啟動必須要選擇一種運(yùn)行模式疼电,有多種運(yùn)行模式可供選擇赔癌,但只能選擇一種
- 運(yùn)行模式里面有source/observer/timer,runloop啟動之前會檢查運(yùn)行模式是否為空(怎么判斷是否為空澜沟?檢查有沒有source和timer,如果一個source和timer都沒有就為空灾票,如果為空,runloop啟動之后就退出了)
- 開啟runloop運(yùn)行循環(huán)-死循環(huán)
-
運(yùn)行模式:CFRunloopModeRef
- CFRunLoopRef代表RunLoop的運(yùn)行模式
- 一個RunLoop包含若干個Mode,每個Mode又包含若干個Source/Timer/Observer
- 每一次Runloop啟動時茫虽,只能指定其中一個Mode,這個Mode被稱作currentMode
- 如果需要切換Mode,只能退出Loop,再重新指定一個Mode進(jìn)入
- 這樣做主要是為了分隔開不同組的Source/Timer/Observer,讓其互不影響
CFRunLoopModeRef
- 系統(tǒng)默認(rèn)注冊了5個Mode
- kCFRunLoopDefaultMode:App的默認(rèn)Mode,通常主線程是在這個Mode下運(yùn)行
- UITrackingRunLoopMode:界面跟蹤Mode,用于ScrollView追蹤觸摸滑動刊苍,保證界面滑動時不受其他Mode影響
- UIInitializationRunLoopMode:在剛啟動App時進(jìn)入第一個Mode,啟動完成后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部Mode,通常用不到
- KCFRunLoopCommonModes:這是一個占位用的Mode,不是一種真正的Mode
CFRunLoopModeRef和NSTimer
- 定時器使用
- 方法一:[NSTimer scheduledTimerWithTimerInterval:target:selector:userInfo:repeats:]
- 該方法內(nèi)部會自動將創(chuàng)建的定時器對象添加到當(dāng)前的runloop當(dāng)中,并且指定runloop的運(yùn)行模式為默認(rèn)
- 開一個子線程調(diào)用這個方法濒析,這個定時器能工作嗎正什?
- 不能工作!:判印婴氮!子線程對應(yīng)的runloop沒有創(chuàng)建
- 手動創(chuàng)建runloop,手動添加定時器,還是不可以6苤隆主经!
- 方法一:[NSTimer scheduledTimerWithTimerInterval:target:selector:userInfo:repeats:]
-
注意
:- 子線程對應(yīng)的runloop需要手動的創(chuàng)建
- 子線程創(chuàng)建的runloop還需要手動的開啟 [currentRunLoop run]
- 方法二:[NSTimer timerWithTimerInterval:target:selector:userInfo:repeats:]
- 使用這個方法需要把定時器添加到runloop中
- [NSRunloop currentRunloop] addTimer:forMode:
- 問題:拖拽textField的時候,定時器不工作的原因庭惜?
- 滾動textField罩驻,runloop運(yùn)行模式改變了,切換到了UITrackingMode
- 問題:怎么讓定時器护赊,在滾動textField的時候惠遏,也能工作?
- 方法一:把定時器對象添加到runloop中,并且制定runloop的運(yùn)行模式為默認(rèn)骏啰,只有runloop的運(yùn)行模式為NSDefaultRunloopMode的時候定時器才工作节吮,要求在滾動textField的時候定時器也能工作,并且指定runloop的運(yùn)行模式為tracking的時候判耕,定時器才工作
- 方法二:占位模式(等價于上面的兩行代碼)NSRunLoopCommonModes = Default & Tracking
- NSRunLoopCommonModes表示把定時器對象添加到runloop中并且指定運(yùn)行模式為Default或者是Tracking
GCD中的定時器(掌握)
- 第一個函數(shù):創(chuàng)建定時器對象
dispatch_source_t timer = dispatch_source_create(DISPATCH_SOURCE_TYPE__TIMER,0,0,dispatchQueue)
- 第一個參數(shù):要創(chuàng)建的是一個定時器
- 第二個參數(shù):默認(rèn)總是傳0透绩,描述信息
- 第三個參數(shù):默認(rèn)總是傳0,
- 第四個參數(shù):隊(duì)列,并發(fā)隊(duì)列(全局),決定代碼塊(dispatch_source_set_event_handler)在哪個線程中調(diào)用(主隊(duì)列+主線程)
- 第二個函數(shù):設(shè)置定時器
dispatch_source_set_timer(timer,DISPATCH_TIME_NOW,intervalInSeconds *NSEC_PER_SEC,leewayInSeconds *NSEC_PER_SEC)
- 第一個參數(shù):定時器對象
- 第二個參數(shù):定時器開始計(jì)時的時間(開始時間)
- 怎么修改開始時間渺贤?
- dispatch_time_t t = dispatch_time(DISPATCH_TIME_NOW,延遲時間2.0*NSEC_PER_SEC)
- 第三個參數(shù):設(shè)置間隔時間 GCD的時間單位:納秒
- 第四個參數(shù):精準(zhǔn)的雏胃,0表示絕對精準(zhǔn)
- 第三個函數(shù):在block塊里面要執(zhí)行的任務(wù)请毛,GCD定時器時間到了之后要執(zhí)行的任務(wù)
dispatch_source_set_event_handler(timer,^{})
-
第四個函數(shù):恢復(fù)執(zhí)行dispatch_resume(timer)
- 默認(rèn)是暫停狀態(tài)志鞍,需要手動開啟
-
定時器不工作的原因:
- GCD是不會受到runloop的影響
- 原因是timer被釋放了
- 定一個屬性,讓timer不釋放
RunloopSourceRef
- CFRunloopSourceRef:是事件源(輸入源)
- 以前的分類:
- Port-Based Source 基于端口
- Custom Input Sources 自定義
- Cocoa Perform Selector Sources
- 現(xiàn)在的分類:
- Source0:非基于Port
- Source1:基于Port
- 根據(jù)函數(shù)調(diào)用棧進(jìn)行分類的
RunloopObserverRef
- 01.創(chuàng)建一個監(jiān)聽者
-
CFRunloopObserverRef observer = CFRunLoopObserverCreateWithHandler()
- 第一個參數(shù)方仿,分配存儲空間
- CFAllocatorGetDefault()
- 第二個參數(shù):要監(jiān)聽的狀態(tài) 0
- 第三個參數(shù):是否要持續(xù)監(jiān)聽YES
- 第四個參數(shù):和優(yōu)先級相關(guān) 0
- block塊:當(dāng)發(fā)現(xiàn)監(jiān)聽對象狀態(tài)改變的時候調(diào)用該block
- KCFRunLoopEntry:runloop進(jìn)入啟動
- BeforeTimers:runloop即將處理timer事件
- BeforeSources: runloop即將處理source事件
- beforeWaiting:runloop即將進(jìn)入睡眠狀態(tài)
- afterWaiting: runloop被喚醒
- exit:runloop即將退出
- AllActivities:監(jiān)聽所有的活動狀態(tài)
- 第一個參數(shù)方仿,分配存儲空間
只能使用C語言代碼
-
- 02.設(shè)置監(jiān)聽
- CFRunLoopAddObserver()
- CFRunloopRef,要監(jiān)聽的runloop對象
- CFRunLoopObserverRef,監(jiān)聽者對象本身
- CFStringRef:runloop的運(yùn)行模式
- NSDefaultRunLoopMode(OC)
- KCFRunLoopDefaultMode(C)??
- CFRunLoopAddObserver()
runloop的運(yùn)行流程
- 處理邏輯(網(wǎng)友整理)
- 1.通知內(nèi)部Observer:即將進(jìn)入Loop
- 2.通知Observer:將要處理Timer
- 3.通知Observer:將要處理Source0
- 4.處理Source0
- 5.如果有Source1,跳到第九步
- 6.通知Observer:線程即將休眠
- 7.休眠等待喚醒
- 8.通知Observer:線程剛被喚醒
- 9.處理喚醒時收到的消息固棚,之后跳回2
- 10.通知Observer:即將退出Runloop
- 處理邏輯(官方)
- Runloop的事件隊(duì)列
- 每次運(yùn)行runloop,線程的runloop會自動處理之前未處理的消息仙蚜,并通知相關(guān)的觀察者此洲,具體的順序如下:
- 1.通知觀察者runloop已經(jīng)啟動
- 2.通知觀察者任何即將要開始的定時器
- 3.通知觀察者任何即將啟動的非基于端口的源
- 4.啟動任何準(zhǔn)備好的的非基于端口的源
- 5.如果基于端口的源準(zhǔn)備好并處于等待狀態(tài),立即啟動委粉,并進(jìn)入步驟9
- 6.通知觀察者線程進(jìn)入休眠
- 7.將線程置于休眠直到下面的任意一個事件發(fā)生
- 某一時間到達(dá)基于端口的源
- 定時器啟動
- Runloop設(shè)置的時間已經(jīng)超時
- runloop被顯示喚醒
- 8.通知觀察者線程將被喚醒
- 9.處理未處理的事件
- 如果用戶定義的定時器啟動呜师,處理定時器事件并重啟runloop,進(jìn)入步驟2
- 如果輸入源啟動,傳遞相應(yīng)的消息
- 如果runloop被顯示喚醒而且時間還沒超時贾节,重啟runloop,進(jìn)入步驟2
- 10.通知觀察者runloop結(jié)束
代碼模擬runloop死循環(huán)
void msg(int n)
{
NSLog(@"runloop被喚醒");
NSLog(@"runloop處理%zd事件",n);
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"runloop啟動了");
do {
NSLog(@"runloop即將處理timer事件");
NSLog(@"runloop即將處理source0事件");
NSLog(@"source1事件");
NSLog(@"runloop詢問:還有事件需要我處理嗎?");
NSLog(@"runloop計(jì)入到休眠狀態(tài)");
int number = 0;
scanf("%zd",&number);
msg(number);
} while (1);
}
return 0;
}
runloop的應(yīng)用
- NSTimer
- ImageView顯示
- PerformSelector
- selecter事件和runloop的關(guān)系
- performSelector:withObject:afterDelay:inModes:
- @[NSDefaultRunLoopMode,UITrackingRunLoopMode]
- @[NSRunLoopCommonModes]
- 默認(rèn)情況下汁汗,selector事件是被添加到當(dāng)前的runloop中執(zhí)行的,并且指定了運(yùn)行模式為默認(rèn)
- performSelector:withObject:afterDelay:inModes:
-(void)test1
{
[NSThread detachNewThreadSelector:@selector(task) toTarget:self withObject:nil];
}
-(void)task
{
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"Snip20160713_9"] afterDelay:3.0];
//運(yùn)行模式啟動之后栗涂,判斷有一個selector事件知牌,runloop才能夠開啟
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
[runloop run];
NSLog(@"+++++");
}
//需要進(jìn)行線程間通信,否則會報錯
- 常駐線程
- 開一條子線程斤程,讓子線程一直工作
- 獲得當(dāng)前線程對應(yīng)的runloop對象
- 為runloop添加input source 或者是timer source
- 啟動runloop
//創(chuàng)建線程角寸,執(zhí)行任務(wù)
- (IBAction)createNewThreadBtnClick:(id)sender {
//01 創(chuàng)建線程,執(zhí)行任務(wù)
//因?yàn)橐玫骄€程對象,所以用NSThread創(chuàng)建線程
NSThread *thread = [[NSThread alloc]initWithTarget:self selector:@selector(run1) object:nil];
//02 執(zhí)行任務(wù)
[thread start];
self.thread = thread;
}
//繼續(xù)執(zhí)行任務(wù)
- (IBAction)goOnBtnClick:(id)sender {
/*
self.thread 任務(wù)執(zhí)行完畢,已經(jīng)進(jìn)入到死亡狀態(tài)|但是還沒有被釋放
程序崩潰報錯:attempt to start the thread again
[self.thread start];?不能嘗再次開啟線程
怎么讓任務(wù)執(zhí)行不完呢忿墅?在run1方法里搞一個死循環(huán)扁藕?這樣做線程不會死,但是不會執(zhí)行任務(wù)2
正確做法:在run1方法里面開啟一個runloop
*/
//讓之前創(chuàng)建的線程繼續(xù)執(zhí)行
[self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)run1
{
NSLog(@"run1---%@",[NSThread currentThread]);
/*
do {
NSLog(@"-%@",[NSThread currentThread]);
} while (1);
*/
//001 獲得當(dāng)前線程對應(yīng)的runloop對象
NSRunLoop *currentRunloop = [NSRunLoop currentRunLoop];
//002 為runloop添加input soucre或者是timer souce ,為了讓運(yùn)行模式不為空疚脐,否則不能開啟runloop
//基于端口的事件
//好處:不需要調(diào)用方法纹磺,開發(fā)中常用
[currentRunloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//Perform事件
//存在的問題:3.0s之后就退出了,開發(fā)中一般不用selector方法
//[self performSelector:@selector(run3) withObject:nil afterDelay:3.0];
//NSTimer事件
//[NSTimer scheduledTimerWithTimeInterval:3.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
//003 啟動runloop
//runUntilDate |run 內(nèi)部都指定了運(yùn)行模式為默認(rèn)
[currentRunloop run];
//[currentRunloop runUntilDate:[NSDate dateWithTimeIntervalSinceNow:10000000]];
NSLog(@"_______end____");//驗(yàn)證runloop是否開啟了,打印(沒有開啟runloop)
}
-(void)run2
{
NSLog(@"run2---%@",[NSThread currentThread]);
}
-(void)run3
{
NSLog(@"run3---%@",[NSThread currentThread]);
}
-(void)timerTest
{
NSLog(@"timer---");
}
runloop常見面試題
-
什么是runloop?
- 從字面意思看:運(yùn)行循環(huán)亮曹、跑圈
- 其實(shí)它內(nèi)部就是do while循環(huán)橄杨,在這個循環(huán)內(nèi)部不斷地處理各種任務(wù)(比如Source/Timer/Observer)
- 一個線程對應(yīng)一個Runloop,主線程的runloop默認(rèn)已經(jīng)啟動照卦,子線程的runloop需要手動啟動(調(diào)用run方法)
- Runloop只能選擇一個mode啟動式矫,如果當(dāng)前mode中沒有任何source(source0/source1)/timer,那么就直接退出runloop
-
自動釋放池什么時候釋放役耕?
- 通過observer監(jiān)聽runloop的狀態(tài)
- 自動釋放池的創(chuàng)建和釋放
- 創(chuàng)建:runloop啟動的時候
- 銷毀:runloop退出的時候
- 其他時候的創(chuàng)建和銷毀:當(dāng)runloop即將進(jìn)入到休眠的時候會把之前的釋放池銷毀,重新創(chuàng)建一個新的自動釋放池
-
在開發(fā)中如何使用runloop?應(yīng)用場景采转?
- 開啟一個常駐線程(讓一個子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)來消息,處理其他事件)
- 在子線程中開啟一個定時器
- 在子線程中進(jìn)行一些長期監(jiān)控
- 可以控制定時器在特定模式下執(zhí)行
- 可以讓某些事件(行為故慈、任務(wù))在特定模式下執(zhí)行
- 可以添加Observer監(jiān)聽Runloop的狀態(tài)板熊,比如監(jiān)聽點(diǎn)擊事件的處理(在所有點(diǎn)擊事件之前做一些事情)
- 開啟一個常駐線程(讓一個子線程不進(jìn)入消亡狀態(tài),等待其他線程發(fā)來消息,處理其他事件)
總結(jié):
-
多圖下載
- 緩存策略:內(nèi)存緩存+磁盤緩存
- 開子線程下載
-
SDWebImage
- 基本使用
- 結(jié)構(gòu)
- caches
- download
- manager
- 細(xì)節(jié)
-
Runloop
- 概念:運(yùn)行循環(huán)(死循環(huán))
- 作用【3】
- 保持程序的持續(xù)運(yùn)行
- 處理app中的各種事件
- 提高性能
- 與線程關(guān)系:一一對應(yīng)(字典)
- 線程與runloop是一一對應(yīng)的關(guān)系(字典)
- 主線程對應(yīng)的runloop已經(jīng)創(chuàng)建并且啟動mainRunloop
- 子線程對應(yīng)的runloop需要手動創(chuàng)建并且開啟
- 退出:線程銷毀
- runloop的相關(guān)類
- runloopRef runloop本身
- runloopModeRef 運(yùn)行模式[5]
- 默認(rèn)Default
- 界面追蹤Tracking
- commonModes
- runloopTimeRef
- observer 監(jiān)聽者
- sourceRef 事件源