什么是RunLoop瘪弓?
- 可以理解為字面意思:Run表示運(yùn)行劫映,Loop表示循環(huán)乓旗。結(jié)合在一起就是運(yùn)行的循環(huán)的意思府蛇。哈哈,我更愿意翻譯為『跑圈』屿愚。直觀理解就像是不停的跑圈
- RunLoop實(shí)際上是一個(gè)對(duì)象汇跨,這個(gè)對(duì)象在循環(huán)中用來(lái)處理程序運(yùn)行過(guò)程中出現(xiàn)的各種事件(比如說(shuō)觸摸事件、UI刷新事件妆距、定時(shí)器事件穷遂、Selector事件),從而保持程序的持續(xù)運(yùn)行娱据;而且在沒(méi)有事件處理的時(shí)候蚪黑,會(huì)進(jìn)入睡眠模式,從而節(jié)省CPU資源中剩,提高程序性能
- RunLoop保存在一個(gè)全局的字典里祠锣,線程作為key,RunLoop作為value
RunLoop內(nèi)部結(jié)構(gòu)
- _modes:存放所有的模式
- _pthread:存放的線程
-
_currentMode:當(dāng)前的模式
RunLoopMode內(nèi)部結(jié)構(gòu)
- _soutce0, _soutce1:存放在CFRunLoopSourceRef
- _observers:存放在CFRunLoopObserverRef
-
_timers:存放在CFRunLoopTimerRef
RunLoop和線程
- RunLoop和線程是息息相關(guān)的咽安,我們知道線程的作用是用來(lái)執(zhí)行特定的一個(gè)或多個(gè)任務(wù),但是在默認(rèn)情況下蓬推,線程執(zhí)行完之后就會(huì)退出妆棒,就不能再執(zhí)行任務(wù)了。這時(shí)我們就需要采用一種方式來(lái)讓線程能夠處理任務(wù)沸伏,并不退出糕珊。所以,我們就有了RunLoop
- 一條線程對(duì)應(yīng)一個(gè)RunLoop對(duì)象毅糟,每條線程都有唯一一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象红选。
- 我們只能在當(dāng)前線程中操作當(dāng)前線程的RunLoop,而不能去操作其他線程的RunLoop姆另。
- RunLoop對(duì)象在第一次獲取RunLoop時(shí)創(chuàng)建喇肋,銷毀則是在線程結(jié)束的時(shí)候坟乾。
- 主線程的RunLoop對(duì)象系統(tǒng)自動(dòng)幫助我們創(chuàng)建好了(原理如下),而子線程的RunLoop對(duì)象需要我們主動(dòng)創(chuàng)建蝶防。
- 子線程的runLoop里面至少要有一個(gè)source或者是timer甚侣,observer是不行的。
默認(rèn)情況下主線程的RunLoop原理
-
我們?cè)趩?dòng)一個(gè)iOS程序的時(shí)候间学,系統(tǒng)會(huì)調(diào)用創(chuàng)建項(xiàng)目時(shí)自動(dòng)生成的main.m的文件殷费。main.m文件如下所示:
int main(int argc, char * argv[]) { @autoreleasepool { return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class])); } }
-
其中UIApplicationMain函數(shù)內(nèi)部幫我們開(kāi)啟了主線程的RunLoop,UIApplicationMain內(nèi)部擁有一個(gè)無(wú)線循環(huán)的代碼低葫。上邊的代碼中開(kāi)啟RunLoop的過(guò)程可以簡(jiǎn)單的理解為如下代碼:
int main(int argc, char * argv[]) { BOOL running = YES; do { // 執(zhí)行各種任務(wù)详羡,處理各種事件 // ...... } while (running); return 0; }
從上邊可看出,程序一直在do-while循環(huán)中執(zhí)行嘿悬,所以UIApplicationMain函數(shù)一直沒(méi)有返回实柠,我們?cè)谶\(yùn)行程序之后程序不會(huì)馬上退出,會(huì)保持持續(xù)運(yùn)行狀態(tài)鹊漠。
下圖是蘋(píng)果官方給出的RunLoop模型圖主到。
從上圖中可以看出,RunLoop就是線程中的一個(gè)循環(huán)躯概,RunLoop在循環(huán)中會(huì)不斷檢測(cè)登钥,通過(guò)Input sources(輸入源)和Timer sources(定時(shí)源)兩種來(lái)源等待接受事件;然后對(duì)接受到的事件通知線程進(jìn)行處理娶靡,并在沒(méi)有事件的時(shí)候進(jìn)行休息
RunLoop相關(guān)類
- 下面我們來(lái)了解一下Core Foundation框架下關(guān)于RunLoop的5個(gè)類牧牢,只有弄懂這幾個(gè)類的含義,我們才能深入了解RunLoop運(yùn)行機(jī)制
- CFRunLoopRef:代表RunLoop的對(duì)象
- CFRunLoopModeRef:RunLoop的運(yùn)行模式
- CFRunLoopSourceRef:就是RunLoop模型圖中提到的輸入源/事件源
- source0:
- 觸摸事件姿锭,
- performSelector:onThread
- source1:
- 基于Port的線程間的通信
- 系統(tǒng)事件的捕捉
- source0:
- CFRunLoopTimerRef:就是RunLoop模型圖中提到的定時(shí)源
- NSTimer
- performSelector:withObject:afterDelay
- CFRunLoopObserverRef:觀察者塔鳍,能夠監(jiān)聽(tīng)RunLoop的狀態(tài)改變
- 用于監(jiān)聽(tīng)RunLoop的狀態(tài)
- UI刷新(BeforeWaiting)
-
AutoreleasePool
CFRunLoopRef
-
CFRunLoopRef就是Core Foundation框架下RunLoop對(duì)象類。我們可通過(guò)以下方式來(lái)獲取RunLoop對(duì)象:
-
Core Foundation
- CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
- CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
當(dāng)然呻此,在Foundation框架下獲取RunLoop對(duì)象類的方法如下:
-
Foundation
- [NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
- [NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
-
CFRunLoopModeRef
- CFRunLoopModeRef代表著RunLoop的運(yùn)行模式
- 一個(gè)RunLoop有若干個(gè)Mode轮纫,每個(gè)模式又包含若干個(gè)Source0,Source1焚鲜,Timers掌唾,Observer
- RunLoop啟動(dòng)時(shí)只能選擇其中一個(gè)Mode,作為currentMode
- 如果需要切換運(yùn)行模式忿磅,只能退出Loop糯彬,再重新選擇一個(gè)運(yùn)行模式進(jìn)入。
- 這樣做主要是為了分隔開(kāi)不同組的Source0葱她,Source1撩扒,Timers,Observer吨些,讓其互不影響 搓谆。
- 如果Mode里面沒(méi)有Source0炒辉,Source1,Timers挽拔,Observer辆脸,RunLoop會(huì)馬上退出
常見(jiàn)的幾種模式
- kCFRunLoopDefaultMode:App的默認(rèn)運(yùn)行模式,通常主線程是在這個(gè)運(yùn)行模式下運(yùn)行
- UITrackingRunLoopMode:跟蹤用戶交互事件(用于 ScrollView 追蹤觸摸滑動(dòng)螃诅,保證界面滑動(dòng)時(shí)不受其他Mode影響)
- UIInitializationRunLoopMode:在剛啟動(dòng)App時(shí)第進(jìn)入的第一個(gè) Mode啡氢,啟動(dòng)完成后就不再使用
- GSEventReceiveRunLoopMode:接受系統(tǒng)內(nèi)部事件,通常用不到
- kCFRunLoopCommonModes:偽模式术裸,不是一種真正的運(yùn)行模式(后邊會(huì)用到)
CFRunLoopObserverRef
- 創(chuàng)建Observer
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) { kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop:1 kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timer:2 kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Source:4 kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠:32 kCFRunLoopAfterWaiting = (1UL << 6), // 即將從休眠中喚醒:64 kCFRunLoopExit = (1UL << 7), // 即將從Loop中退出:128 kCFRunLoopAllActivities = 0x0FFFFFFFU // 監(jiān)聽(tīng)全部狀態(tài)改變 }; //創(chuàng)建一個(gè)監(jiān)聽(tīng)對(duì)象 // 第一個(gè)參數(shù):分配存儲(chǔ)空間 // 第二個(gè)參數(shù):要監(jiān)聽(tīng)的狀態(tài) // 第三個(gè)參數(shù): 是否要持續(xù)監(jiān)聽(tīng) // 第四個(gè)參數(shù):優(yōu)先級(jí) // 第五個(gè)參數(shù):回調(diào) CFRunLoopObserverRef obser = CFRunLoopObserverCreateWithHandler(CFAllocatorGetDefault(), kCFRunLoopAllActivities, YES, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity activity) { // 狀態(tài)判斷 switch (activity) { case kCFRunLoopEntry: break; case kCFRunLoopBeforeTimers: break; case kCFRunLoopBeforeSources: break; case kCFRunLoopBeforeWaiting: break; case kCFRunLoopAfterWaiting: break; case kCFRunLoopExit: break; case kCFRunLoopAllActivities: break; default: break; } }); // 第一個(gè)參數(shù):要監(jiān)聽(tīng)哪個(gè)Loop // 第二個(gè)參數(shù):監(jiān)聽(tīng)者 // 第三個(gè)參數(shù): 要監(jiān)聽(tīng)runloop在哪種運(yùn)行模式下的狀態(tài) CFRunLoopAddObserver(CFRunLoopGetCurrent(), obser, kCFRunLoopDefaultMode);
- CFRunLoopTimerRef
- 創(chuàng)建定時(shí)器
NSTimer *time =[NSTimer timerWithTimeInterval:2.0 target:self selector:@selector(test) userInfo:nil repeats:YES]; //設(shè)置runloop,添加到默認(rèn)模式下 [[NSRunLoop currentRunLoop] addTimer:time forMode:NSDefaultRunLoopMode]; //設(shè)置runloop,添加到拖拽模式下 [[NSRunLoop currentRunLoop] addTimer:time forMode:UITrackingRunLoopMode]; //NSRunLoopCommonModes標(biāo)記,在兩種模式下都會(huì)運(yùn)行 [[NSRunLoop currentRunLoop] addTimer:time forMode:NSRunLoopCommonModes];
- 創(chuàng)建定時(shí)器
RunLoop運(yùn)行邏輯
- 具體的順序如下:
- 1.通知觀察者RunLoop已經(jīng)啟動(dòng)
- 2.通知觀察者即將要開(kāi)始的定時(shí)器
- 3.通知觀察者任何即將啟動(dòng)的非基于端口的源
- 4.啟動(dòng)任何準(zhǔn)備好的非基于端口的源
- 5.如果基于端口的源準(zhǔn)備好并處于等待狀態(tài)倘是,立即啟動(dòng);并進(jìn)入步驟9
- 6.通知觀察者線程進(jìn)入休眠狀態(tài)
- 7.將線程置于休眠知道任一下面的事件發(fā)生:
- 某一事件到達(dá)基于端口的源
- 定時(shí)器啟動(dòng)
- RunLoop設(shè)置的時(shí)間已經(jīng)超時(shí)
- RunLoop被顯示喚醒
- 8.通知觀察者線程將被喚醒
- 9.處理未處理的事件
- 如果用戶定義的定時(shí)器啟動(dòng)袭艺,處理定時(shí)器事件并重啟RunLoop搀崭。進(jìn)入步驟2
- 如果輸入源啟動(dòng),傳遞相應(yīng)的消息
- 如果RunLoop被顯示喚醒而且時(shí)間還沒(méi)超時(shí)猾编,重啟RunLoop瘤睹。進(jìn)入步驟2
- 10.通知觀察者RunLoop結(jié)束
RunLoop運(yùn)用場(chǎng)景詳情
- 4.1 NSTimer的使用
NSTimer的使用方法在講解CFRunLoopTimerRef類的時(shí)候詳細(xì)講解過(guò),具體參考上邊 2.3 CFRunLoopTimerRef答倡。
- 4.2 ImageView推遲顯示
有時(shí)候轰传,我們會(huì)遇到這種情況:
當(dāng)界面中含有UITableView,而且每個(gè)UITableViewCell里邊都有圖片瘪撇。這時(shí)候當(dāng)我們滾動(dòng)UITableView的時(shí)候获茬,如果有一堆的圖片需要顯示,那么可能會(huì)出現(xiàn)卡頓的現(xiàn)象倔既。
怎么解決這個(gè)問(wèn)題呢恕曲?
這時(shí)候,我們應(yīng)該推遲圖片的顯示渤涌,也就是ImageView推遲顯示圖片佩谣。有兩種方法:
* 1. 監(jiān)聽(tīng)UIScrollView的滾動(dòng)
因?yàn)閁ITableView繼承自UIScrollView,所以我們可以通過(guò)監(jiān)聽(tīng)UIScrollView的滾動(dòng)实蓬,實(shí)現(xiàn)UIScrollView相關(guān)delegate即可稿存。
* 2. 利用PerformSelector設(shè)置當(dāng)前線程的RunLoop的運(yùn)行模式
利用performSelector方法為UIImageView調(diào)用setImage:方法,并利用inModes將其設(shè)置為RunLoop下NSDefaultRunLoopMode運(yùn)行模式瞳秽。代碼如下:
- 4.3 后臺(tái)常駐線程(很常用)
- (void)viewDidLoad { [super viewDidLoad]; // 創(chuàng)建線程,并調(diào)用run1方法執(zhí)行任務(wù) self.thread = [[NSThread alloc] initWithTarget:self selector:@selector(run1) object:nil]; // 開(kāi)啟線程 [self.thread start]; } - (void) run1 { // 這里寫(xiě)任務(wù) NSLog(@"----run1-----"); // 添加下邊兩句代碼率翅,就可以開(kāi)啟RunLoop练俐,之后self.thread就變成了常駐線程,可隨時(shí)添加任務(wù)冕臭,并交于RunLoop處理 [[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode]; [[NSRunLoop currentRunLoop] run]; // 測(cè)試是否開(kāi)啟了RunLoop腺晾,如果開(kāi)啟RunLoop燕锥,則來(lái)不了這里,因?yàn)镽unLoop開(kāi)啟了循環(huán)悯蝉。 NSLog(@"未開(kāi)啟RunLoop"); } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { // 利用performSelector归形,在self.thread的線程中調(diào)用run2方法執(zhí)行任務(wù) [self performSelector:@selector(run2) onThread:self.thread withObject:nil waitUntilDone:NO]; } - (void) run2 { NSLog(@"----run2------"); }
- 4.3 封裝常駐線程
//MJPermenantThread.h typedef void (^MJPermenantThreadTask)(void); @interface MJPermenantThread : NSObject /** 開(kāi)啟線程 */ //- (void)run; /** 在當(dāng)前子線程執(zhí)行一個(gè)任務(wù) */ - (void)executeTask:(MJPermenantThreadTask)task; /** 結(jié)束線程 */ - (void)stop; @end
//MJPermenantThread.m /** MJThread **/ @interface MJThread : NSThread @end @implementation MJThread - (void)dealloc { NSLog(@"%s", __func__); } @end /** MJPermenantThread **/ @interface MJPermenantThread() @property (strong, nonatomic) MJThread *innerThread; @property (assign, nonatomic, getter=isStopped) BOOL stopped; @end @implementation MJPermenantThread #pragma mark - public methods - (instancetype)init { if (self = [super init]) { self.stopped = NO; __weak typeof(self) weakSelf = self; self.innerThread = [[MJThread alloc] initWithBlock:^{ [[NSRunLoop currentRunLoop] addPort:[[NSPort alloc] init] forMode:NSDefaultRunLoopMode]; while (weakSelf && !weakSelf.isStopped) { [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]]; } }]; [self.innerThread start]; } return self; } //- (void)run //{ // if (!self.innerThread) return; // // [self.innerThread start]; //} - (void)executeTask:(MJPermenantThreadTask)task { if (!self.innerThread || !task) return; [self performSelector:@selector(__executeTask:) onThread:self.innerThread withObject:task waitUntilDone:NO]; } - (void)stop { if (!self.innerThread) return; [self performSelector:@selector(__stop) onThread:self.innerThread withObject:nil waitUntilDone:YES]; } - (void)dealloc { NSLog(@"%s", __func__); [self stop]; } #pragma mark - private methods - (void)__stop { self.stopped = YES; CFRunLoopStop(CFRunLoopGetCurrent()); self.innerThread = nil; } - (void)__executeTask:(MJPermenantThreadTask)task { task(); } @end
//調(diào)用 (void)viewDidLoad { [super viewDidLoad]; self.thread = [[MJPermenantThread alloc] init]; } - (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event { [self.thread executeTask:^{ NSLog(@"執(zhí)行任務(wù) - %@", [NSThread currentThread]); }]; } - (IBAction)stop { [self.thread stop]; } - (void)dealloc { NSLog(@"%s", __func__); }
-
RunLoop休眠實(shí)現(xiàn)原理