懵逼
主要聊聊以下內(nèi)容
1 Runloop基本概要
2 Runloop與定時(shí)器
3 Runloop常駐線程
4 Runloop性能優(yōu)化
Runloop基本概要
Runloop就是一個(gè)do…while 循環(huán)
functionloop(){initialize();do{varmessage=get_next_message();process_message(message);}while(message!=quit);}
只有主線程默認(rèn)打開(kāi)了Runloop,子線程需要手動(dòng)打開(kāi)
Runloop一共有5個(gè)mode掺逼,其中給我們使用的有三個(gè)
NSDefaultRunLoopMode? 默認(rèn)模式UITrackingRunLoopMode scrollView進(jìn)入拖拽的時(shí)候會(huì)進(jìn)入的模式NSRunLoopCommonModes? 占位模式
內(nèi)部實(shí)現(xiàn)原理(圖片網(wǎng)上找的)
內(nèi)部實(shí)現(xiàn)原理
Runloop會(huì)在一次循環(huán)繪制屏幕上所有的點(diǎn)
Runloop-定時(shí)器
不多說(shuō)感论,直接上代碼肢础,分析問(wèn)題音瓷。
NSTimer*timer=[NSTimer timerWithTimeInterval:1.frepeats:YES block:^(NSTimer*_Nonnull timer){NSLog(@"%s",__func__);}];[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSDefaultRunLoopMode];
小知識(shí):
[NSRunLoopcurrentRunLoop][NSRunLoopmainRunLoop]在主線程中上面獲取的Runloop是同一個(gè)姓迅,在子線程中不同般甲。[NSRunLoopmainRunLoop]表示獲取主線程的Runloop對(duì)象[NSRunLoopcurrentRunLoop]表示獲取當(dāng)前線程的Runloop對(duì)象
運(yùn)行圖:
mode問(wèn)題
通過(guò)上面的圖得知Runloop在scrollView進(jìn)行拖拽的時(shí)候會(huì)進(jìn)入U(xiǎn)ITrackingRunLoopMode模式肋乍,退出拖拽的時(shí)候則會(huì)回到NSDefaultRunLoopMode模式,如何解決上面的問(wèn)題呢敷存?
需要用到NSRunLoopCommonModes占位模式墓造,切換模式后,在看圖锚烦。
NSTimer*timer=[NSTimer timerWithTimeInterval:1.frepeats:YES block:^(NSTimer*_Nonnull timer){staticintcount=0;NSLog(@"%s - %d",__func__,count++);}];[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
運(yùn)行圖:
占位模式
Runloop常駐線程
在項(xiàng)目開(kāi)發(fā)中我們通常把耗時(shí)操作方在子線程中運(yùn)行觅闽,我們就接著上面的那個(gè)代碼中模擬耗時(shí)操作。
NSTimer*timer=[NSTimer timerWithTimeInterval:1.frepeats:YES block:^(NSTimer*_Nonnull timer){staticintcount=0;[NSThread sleepForTimeInterval:1];//休息一秒鐘挽牢,模擬耗時(shí)操作NSLog(@"%s - %d",__func__,count++);}];[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];
這個(gè)時(shí)候我們是沒(méi)有放在子線程里面進(jìn)行處理的谱煤,通過(guò)實(shí)驗(yàn)效果得知,會(huì)卡頓禽拔。
效果圖:
模擬耗時(shí)操作
然后此時(shí)的你刘离,可能就會(huì)寫(xiě)出這樣子的代碼室叉。
dispatch_async(dispatch_get_global_queue(0,0),^{NSTimer*timer=[NSTimer timerWithTimeInterval:1.frepeats:YES block:^(NSTimer*_Nonnull timer){staticintcount=0;[NSThread sleepForTimeInterval:1];//休息一秒鐘,模擬耗時(shí)操作NSLog(@"%s - %d",__func__,count++);}];[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];});
然后你就會(huì)發(fā)現(xiàn)硫惕,啥都沒(méi)有啊茧痕,根本沒(méi)運(yùn)行……
分析問(wèn)題:
當(dāng)進(jìn)入block的時(shí)候,先創(chuàng)建了timer恼除,并且也把timer也把timer加入到runloop中踪旷,但是很重要的一點(diǎn)子線程中Runloop不會(huì)自動(dòng)運(yùn)行,需要手動(dòng)運(yùn)行豁辉,因?yàn)檫@里沒(méi)有運(yùn)行Runloop令野,所以timer就被釋放掉了,所以導(dǎo)致了啥都沒(méi)有徽级。
既然得知原因是沒(méi)有運(yùn)行Runloop气破,那我們自己運(yùn)行不就可以了嗎?
dispatch_async(dispatch_get_global_queue(0,0),^{NSTimer*timer=[NSTimer timerWithTimeInterval:1.frepeats:YES block:^(NSTimer*_Nonnull timer){staticintcount=0;[NSThread sleepForTimeInterval:1];//休息一秒鐘餐抢,模擬耗時(shí)操作NSLog(@"%s - %d",__func__,count++);}];[[NSRunLoop currentRunLoop]addTimer:timer forMode:NSRunLoopCommonModes];//子線程需要手動(dòng)開(kāi)啟Runloop[[NSRunLoop currentRunLoop]run];});
小知識(shí)
[[NSRunLoop currentRunLoop]run];運(yùn)行runloop[[NSRunLoop currentRunLoop]runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];指定runloop在指定模式下现使,設(shè)置開(kāi)始時(shí)間,開(kāi)啟成功會(huì)返回YES[[NSRunLoop currentRunLoop]runUntilDate:[NSDate distantFuture]];運(yùn)行runloop直到一個(gè)時(shí)間
運(yùn)行效果:
runloop-timer4.gif
這樣子我們就可以實(shí)現(xiàn)在子線程處理耗時(shí)操作,并且常駐線程了旷痕。
Runloop性能優(yōu)化
案例:tableView的Cell中有多個(gè)ImageView碳锈,同時(shí)加載大圖,導(dǎo)致UI卡頓欺抗。
解決思路:使用Runloop每次循環(huán)址添加一張圖片售碳。
工具:這里我們需要使用到CFRunloop
實(shí)現(xiàn)過(guò)程:
1、把加載圖片等代碼保存起來(lái)佩迟,先不執(zhí)行 (保存一段代碼团滥,block)
2竿屹、監(jiān)聽(tīng)Runloop循環(huán)(CFRunloopObserver)
3报强、每次都從任務(wù)數(shù)組中取出一個(gè)加載圖片等代碼執(zhí)行(執(zhí)行block代碼)
監(jiān)聽(tīng)Runloop
//添加runloop監(jiān)聽(tīng)者-(void)addRunloopObserver{//? ? 獲取 當(dāng)前的Runloop ref - 指針CFRunLoopRef current=CFRunLoopGetCurrent();//定義一個(gè)RunloopObserverCFRunLoopObserverRef defaultModeObserver;//上下文/*
? ? typedef struct {
? ? ? ? CFIndex version; //版本號(hào) long
? ? ? ? void *? info;? ? //這里我們要填寫(xiě)對(duì)象(self或者傳進(jìn)來(lái)的對(duì)象)
? ? ? ? const void *(*retain)(const void *info);? ? ? ? //填寫(xiě)&CFRetain
? ? ? ? void? ? (*release)(const void *info);? ? ? ? ? //填寫(xiě)&CGFRelease
? ? ? ? CFStringRef (*copyDescription)(const void *info); //NULL
? ? } CFRunLoopObserverContext;
? ? */CFRunLoopObserverContext context={0,(__bridgevoid*)(self),&CFRetain,&CFRelease,NULL};/*
? ? 1 NULL空指針 nil空對(duì)象 這里填寫(xiě)NULL
? ? 2 模式
? ? ? ? kCFRunLoopEntry = (1UL << 0),
? ? ? ? kCFRunLoopBeforeTimers = (1UL << 1),
? ? ? ? kCFRunLoopBeforeSources = (1UL << 2),
? ? ? ? kCFRunLoopBeforeWaiting = (1UL << 5),
? ? ? ? kCFRunLoopAfterWaiting = (1UL << 6),
? ? ? ? kCFRunLoopExit = (1UL << 7),
? ? ? ? kCFRunLoopAllActivities = 0x0FFFFFFFU
? ? 3 是否重復(fù) - YES
? ? 4 nil 或者 NSIntegerMax - 999
? ? 5 回調(diào)
? ? 6 上下文
? ? *///? ? 創(chuàng)建觀察者defaultModeObserver=CFRunLoopObserverCreate(NULL,kCFRunLoopBeforeWaiting,YES,NSIntegerMax-999,&Callback,&context);//添加當(dāng)前runloop的觀察著CFRunLoopAddObserver(current,defaultModeObserver,kCFRunLoopDefaultMode);//釋放CFRelease(defaultModeObserver);}
我們要實(shí)現(xiàn)回調(diào)方法
staticvoidCallback(CFRunLoopObserverRef observer,CFRunLoopActivity activity,void*info){//通過(guò)info橋接為當(dāng)前的對(duì)象PQRunloop*runloop=(__bridge PQRunloop*)info;//如果沒(méi)有任務(wù),就直接返回if(runloop.tasks.count==0){return;}BOOL result=NO;while(result==NO&&runloop.tasks.count){//取出任務(wù)RunloopBlock unit=runloop.tasks.firstObject;//執(zhí)行任務(wù)result=unit();//刪除任務(wù)[runloop.tasks removeObjectAtIndex:0];}}因?yàn)樵贑方法中沒(méi)有辦法調(diào)用OC對(duì)象拱燃,所以context中有一個(gè)void*info秉溉,為的就是解決這個(gè)問(wèn)題,把OC的對(duì)象傳入到回調(diào)方法中碗誉。
通過(guò)上面的兩個(gè)方法我們可以做到監(jiān)聽(tīng)Runloop循環(huán)召嘶,以及每次循環(huán)需要處理的事情,這個(gè)時(shí)候我們只需要對(duì)外提供一個(gè)添加任務(wù)的方法哮缺,用數(shù)組保存起來(lái)弄跌。
//add task 添加任務(wù)-(void)addTask:(RunloopBlock)unit withId:(id)key{//添加任務(wù)到數(shù)組[self.tasks addObject:unit];[self.taskKeys addObject:key];//為了保證加載到圖片最大數(shù)是18所以要?jiǎng)h除if(self.tasks.count>self.maxQueue){[self.tasks removeObjectAtIndex:0];[self.taskKeys removeObjectAtIndex:0];}}
如果你一切就這樣子結(jié)束了,那么你就錯(cuò)了尝苇,Runloop在執(zhí)行后如果沒(méi)有喚醒操作铛只,就會(huì)進(jìn)入睡眠狀態(tài)埠胖,也就是歇菜了,啥都不干了淳玩,所以為了能讓Runloop能一直跑直撤,我們需要?jiǎng)?chuàng)建一個(gè)定時(shí)器,但是最好不要在定時(shí)器中作操作(1 耗時(shí)蜕着,2 耗電)
所以我們還需要:
self.timer=[NSTimer scheduledTimerWithTimeInterval:0.001repeats:YES block:^(NSTimer*_Nonnull timer){}];
小知識(shí)
NULL空指針nil? 空對(duì)象
最后送上運(yùn)行圖:
優(yōu)化后
優(yōu)化后
優(yōu)化前
優(yōu)化前
結(jié)論
通過(guò)上面兩個(gè)很明顯的可以看出谋竖,優(yōu)化后明顯的流暢度得到提高,并且在滑動(dòng)的時(shí)候內(nèi)存會(huì)減承匣,而未優(yōu)化的明顯卡頓蓖乘,內(nèi)存消耗也不會(huì)在滑動(dòng)的時(shí)候得到減少。
如果對(duì)你有一點(diǎn)點(diǎn)幫助韧骗,請(qǐng)用star砸死我驱敲,也歡迎交流。
作為一個(gè)開(kāi)發(fā)者宽闲,有一個(gè)學(xué)習(xí)的氛圍和一個(gè)交流圈子特別重要众眨,這是我的交流群761407670(111),大家有興趣可以進(jìn)群里一起交流學(xué)習(xí)