在我的另一篇文章RunLoop簡(jiǎn)單介紹了關(guān)于runLoop基礎(chǔ)知識(shí)和NSTimer時(shí)runloop簡(jiǎn)單應(yīng)用, 下面看下怎么使用RunLoop。
一、Runloop的啟動(dòng)
在談到RunLoop與線程的關(guān)系時(shí), 每個(gè)人都會(huì)說(shuō)主線程的RunLoop是默認(rèn)自動(dòng)創(chuàng)建啟動(dòng)的,子線程的 RunLoop 需要手動(dòng)創(chuàng)建,手動(dòng)啟動(dòng), 這是在多數(shù)博文里都寫(xiě)到的,但是主線程的RunLoop是怎樣啟動(dòng), 又怎樣手動(dòng)創(chuàng)建的呢
1. 主線程的RunLoop是默認(rèn)啟動(dòng)的
-
先看下程序啟動(dòng)時(shí)都做了什么,此處盜圖 程序啟動(dòng)過(guò)程.png
下面看下main()函數(shù)
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}
在main()函數(shù)里只調(diào)用一個(gè)UIApplicationMain()函數(shù), 先點(diǎn)進(jìn)去看下UIApplicationMain函數(shù)
1. UIKIT_EXTERN int UIApplicationMain(int argc, char * _Nonnull * _Null_unspecified argv, NSString * _Nullable principalClassName, NSString * _Nullable delegateClassName);
從上面UIApplicationMain函數(shù)看到返回值是int類(lèi)型,那么開(kāi)始改造下代碼,看UIApplicationMain()返回的是什么
int main(int argc, char * argv[]) {
@autoreleasepool {
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
// 開(kāi)始改造main函數(shù)
NSLog(@"調(diào)用UIApplicationMain函數(shù)");
int who = UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"UIApplicationMain函數(shù)的返回值: %d", who);
return who;
}
}
運(yùn)行試下, 沒(méi)有打印返回值who??梢钥闯?code>UIApplicationMain函數(shù)開(kāi)啟了一個(gè)和主線程相關(guān)的RunLoop,導(dǎo)致UIApplicationMain不會(huì)返回,一直在運(yùn)行中,也就保證了程序的持續(xù)運(yùn)行。
2. 子線程開(kāi)啟RunLoop
子線程的 RunLoop 需要手動(dòng)創(chuàng)建,手動(dòng)啟動(dòng)
// 子線程 開(kāi)啟 RunLoop
// 創(chuàng)建子線程
_thead = [[NSThread alloc] initWithTarget:self selector:@selector(startRunLoop) object:nil];
// 開(kāi)啟 子線程
[_thead start];
- (void)startRunLoop
{
// 獲取 currentRunLoop
NSRunLoop *runloop = [NSRunLoop currentRunLoop];
// 因?yàn)樵谧泳€程中開(kāi)啟runloop 至少需要一個(gè)timer 或者 source ,runloop 才會(huì)啟動(dòng). addPort 是比添加timer 更好的方式
[runloop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 開(kāi)啟 runloop
[runloop run];
}
注意 runloop 在線程中獲取就是創(chuàng)建
RunLoop應(yīng)用場(chǎng)景
下面介紹幾個(gè)RunLoop應(yīng)用場(chǎng)景
1. 子線程的NSTimer
在子線程中添加定時(shí)器, 必須手動(dòng)創(chuàng)建并開(kāi)啟RunLoop, 將Timer添加進(jìn)創(chuàng)建的runloop中, 否則定時(shí)器無(wú)效。
_thead = [[NSThread alloc] initWithBlock:^{
NSTimer *timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerTest) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
}];
[_thead start];
2. 滑動(dòng)與圖片刷新
問(wèn)題: tableview的cell中的imageView, 需要從網(wǎng)絡(luò)加載的圖片的時(shí)候,上下滑動(dòng)動(dòng)tableView,異步線程會(huì)去獲取圖片,獲取完成后主線程就會(huì)設(shè)置cell的圖片,但是會(huì)造成卡頓。
解決 滑動(dòng)tableView的時(shí)候,RunLoop是在 UITrackingRunLoopMode下進(jìn)行,此時(shí)不會(huì)去設(shè)置圖片,所以可以設(shè)置將圖片的任務(wù)在NSDefaultRunLoopMode下進(jìn)行,即當(dāng)tableView滑動(dòng)停止的時(shí)候,再去設(shè)置圖片。
[_imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@""] afterDelay:0.5 inModes:@[NSDefaultRunLoopMode]];
3. 常駐子線程
因?yàn)樵谧泳€程中開(kāi)啟runloop 至少需要一個(gè)timer 或者 source ,runloop 才會(huì)啟動(dòng)。所以想要保持線程長(zhǎng)期運(yùn)轉(zhuǎn),必須讓子線程一直處理事件??梢栽谧泳€程中加入RunLoop,并且給Runloop設(shè)置item,防止Runloop自動(dòng)退出。
- a. 添加Timer ---- 參考上文中 子線程的NSTimer
- b. 添加事件源 ---- 參考上文中 子線程開(kāi)啟RunLoop
關(guān)于RunLoop的應(yīng)用場(chǎng)景還有很多, 如果感興趣的話, 推薦去看看 我的runloop學(xué)習(xí)筆記 寫(xiě)得非常好, 里面都是實(shí)際項(xiàng)目中的使用點(diǎn), 還附有Demo。
