基本做用面試
最下面的UIApplicationMain函數內部就啓動了一個RunLoop,因此UIApplicationMain就一直沒有返回,保持了程序的持續運行markdown
iOS有兩套API來訪問和使用RunLoop網絡
NSRunLoop和CFRunLoopRef都表明着RunLoop對象 可是NSRunLoop是基於CFRunLoopRef的一層OC包裝 ,因此更底層的是CFRunLoopRef異步
是如何保證每條線程有惟一一個對應的RunLoop對象的呢?函數
注意,經過NSRunLoop和CFRunLoopRef獲得的RunLoop也仍是不一樣的對象,可是能夠經過.getCFRunLoop
將NSRunLoop轉爲CFRunLoopRefoop
子線程的RunLoop直接經過[NSThread currentThread]
方法建立獲得,實際上是懶加載的性能
Runloop的五個類atom
這五個類的關係:spa
系統默認註冊了五個mode 線程
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
[self timer];
}
-(void)timer{
//1. 建立計時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//2. 將計時器添加到runloop中
//第一個參數是計時器,第二個參數是runloop的mode,這裏選擇默認mode
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
}
-(void)run{
NSLog(@"run---%@----%@",[NSThread currentThread],[NSRunLoop currentRunLoop].currentMode);
}
複製代碼
當你點擊模擬器時,打印結果以下,能夠看到每隔2s進行一次打印
2021-03-10 10:37:05.571371+0800 runloop1[1771:51979] run---<NSThread: 0x600001f44a00>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 10:37:07.572174+0800 runloop1[1771:51979] run---<NSThread: 0x600001f44a00>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 10:37:09.571467+0800 runloop1[1771:51979] run---<NSThread: 0x600001f44a00>{number = 1, name = main}----kCFRunLoopDefaultMode
複製代碼
可是當你向storyboard中添加一個textView會發生什麼狀況呢?
咱們來看看場景二
你會發現當你點擊背景時,正常每隔2s進行打印,可是當你滑動textView時,打印中止,且你再中止滑動,打印又從新開始
這是爲何?
解決方法,修改runloop的mode類型,改成UITrackingRunLoopMode(界面追蹤模式)
-(void)timer{
//1. 建立計時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//2. 將計時器添加到runloop中
[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];
}
複製代碼
從新運行,會發現當你改成滑動textView時,打印結果以下
2021-03-10 10:55:22.039501+0800 runloop1[2084:70476] run---<NSThread: 0x600002eb4880>{number = 1, name = main}----UITrackingRunLoopMode
2021-03-10 10:55:22.367009+0800 runloop1[2084:70476] run---<NSThread: 0x600002eb4880>{number = 1, name = main}----UITrackingRunLoopMode
複製代碼
能夠看到此時的模式是界面追蹤模式
那麼如何既點擊view時啓動計時器打印,拖動textview時也打印呢?
接下來進入下一場景
但願達到 既點擊view時啓動計時器打印,拖動textview時也打印 有兩種方式:
-(void)timer{
//1. 建立計時器
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//2. 將計時器添加到runloop中
[[NSRunLoop currentRunLoop]addTimer:timer forMode:UITrackingRunLoopMode];
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
}
複製代碼
打印結果以下
2021-03-10 11:01:20.588388+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 11:01:22.588833+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----kCFRunLoopDefaultMode
2021-03-10 11:01:24.588094+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----UITrackingRunLoopMode
2021-03-10 11:01:26.588974+0800 runloop1[2269:79479] run---<NSThread: 0x60000038c240>{number = 1, name = main}----UITrackingRunLoopMode
複製代碼
NSRunLoopCommonModes = kCFRunLoopDefaultMode + UITrackingRunLoopMode
[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
複製代碼
定時器的建立有兩種方式,上面的場景中使用的是第一種,也就是timerWithTimeInterval
的方式,接下來的場景咱們使用第二種方式scheduledTimerWithTimeInterval
回憶一下咱們剛剛上面建立定時器的方法還須要本身將定時器添加到runloop當中,可是scheduledTimerWithTimeInterval建立的定時器是不須要這樣的,系統會幫你作,而且設置運行模式位默認mode
-(void)timer2{
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
}
複製代碼
還有一個問題,若是咱們的timer是在子線程中建立的,會出現什麼問題?
這是爲何呢?緣由很簡單
-(void)timer2{
//1. 建立子線程的runloop
NSRunLoop *currentRunLoop = [NSRunLoop currentRunLoop];
//2. 建立計時器
[NSTimer scheduledTimerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//3. 啓動runloop
[currentRunLoop run];
}
複製代碼
重點來看這個常駐線程
這時候就須要咱們的常駐線程了
注意,咱們想要達到的目的是讓一個線程不在他的任務執行完畢後就死亡,而是進入等待模式,在須要時再從新使用該線程
解決方法:
接下來進入具體應用狀況
定義三個按鈕以下
@property(nonatomic,strong) NSThread *thread;
複製代碼
- (IBAction)createClickBtn:(id)sender {
// 建立線程
self.thread = [[NSThread alloc]initWithTarget:self selector:@selector(createRunLoop) object:nil];
[self.thread start];
}
複製代碼
建立runloop有兩種設置方式
- (void)createRunLoop{
//1. 得到子線程對應的runloop
NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
//2. 在runloop中設置一個timer
NSTimer *timer = [NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(run) userInfo:nil repeats:YES];
//3. 將timer添加到runloop中
[currentLoop addTimer:timer forMode:NSDefaultRunLoopMode];
//4. 啓動runloop
[currentLoop run];
}
複製代碼
- (void)createRunLoop{
//1. 得到子線程對應的runloop
NSRunLoop *currentLoop = [NSRunLoop currentRunLoop];
//2. 在runloop中設置一個source
[currentLoop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
//3. 啓動runloop
[currentLoop run];
}
複製代碼
- (IBAction)task1ClickBtn:(id)sender {
[self performSelector:@selector(task1) onThread:self.thread withObject:nil waitUntilDone:YES];
}
- (IBAction)task2ClickBtn:(id)sender {
[self performSelector:@selector(task2) onThread:self.thread withObject:nil waitUntilDone:YES];
}
-(void)task1{
NSLog(@"task1---%@",[NSThread currentThread]);
}
-(void)task2{
NSLog(@"task2---%@",[NSThread currentThread]);
}
//這個run函數是用於計時器狀況時的
-(void)run{
NSLog(@"%s",__func__);
}
複製代碼
運行結果以下
能夠看到當你交換點擊兩個按鈕時,任務也是交替執行的,而且是在同一線程下
例如一個網絡請求,由於網絡請求是異步的,且比較耗時,因此咱們能夠建立一個子線程來負責網絡請求的功能