1飒箭、介紹RunLoop
什么是RunLoop
//runloop從字面的意思來看就是:跑 - 圈 也就是運(yùn)行循環(huán)狼电。
//以下是runloop的偽代碼。弦蹂。肩碟。
int main(int argc, char * argv[]) {
BOOL running = YES;
do {
//執(zhí)行各種任務(wù),處理事件
}while (running);
return 0;
}
基本作用:
- 保持程序的持續(xù)運(yùn)行凸椿,如果沒有RunLoop削祈,程序執(zhí)行完main函數(shù)就結(jié)束了。
- 處理App中的各種事件(比如觸摸事件脑漫、定時器事件髓抑、Selector事件)
- 節(jié)省CPU資源,提高程序性能:該做事時做事窿撬,該休息時休息......
2启昧、RunLoop對象
- iOS中有2套API來訪問和使用RunLoop
Foundation框架中的NSRunLoop;
Core Foundation中的CFRunLoop;
NSRunLoop是基于CFRunLoopRef的一層OC包裝,所以要了解RunLoop內(nèi)部結(jié)構(gòu)劈伴,需要多研究CFRunLoopRef層面的API(Core Foundation層面)
3密末、RunLoop與線程
每條線程都有唯一的一個與之對應(yīng)的RunLoop對象
主線程中的RunLoop由系統(tǒng)自動創(chuàng)建,子線程中RunLoop可以通過手動創(chuàng)建
RunLoop在線程結(jié)束的時候會被銷毀
1跛璧、獲取RunLoop對象Foundation框架中
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對象
2严里、Core Foundation框架中
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對象
3.注意點(diǎn):開一個子線程創(chuàng)建runloop,不是通過alloc init方法創(chuàng)建,而是直接通過調(diào)用currentRunLoop方法來創(chuàng)建追城,它本身是一個懶加載的刹碾。
4.在子線程中,如果不主動獲取Runloop的話座柱,那么子線程內(nèi)部是不會創(chuàng)建Runloop的迷帜∥锸妫可以下載CFRunloopRef的源碼,搜索_CFRunloopGet0,查看代碼戏锹。
5.Runloop對象是利用字典來進(jìn)行存儲冠胯,而且key是對應(yīng)的線程Value為該線程對應(yīng)的Runloop。
4锦针、RunLoop的結(jié)構(gòu)
RunLoop相關(guān)聯(lián)的類有五個
- CFRunLoopRef
- CFRunLoopSourceRef
- CFRunLoopObserverRef
- CFRunLoopTimerRef
- CFRunLoopModeRef
5荠察、RunLoop相關(guān)類的理解
1.從上圖可以看出
- 一個RunLoop 可以有多個Mode(模式),每個模式又包含若干個 Source/Observer/Timer
- RunLoop 一次只能運(yùn)行一種Mode,切換Mode需要退出當(dāng)前Mode
2.CFRunLoopModeRef 一共有五種模式
- kCFRunLoopDefaultMode 默認(rèn)模式奈搜,通常主線程在這個模式下運(yùn)行
- kCFRunLoopCommonModes 占位符(表示)
- UITrackingRunLoopMode 界面跟蹤Mode悉盆,用于追蹤Scrollview的觸摸滑動
- UIInitializationRunLoopMode:剛啟動App時進(jìn)入的第一個Mode,啟動后不在使用馋吗。
- GSEventReceiveRunLoop:內(nèi)部Mode焕盟,接收系事件。
3.CFRunLoopSourceRef(函數(shù)調(diào)用棧)
- Source0:非基于Port(處理事件)
- Source1:基于Port (接收分發(fā)系統(tǒng)事件)
4.CFRunLoopTimerRef:基于時間的觸發(fā)器(和NSTimer差不多)
5.CFRunLoopObserverRef:監(jiān)聽RunLoop狀態(tài)的觀察者
CFRunLoopObserverRef是RunLoop的觀察者耗美,可以通過CFRunLoopObserverRef來監(jiān)聽RunLoop狀態(tài)的改變
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 狀態(tài)值:1,表示進(jìn)入RunLoop
kCFRunLoopBeforeTimers = (1UL << 1), // 狀態(tài)值:2,表示即將處理NSTimer
kCFRunLoopBeforeSources = (1UL << 2), // 狀態(tài)值:4,表示即將處理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // 狀態(tài)值:32,表示即將休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 狀態(tài)值:64,表示從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 狀態(tài)值:128,表示退出RunLoop
kCFRunLoopAllActivities = 0x0FFFFFFFU // 表示監(jiān)聽上面所有的狀態(tài)
};
};
- 如何監(jiān)聽RunLoop的狀態(tài):
- 1.創(chuàng)建CFRunLoopObserverRef
// 第一個參數(shù)用于分配該observer對象的內(nèi)存
// 第二個參數(shù)用以設(shè)置該observer所要關(guān)注的的事件京髓,詳見回調(diào)函數(shù)myRunLoopObserver中注釋
// 第三個參數(shù)用于標(biāo)識該observer是在第一次進(jìn)入run loop時執(zhí)行還是每次進(jìn)入run loop處理時均執(zhí)行
// 第四個參數(shù)用于設(shè)置該observer的優(yōu)先級,一般為0
// 第五個參數(shù)用于設(shè)置該observer的回調(diào)函數(shù)
// 第六個參數(shù)observer的運(yùn)行狀態(tài)
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) {
NSLog(@"----監(jiān)聽到RunLoop狀態(tài)發(fā)生改變---%zd", activity);
});
- 2.將觀察者CFRunLoopObserverRef添加到RunLoop上面
CFRunLoopAddObserver(CFRunLoopGetCurrent(), observer, kCFRunLoopDefaultMode);
- 3.觀察者CFRunLoopObserverRef要手動釋放
CFRelease(observer);````
6航缀、RunLoop 處理邏輯
![RunLoop 處理邏輯](http://upload-images.jianshu.io/upload_images/785453-846c6532a9e0b7ba.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
#####6商架、RunLoop 使用
1.圖片刷新(假如界面要刷新N多圖片(渲染),此時用戶拖拽UI控件就會出現(xiàn)卡的效果芥玉,我們可以通過RunLoop實(shí)現(xiàn)蛇摸,只在RunLoop默認(rèn)Mode下下載,也就是拖拽Mode下不刷新圖片)
- (void)viewDidLoad {
[super viewDidLoad];
// 只在NSDefaultRunLoopMode下執(zhí)行(刷新圖片)
[self.myImageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"0"] afterDelay:2.0 inModes:@[NSDefaultRunLoopMode]];
}
2.在RunLoop中創(chuàng)建定時器:
> - 1灿巧、第一種方式
- 定時器NSTimer的創(chuàng)建方式一:timerWithTimeInterval:
- 創(chuàng)建timer
- 將timer 添加到RunLoop中
- 第二種方式
NSTimer 和 scheduledTimerWithTimeInterval赶袄,這個不用加在runloop中,默認(rèn)Mode:NSDefaultRunLoopMode
objc
// 創(chuàng)建一個定時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// NSDefaultRunLoopMode:NSTimer只有在默認(rèn)模式下(NSDefaultRunLoopMode)工作抠藕,切換到其他模式不再工作饿肺,比如拖拽了界面上的某個控件(會切換成UITrackingRunLoopMode)
[[NSRunLoop mainRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
objc
// 創(chuàng)建一個定時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// 拖拽UI界面時出發(fā)定時器,在默認(rèn)模式(NSDefaultRunLoopMode)下不工作
[[NSRunLoop mainRunLoop] addTimer:timer forMode:UITrackingRunLoopMode];
objc
// 創(chuàng)建一個定時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
// NSRunLoopCommonModes僅僅是標(biāo)記NSTimer在兩種模式(UITrackingRunLoopMode/NSDefaultRunLoopMode)下都能運(yùn)行,但一個RunLoop中同一時間內(nèi)只能運(yùn)行一種模式
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
objc
// 默認(rèn)已經(jīng)添加到主線程中RunLoop(mainRunLoop)中(Mode:NSDefaultRunLoopMode)
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
3.保證一個線程永遠(yuǎn)不死
import "ViewController.h"
/*
思路:為了保證線程不死,我們考慮在子線程中加入RunLoop盾似,
但是由于RunLoop中沒有沒有源敬辣,就會自動退出RunLoop,
所以我們要為子線程添加一個RunLoop零院,
并且為這個RunLoop添加源(保證RunLoop不退出)
*/
@interface ViewController ()
/** 線程對象 */
@property (nonatomic, strong)NSThread *thread;
@end
@implementation ViewController
-
(void)viewDidLoad {
[super viewDidLoad];// 創(chuàng)建子線程
self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run) object:nil];//啟動子線程
[self.thread start];
}
-
(void)run {
NSLog(@"run--%@", [NSThread currentThread]); // 子線程
// 給子線程添加一個RunLoop溉跃,并且加入源
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 啟動RunLoop
[[NSRunLoop currentRunLoop] run];
NSLog(@"------------"); // RunLoop啟動,這句沒有執(zhí)行的機(jī)會
} (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
// 在子線程中調(diào)用test方法告抄,如果子線程還在就能夠調(diào)用成功
[self performSelector:@selector(test) onThread:self.thread withObject:nil waitUntilDone:YES modes:@[NSDefaultRunLoopMode]];
}(void)test {
NSLog(@"test--%@", [NSThread currentThread]); // 子線程
}
@end
#####8撰茎、RunLoop的面試題
1.runloop與autoreleasepool的關(guān)系
>
- 第一次創(chuàng)建:進(jìn)入runloop的時候
- 最后一次釋放:runloop退出的時候
- 其它創(chuàng)建和釋放:當(dāng)runloop即將休眠的時候會把之前的自動釋放池釋放,然后重新創(chuàng)建一個新的釋放池
2.如何處理滑動UI過程中打洼,廣告輪播圖停止輪詢問題龄糊,使用runloop的哪種模式
> 這個問題其實(shí)看在"RunLoop中創(chuàng)建定時器"就可以了
- kCFRunLoopDefaultMode
3.runloop有幾種模式逆粹,runloop接收幾種輸入源
>
###### runloop有幾種模式
- kCFRunLoopDefaultMode 默認(rèn)模式,通常主線程在這個模式下運(yùn)行
- kCFRunLoopCommonModes 占位符(表示)
- UITrackingRunLoopMode 界面跟蹤Mode炫惩,用于追蹤Scrollview的觸摸滑動
- UIInitializationRunLoopMode:剛啟動App時進(jìn)入的第一個Mode枯饿,啟動后不在使用。
- GSEventReceiveRunLoop:內(nèi)部Mode诡必,接收系事件奢方。
> ######runloop接收幾種輸入源
- Source0:非基于Port(處理事件)
- Source1:基于Port (接收分發(fā)系統(tǒng)事件)
#####9、參考資料
[ CFRunLoopRef](http://opensource.apple.com/source/CF/CF-1151.16/)
[官方文檔](https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/Multithreading/RunLoopManagement/RunLoopManagement.html)
[sunnyxxx線下視頻](http://pan.baidu.com/s/1o8GRQ9W)
密碼: jwnp