iOS RunLoop

? 前言:一般代碼運(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)系

RunLoop運(yù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瘫拣。
RunLoop和Mode關(guān)系

在iOS中五種模式如下圖:
RunLoopMode

注意:實(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)行邏輯圖

如上圖所示摆舟,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è)贊哦谤草!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末跟束,一起剝皮案震驚了整個(gè)濱河市莺奸,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌冀宴,老刑警劉巖灭贷,帶你破解...
    沈念sama閱讀 217,657評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異略贮,居然都是意外死亡甚疟,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,889評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門逃延,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)览妖,“玉大人,你說(shuō)我怎么就攤上這事揽祥》砀啵” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,057評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵盔然,是天一觀的道長(zhǎng)桅打。 經(jīng)常有香客問(wèn)我,道長(zhǎng)愈案,這世上最難降的妖魔是什么挺尾? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,509評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮站绪,結(jié)果婚禮上遭铺,老公的妹妹穿的比我還像新娘。我一直安慰自己恢准,他們只是感情好魂挂,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,562評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著馁筐,像睡著了一般涂召。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上敏沉,一...
    開(kāi)封第一講書(shū)人閱讀 51,443評(píng)論 1 302
  • 那天果正,我揣著相機(jī)與錄音,去河邊找鬼盟迟。 笑死秋泳,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的攒菠。 我是一名探鬼主播迫皱,決...
    沈念sama閱讀 40,251評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼辖众!你這毒婦竟也來(lái)了卓起?” 一聲冷哼從身側(cè)響起和敬,我...
    開(kāi)封第一講書(shū)人閱讀 39,129評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎既绩,沒(méi)想到半個(gè)月后概龄,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體还惠,經(jīng)...
    沈念sama閱讀 45,561評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡饲握,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,779評(píng)論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了蚕键。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片救欧。...
    茶點(diǎn)故事閱讀 39,902評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖锣光,靈堂內(nèi)的尸體忽然破棺而出笆怠,到底是詐尸還是另有隱情,我是刑警寧澤誊爹,帶...
    沈念sama閱讀 35,621評(píng)論 5 345
  • 正文 年R本政府宣布蹬刷,位于F島的核電站,受9級(jí)特大地震影響频丘,放射性物質(zhì)發(fā)生泄漏办成。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,220評(píng)論 3 328
  • 文/蒙蒙 一搂漠、第九天 我趴在偏房一處隱蔽的房頂上張望迂卢。 院中可真熱鬧,春花似錦桐汤、人聲如沸而克。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,838評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)员萍。三九已至,卻和暖如春拣度,著一層夾襖步出監(jiān)牢的瞬間碎绎,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 32,971評(píng)論 1 269
  • 我被黑心中介騙來(lái)泰國(guó)打工蜡娶, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留混卵,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,025評(píng)論 2 370
  • 正文 我出身青樓窖张,卻偏偏與公主長(zhǎng)得像幕随,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子宿接,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,843評(píng)論 2 354

推薦閱讀更多精彩內(nèi)容

  • Runloop是iOS和OSX開(kāi)發(fā)中非匙富矗基礎(chǔ)的一個(gè)概念辕录,從概念開(kāi)始學(xué)習(xí)。 RunLoop的概念 -般說(shuō)梢卸,一個(gè)線程一...
    小貓仔閱讀 995評(píng)論 0 1
  • 概述 RunLoop作為iOS中一個(gè)基礎(chǔ)組件和線程有著千絲萬(wàn)縷的關(guān)系走诞,同時(shí)也是很多常見(jiàn)技術(shù)的幕后功臣。盡管在平時(shí)多...
    sumrain_cloud閱讀 946評(píng)論 0 5
  • RunLoop 蘋(píng)果是如何利用RunLoop實(shí)現(xiàn)自動(dòng)釋放池蛤高、延遲回調(diào)蚣旱、觸摸事件、屏幕刷新等功能的戴陡,今天我們就了解一...
    AKyS佐毅閱讀 810評(píng)論 0 23
  • RunLoop 前言 RunLoop是iOS/OS開(kāi)發(fā)中比較基礎(chǔ)的一個(gè)概念塞绿,在蘋(píng)果開(kāi)發(fā)中用在事件處理,延遲加載恤批,屏...
    etund閱讀 2,839評(píng)論 6 20
  • 一异吻、概述 一般來(lái)說(shuō),一個(gè)線程只能執(zhí)行一個(gè)任務(wù)喜庞,執(zhí)行完就會(huì)退出诀浪,如果我們需要一種機(jī)制,讓線程能隨時(shí)處理時(shí)間但并不退出...
    一直在路上66閱讀 287評(píng)論 0 0