? 前言:一般代碼運(yùn)行完就結(jié)束了洋幻,為何APP就一直能保持運(yùn)行狀態(tài)呢讲竿?這個(gè)秘訣就是RunLoop,本文先介紹了RunLoop的概念庄涡,引出它的作用量承,并在CoreFoundation框架源碼查看它的底層結(jié)構(gòu);然后介紹RunLoop和線程的關(guān)系穴店,并介紹它運(yùn)行的各種模式宴合;最后介紹RunLoop在實(shí)際項(xiàng)目中的應(yīng)用,比如解決NSTimer在滑動(dòng)時(shí)停止工作的問(wèn)題迹鹅,線程必郧ⅲ活和性能優(yōu)化等問(wèn)題。
一斜棚、RunLoop概念
1阀蒂、RunLoop概念:
? 顧名思義,運(yùn)行循環(huán)弟蚀,在程序運(yùn)行過(guò)程中循環(huán)做一些事情蚤霞。應(yīng)用范疇有:定時(shí)器(Timer)、PerformSelector义钉、GCD昧绣、事件響應(yīng)、手勢(shì)識(shí)別捶闸、界面刷新夜畴、網(wǎng)絡(luò)請(qǐng)求拖刃、AutoreleasePool等。
2贪绘、RunLoop作用:
? 如果沒(méi)有RunLoop兑牡,運(yùn)行代碼
int main(int argc, char * argv[]) {
@autoreleasepool {
// return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
NSLog(@"Hello world!");
}
return 0;
}
程序運(yùn)行完就結(jié)束了,App也就退出了税灌,所以RunLoop作用有以下幾條:
? 1)保持程序的持續(xù)運(yùn)行均函;
? 2)處理App中的各種事件(比如觸摸事件、定時(shí)器事件等)菱涤;
? 3)節(jié)省CPU資源苞也,該做事時(shí)做事,該休息時(shí)休息等粘秆;
3如迟、RunLoop構(gòu)成:
? 下載CoreFoundation框架源碼, 在CFRunLoop.c文件中找到RunLoop的底層結(jié)構(gòu):
struct __CFRunLoop {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* locked for accessing mode list 線程鎖 */
__CFPort _wakeUpPort; // used for CFRunLoopWakeUp 內(nèi)核向該端口發(fā)送消息可以喚醒runloop
Boolean _unused;
volatile _per_run_data *_perRunData; // reset for runs of the run loop
pthread_t _pthread; //RunLoop對(duì)應(yīng)的線程
uint32_t _winthread;
CFMutableSetRef _commonModes; //記錄所有標(biāo)記為common的mode
CFMutableSetRef _commonModeItems; //存儲(chǔ)所有commonMode的item(source、timer翻擒、observer)
CFRunLoopModeRef _currentMode; //當(dāng)前運(yùn)行的mode
CFMutableSetRef _modes; //存儲(chǔ)的是CFRunLoopModeRef
struct _block_item *_blocks_head;
struct _block_item *_blocks_tail;
CFAbsoluteTime _runTime;
CFAbsoluteTime _sleepTime;
CFTypeRef _counterpart;
};
可見(jiàn)氓涣,RunLoop底層是一個(gè)結(jié)構(gòu)體牛哺,主要包含了一個(gè)線程陋气,當(dāng)前運(yùn)行的Mode,若干個(gè)commonMode引润,若干個(gè)commonModeItem等巩趁。
二、RunLoop和線程
? 程序運(yùn)行的時(shí)候需要一個(gè)常駐線程淳附,可以讓線程接收到事件的時(shí)候干活议慰,沒(méi)事的時(shí)候休眠。我們執(zhí)行下面的偽代碼奴曙,一直等待消息别凹,線程就不會(huì)退出了。
do {
// 睡眠中等待消息
// 接收消息
// 處理消息
} while (消息 洽糟!= 退出)
那么RunLoop和線程的關(guān)系是怎樣呢炉菲?
? 1)每條線程都有唯一的一個(gè)與之對(duì)應(yīng)的RunLoop對(duì)象;
? 2)RunLoop保存在一個(gè)全局的Dictionary里坤溃,線程作為key拍霜,RunLoop作為value;
? 3)線程剛創(chuàng)建時(shí)并沒(méi)有RunLoop對(duì)象薪介,RunLoop會(huì)在第一次獲取它時(shí)創(chuàng)建祠饺;
? 4)RunLoop會(huì)在線程結(jié)束時(shí)銷毀;
? 5)主線程的RunLoop已經(jīng)自動(dòng)獲戎(創(chuàng)建)道偷,子線程默認(rèn)沒(méi)有開(kāi)啟RunLoop缀旁;
在RunLoop官方文檔中,可以看到RunLoop和線程的關(guān)系
? 圖中展現(xiàn)了RunLoop 在線程中的作用:從 input sources 和 timer sources 接受事件试疙,然后在線程中處理事件诵棵。
三、RunLoop對(duì)象
1祝旷、獲取RunLoop對(duì)象:
? iOS中有兩套來(lái)訪問(wèn)和使用RunLoop履澳。
? 1)Foundation框架:NSRunLoop,NSRunLoop是基于CFRunLoopRef的OC的封裝怀跛;
[NSRunLoop currentRunLoop]; // 獲得當(dāng)前線程的RunLoop對(duì)象
[NSRunLoop mainRunLoop]; // 獲得主線程的RunLoop對(duì)象
? 2)CoreFoundation框架:CFRunLoopRef距贷;
CFRunLoopGetCurrent(); // 獲得當(dāng)前線程的RunLoop對(duì)象
CFRunLoopGetMain(); // 獲得主線程的RunLoop對(duì)象
2、CFRunLoopModeRef-運(yùn)行模式:
? Mode模式可以看做事件的管家吻谋,一個(gè)Mode管理著各種事件忠蝗,在CFRunLoop.c文件中結(jié)構(gòu):
typedef struct __CFRunLoopMode *CFRunLoopModeRef;
struct __CFRunLoopMode {
CFRuntimeBase _base;
pthread_mutex_t _lock; /* must have the run loop locked before locking this */
CFStringRef _name; //mode名稱
Boolean _stopped; //mode是否被終止
char _padding[3];
CFMutableSetRef _sources0; //sources0 觸摸事件處理,performSelector:onThread:
CFMutableSetRef _sources1; //sources1 基于port的線程通信漓拾,系統(tǒng)事件捕捉
CFMutableArrayRef _observers; //觀察者 用于監(jiān)聽(tīng)RunLoop狀態(tài)阁最,UI刷新,AutoreleasePool
CFMutableArrayRef _timers; //定時(shí)器
CFMutableDictionaryRef _portToV1SourceMap;
__CFPortSet _portSet; //端口
CFIndex _observerMask;
#if USE_DISPATCH_SOURCE_FOR_TIMERS
dispatch_source_t _timerSource;
dispatch_queue_t _queue;
Boolean _timerFired; // set to true by the source when a timer has fired
Boolean _dispatchTimerArmed;
#endif
#if USE_MK_TIMER_TOO
mach_port_t _timerPort;
Boolean _mkTimerArmed;
#endif
#if DEPLOYMENT_TARGET_WINDOWS
DWORD _msgQMask;
void (*_msgPump)(void);
#endif
uint64_t _timerSoftDeadline; /* TSR */
uint64_t _timerHardDeadline; /* TSR */
};
一個(gè)CFRunLoopMode對(duì)象有一個(gè)name骇两,若干個(gè)sources0速种、sources1、observers低千、timers和ports配阵,可見(jiàn)事件都是由Mode在管理,而RunLoop管理Mode示血。RunLoop運(yùn)行時(shí)總是指定一種Mode棋傍,就是currentMode,當(dāng)切換Mode時(shí)必須退出當(dāng)前Mode难审,重新進(jìn)入RunLoop瘫拣。注意:實(shí)際開(kāi)發(fā)中有三個(gè)使用
? 1)NSDefaultRunLoopMode:這個(gè)是默認(rèn)模式,使用最多告喊;
? 2)UITrackingRunLoopMode:界面追蹤Mode麸拄,用于ScrollView追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響葱绒;
? 3)NSRunLoopCommonModes:這個(gè)不是一種模式感帅,默認(rèn)包括NSDefaultRunLoopMode和UITrackingRunLoopMode;
比如常用定時(shí)器代碼:
- (void)viewDidLoad {
[super viewDidLoad];
// 創(chuàng)建tableView
[self tableView];
// 創(chuàng)建一個(gè)定時(shí)器地淀,并把它放在當(dāng)前的RunLoop上失球,模式是NSDefaultRunLoopMode
// self.timer = [NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
// 創(chuàng)建一個(gè)定時(shí)器,但是必須手動(dòng)把它添加到RunLoop中,添加的時(shí)候可以指定Mode
self.timer = [NSTimer timerWithTimeInterval:1 target:self selector:@selector(timerAction:) userInfo:nil repeats:YES];
// 默認(rèn)模式实苞,運(yùn)行執(zhí)行timerAction豺撑,當(dāng)滑動(dòng)tableView時(shí),timerAction就不會(huì)執(zhí)行
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
// 通用模式組合黔牵,運(yùn)行執(zhí)行timerAction聪轿,當(dāng)滑動(dòng)tableView時(shí),timerAction依然繼續(xù)執(zhí)行
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSRunLoopCommonModes];
// UI追蹤模式猾浦,此時(shí)默認(rèn)不執(zhí)行timerAction陆错,當(dāng)滑動(dòng)tableView時(shí),才會(huì)執(zhí)行timerAction
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:UITrackingRunLoopMode];
}
四金赦、RunLoop運(yùn)行邏輯
1音瓷、RunLoop運(yùn)行邏輯
在CFRunLoop.c文件中可以找到CFRunLoopRun()、CFRunLoopRunInMode()夹抗、CFRunLoopRunSpecific等函數(shù)绳慎。
// 用DefaultMode啟動(dòng)
void CFRunLoopRun(void) { /* DOES CALLOUT */
int32_t result;
do {
result = CFRunLoopRunSpecific(CFRunLoopGetCurrent(), kCFRunLoopDefaultMode, 1.0e10, false);
CHECK_FOR_FORK();
} while (kCFRunLoopRunStopped != result && kCFRunLoopRunFinished != result);
}
可以看到,實(shí)際上 RunLoop 就是這樣一個(gè)函數(shù)漠烧,其內(nèi)部是一個(gè) do-while 循環(huán)杏愤。當(dāng)你調(diào)用 CFRunLoopRun() 時(shí),線程就會(huì)一直停留在這個(gè)循環(huán)里已脓;直到超時(shí)或被手動(dòng)停止珊楼,該函數(shù)才會(huì)返回。
如上圖所示摆舟,RunLoop大概運(yùn)行邏輯是:
1亥曹、通知Observers:即將進(jìn)入Loop邓了,kCFRunLoopEntry恨诱;
2、通知Observers:即將處理Timers骗炉,kCFRunLoopBeforeTimers照宝;
3、通知Observers:即將處理Sources句葵,kCFRunLoopBeforeSources厕鹃;
4、處理blocks和source0乍丈,_CFRunLoopDoBlocks和__CFRunLoopDoSources0剂碴;
5、如果有source1轻专,跳轉(zhuǎn)到第9步忆矛;
6、通知Observer:線程即將休眠,kCFRunLoopBeforeWaiting催训;
7洽议、休眠,等待喚醒漫拭,等待Timer或者source1事件或者被手動(dòng)喚醒亚兄,__CFRunLoopServiceMachPort;
8采驻、通知Observer:線程剛被喚醒审胚,kCFRunLoopAfterWaiting;
9礼旅、處理喚醒時(shí)收到的消息菲盾,之后跳轉(zhuǎn)到第2步,_ CFRunLoopDoTimers各淀、_CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE和__CFRunLoopDoSource1懒鉴;
10、通知Observer:即將退出Loop碎浇,kCFRunLoopExit临谱;
2、RunLoop狀態(tài)
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0), // 即將進(jìn)入Loop
kCFRunLoopBeforeTimers = (1UL << 1), // 即將處理Timers
kCFRunLoopBeforeSources = (1UL << 2), // 即將處理Sources
kCFRunLoopBeforeWaiting = (1UL << 5), // 即將進(jìn)入休眠
kCFRunLoopAfterWaiting = (1UL << 6), // 剛從休眠中喚醒
kCFRunLoopExit = (1UL << 7), // 即將退出Loop
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
3奴璃、RunLoop各個(gè)事件的作用
1)Source0:
- performSelector:onThread:悉默;
2)Source1:
- 基于Port的線程間通信;
- 系統(tǒng)事件捕捉苟穆,事件響應(yīng)(嚴(yán)格來(lái)說(shuō)抄课,Source0也參與了事件響應(yīng));
3)Timers:
- NSTimer雳旅;
- performSelector:withObject:afterDelay:跟磨;
4)Observers:
- 用于監(jiān)聽(tīng)RunLoop的狀態(tài);
- UI刷新(BeforeWaiting)攒盈;
- Autorelease pool(BeforeWaiting)抵拘;
五、RunLoop在系統(tǒng)的應(yīng)用
通過(guò)打印主線程RunLoop型豁,可以看到僵蛛,系統(tǒng)默認(rèn)注冊(cè)了5個(gè)Mode:
- kCFRunLoopDefaultMode:App的默認(rèn) Mode,通常主線程是在這個(gè) Mode 下運(yùn)行的迎变。
- UITrackingRunLoopMode:界面跟蹤 Mode充尉,用于 ScrollView 追蹤觸摸滑動(dòng),保證界面滑動(dòng)時(shí)不受其他 Mode 影響衣形。
- GSEventReceiveRunLoopMode:接受系統(tǒng)事件的內(nèi)部 Mode驼侠,通常用不到。
- kCFRunLoopCommonModes:這是一個(gè)占位的 Mode,沒(méi)有實(shí)際作用泪电。
- UIInitializationRunLoopMode:剛啟動(dòng) App 時(shí)進(jìn)入的第一個(gè) Mode般妙,啟動(dòng)完成后就不再使用。這個(gè)Mode實(shí)際測(cè)試中沒(méi)找到相速。
1)AutoreleasePool:App啟動(dòng)后碟渺,蘋(píng)果在主線程 RunLoop 里注冊(cè)了兩個(gè) Observer。
第一個(gè) Observer 監(jiān)視的事件是 Entry(即將進(jìn)入Loop)突诬,其回調(diào)內(nèi)會(huì)調(diào)用 _objc_autoreleasePoolPush() 創(chuàng)建自動(dòng)釋放池苫拍。其 order 是-2147483647,優(yōu)先級(jí)最高旺隙,保證創(chuàng)建釋放池發(fā)生在其他所有回調(diào)之前绒极。
第二個(gè) Observer 監(jiān)視了兩個(gè)事件: BeforeWaiting(準(zhǔn)備進(jìn)入休眠) 時(shí)調(diào)用_objc_autoreleasePoolPop() 和 _objc_autoreleasePoolPush() 釋放舊的池并創(chuàng)建新池;Exit(即將退出Loop) 時(shí)調(diào)用 _objc_autoreleasePoolPop() 來(lái)釋放自動(dòng)釋放池蔬捷。這個(gè) Observer 的 order 是 2147483647垄提,優(yōu)先級(jí)最低,保證其釋放池子發(fā)生在其他所有回調(diào)之后周拐。
在主線程執(zhí)行的代碼铡俐,通常是寫(xiě)在諸如事件回調(diào)、Timer回調(diào)內(nèi)的妥粟。這些回調(diào)會(huì)被 RunLoop 創(chuàng)建好的 AutoreleasePool 環(huán)繞著审丘,所以不會(huì)出現(xiàn)內(nèi)存泄漏,開(kāi)發(fā)者也不必顯示創(chuàng)建 Pool 了勾给。
2)事件響應(yīng):蘋(píng)果注冊(cè)了一個(gè) Source1 (基于 mach port 的) 用來(lái)接收系統(tǒng)事件滩报,其回調(diào)函數(shù)為 __IOHIDEventSystemClientQueueCallback()。
當(dāng)一個(gè)硬件事件(觸摸/鎖屏/搖晃等)發(fā)生后播急,首先由 IOKit.framework 生成一個(gè) IOHIDEvent 事件脓钾,Source1 接收IOHIDEvent,之后再回調(diào)__IOHIDEventSystemClientQueueCallback()內(nèi)觸發(fā)的Source0旅择,Source0再觸發(fā)的 _UIApplicationHandleEventQueue()惭笑。
_UIApplicationHandleEventQueue() 會(huì)把 IOHIDEvent 處理并包裝成 UIEvent 進(jìn)行處理或分發(fā)侣姆,其中包括識(shí)別 UIGesture/處理屏幕旋轉(zhuǎn)/發(fā)送給 UIWindow 等生真。通常事件比如 UIButton 點(diǎn)擊、touchesBegin /Move /End /Cancel 事件都是在這個(gè)回調(diào)中完成的捺宗。
3)手勢(shì)識(shí)別:當(dāng)上面的 _UIApplicationHandleEventQueue() 識(shí)別了一個(gè)手勢(shì)時(shí)柱蟀,其首先會(huì)調(diào)用 Cancel 將當(dāng)前的 touchesBegin/Move/End 系列回調(diào)打斷。隨后系統(tǒng)將對(duì)應(yīng)的 UIGestureRecognizer 標(biāo)記為待處理蚜厉。
蘋(píng)果注冊(cè)了一個(gè) Observer 監(jiān)測(cè) BeforeWaiting (Loop即將進(jìn)入休眠) 事件,這個(gè)Observer的回調(diào)函數(shù)是 _UIGestureRecognizerUpdateObserver(),其內(nèi)部會(huì)獲取所有剛被標(biāo)記為待處理的 GestureRecognizer吏口,并執(zhí)行GestureRecognizer的回調(diào)。當(dāng)有 UIGestureRecognizer 的變化(創(chuàng)建/銷毀/狀態(tài)改變)時(shí)康聂,這個(gè)回調(diào)都會(huì)進(jìn)行相應(yīng)處理。
4)界面更新:當(dāng)在操作 UI 時(shí)胞四,比如改變了 Frame恬汁、更新了 UIView/CALayer 的層次時(shí),這個(gè) UIView/CALayer 就被標(biāo)記為待處理辜伟,并被提交到一個(gè)全局的容器去氓侧。
蘋(píng)果注冊(cè)了一個(gè) Observer 監(jiān)聽(tīng) BeforeWaiting(即將進(jìn)入休眠) 和 Exit (即將退出Loop) 事件,回調(diào)去執(zhí)行一個(gè)很長(zhǎng)的函數(shù)导狡,這個(gè)函數(shù)里會(huì)遍歷所有待處理的 UIView/CAlayer 以執(zhí)行實(shí)際的繪制和調(diào)整约巷,并更新 UI 界面。
5)NSTimer旱捧、CADisplayLink:都使用到了RunLoop独郎,并受到RunLoop的影響。想知道更多細(xì)節(jié)枚赡,請(qǐng)移步-NSTimer囚聚、GCD定時(shí)器、CADisplayLink詳細(xì)分析标锄。
6)PerformSelecter:當(dāng)調(diào)用 NSObject 的 performSelecter:afterDelay: 后顽铸,實(shí)際上其內(nèi)部會(huì)創(chuàng)建一個(gè) Timer 并添加到當(dāng)前線程的 RunLoop 中。所以如果當(dāng)前線程沒(méi)有 RunLoop料皇,則這個(gè)方法會(huì)失效谓松。
7)關(guān)于GCD:當(dāng)調(diào)用 dispatch_async(dispatch_get_main_queue(), block) 時(shí),libDispatch 會(huì)向主線程的 RunLoop 發(fā)送消息践剂,RunLoop會(huì)被喚醒鬼譬,并從消息中取得這個(gè) block,并執(zhí)行該block逊脯。但這個(gè)邏輯僅限于 dispatch 到主線程优质,dispatch 到其他線程仍然是由 libDispatch 處理的。
六军洼、RunLoop項(xiàng)目應(yīng)用
1巩螃、解決NSTimer在滑動(dòng)時(shí)停止工作的問(wèn)題
?如上講述,創(chuàng)建timer后匕争,將它放進(jìn)當(dāng)前的RunLoop避乏,Mode格式為NSRunLoopCommonModes,NSRunLoopCommonModes包含NSDefaultRunLoopMode(沒(méi)有觸摸事件)和UITrackingRunLoopMode(有觸摸事件)甘桑,所以不管有沒(méi)有滑動(dòng)事件都會(huì)執(zhí)行timer拍皮。NSTimer有個(gè)屬性tolerance容忍度歹叮,NSTimer依賴于RunLoop,所以執(zhí)行timer的時(shí)間不會(huì)非常準(zhǔn)確铆帽,使用的時(shí)候需要注意這點(diǎn)咆耿。
如果想知道NSTimer、GCD定時(shí)器爹橱、CADisplayLink更加詳細(xì)分析票灰,請(qǐng)移步-NSTimer、GCD定時(shí)器宅荤、CADisplayLink詳細(xì)分析屑迂。
2、控制線程生命周期(線程狈爰活)
? 有時(shí)候我們需要常駐線程來(lái)處理頻繁的事務(wù)惹盼,比如早期的AFNetworking創(chuàng)建一個(gè)常駐線程處理網(wǎng)絡(luò)事務(wù),比如監(jiān)測(cè)網(wǎng)絡(luò)狀態(tài)等惫确。
默認(rèn)情況一個(gè)線程創(chuàng)建出來(lái)手报,運(yùn)行完要做的事情,線程就會(huì)消亡改化。而程序啟動(dòng)的時(shí)候掩蛤,創(chuàng)建的主線程已經(jīng)加入到mainRunLoop中,所以主線程不會(huì)消亡陈肛。
線程弊崮瘢活代碼:
- (void)viewDidLoad {
[super viewDidLoad];
NSLog(@"mainThread = %@", [NSThread mainThread]);
NSThread *thread = [[NSThread alloc] initWithTarget:self selector:@selector(runThread) object:nil];
self.thread = thread;
thread.name = @"KeepThread";
[self.thread start];
}
// 子線程運(yùn)行
- (void)runThread {
NSLog(@"runThread = %@", [NSThread currentThread]);
// 給runloop添加一個(gè)NSPort,就是添加一個(gè)事件源句旱,也可以添加一個(gè)timer阳藻,或者observer,讓runloop不會(huì)掛掉
[[NSRunLoop currentRunLoop] addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];
NSLog(@"-----runThread end-----"); //不會(huì)執(zhí)行
}
// 測(cè)試線程碧溉觯活
- (void)testThread {
NSLog(@"testThread = %@", [NSThread currentThread]);
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
[self performSelector:@selector(testThread) onThread:self.thread withObject:nil waitUntilDone:NO];
}
點(diǎn)擊屏幕觸發(fā)performSelector腥泥,打印結(jié)果:
mainThread = <NSThread: 0x600001488680>{number = 1, name = main} //主線程
runThread = <NSThread: 0x6000014f9100>{number = 5, name = KeepThread} //運(yùn)行子線程
testThread = <NSThread: 0x6000014f9100>{number = 5, name = KeepThread} //子線程保活
3啃匿、性能優(yōu)化
? 1)tableView圖片顯示優(yōu)化:由于圖片渲染到屏幕需要消耗較多資源蛔外,為了提高用戶體驗(yàn),當(dāng)用戶滾動(dòng)Tableview的時(shí)候溯乒,只在后臺(tái)下載圖片夹厌,但是不顯示圖片,當(dāng)用戶停下來(lái)的時(shí)候才顯示圖片橙数。
[self.imageView performSelector:@selector(setImage:) withObject:[UIImage imageNamed:@"imgName"] afterDelay:1.0 inModes:@[NSDefaultRunLoopMode]];
? 2)AsyncDisplayKit:AsyncDisplayKit 是 Facebook 推出的用于保持界面流暢性的框架(最新的改為Texture)尊流,其原理大致如下:
? UI 線程中一旦出現(xiàn)繁重的任務(wù)就會(huì)導(dǎo)致界面卡頓,這類任務(wù)通常分為3類:排版灯帮,繪制崖技,UI對(duì)象操作。排版通常包括計(jì)算視圖大小钟哥、計(jì)算文本高度迎献、重新計(jì)算子式圖的排版等操作。繪制一般有文本繪制 (例如 CoreText)腻贰、圖片繪制 (例如預(yù)先解壓)吁恍、元素繪制 (Quartz)等操作。UI對(duì)象操作通常包括 UIView/CALayer 等 UI 對(duì)象的創(chuàng)建播演、設(shè)置屬性和銷毀冀瓦。
? 其中前兩類操作可以通過(guò)各種方法扔到后臺(tái)線程執(zhí)行,而最后一類操作只能在主線程完成写烤,并且有時(shí)后面的操作需要依賴前面操作的結(jié)果 (例如TextView創(chuàng)建時(shí)可能需要提前計(jì)算出文本的大幸砻觥)。ASDK 所做的洲炊,就是盡量將能放入后臺(tái)的任務(wù)放入后臺(tái)感局,不能的則盡量推遲 (例如視圖的創(chuàng)建、屬性的調(diào)整)暂衡。
? 為此询微,ASDK 創(chuàng)建了一個(gè)名為 ASDisplayNode 的對(duì)象,并在內(nèi)部封裝了 UIView/CALayer狂巢,它具有和 UIView/CALayer 相似的屬性撑毛,例如 frame、backgroundColor等唧领。所有這些屬性都可以在后臺(tái)線程更改代态,開(kāi)發(fā)者可以只通過(guò) Node 來(lái)操作其內(nèi)部的 UIView/CALayer,這樣就可以將排版和繪制放入了后臺(tái)線程疹吃。但是無(wú)論怎么操作蹦疑,這些屬性總需要在某個(gè)時(shí)刻同步到主線程的 UIView/CALayer 去。
? ASDK 仿照QuartzCore/UIKit 框架的模式萨驶,實(shí)現(xiàn)了一套類似的界面更新的機(jī)制:即在主線程的 RunLoop 中添加一個(gè)Observer歉摧,監(jiān)聽(tīng)了 kCFRunLoopBeforeWaiting 和 kCFRunLoopExit 事件,在收到回調(diào)時(shí)腔呜,遍歷所有之前放入隊(duì)列的待處理的任務(wù)叁温,然后一一執(zhí)行。
? 3)第二條是Facebook寫(xiě)好的框架核畴,下面自己手動(dòng)寫(xiě)下計(jì)算cell的預(yù)緩存高度的偽代碼:
- (void)viewDidLoad {
[super viewDidLoad];
CFRunLoopRef currentLoop = CFRunLoopGetCurrent(); //獲取當(dāng)前的RunLoop
CFStringRef mode = kCFRunLoopDefaultMode; //mode
// 創(chuàng)建observer
CFRunLoopObserverRef observer = CFRunLoopObserverCreateWithHandler(kCFAllocatorDefault, kCFRunLoopBeforeWaiting, true, 0, ^(CFRunLoopObserverRef observer, CFRunLoopActivity _) {
// 在RunLoop處于“空閑”狀態(tài)時(shí)進(jìn)行計(jì)算
NSLog(@"Thread = %@", [NSThread currentThread]);
sleep(1);
CFRunLoopRemoveObserver(currentLoop, observer, mode); //移除observer
});
CFRunLoopAddObserver(currentLoop, observer, mode); //添加observer
}
覺(jué)得寫(xiě)的不錯(cuò)膝但,有些啟發(fā)或幫助,點(diǎn)個(gè)贊哦谤草!