對于列表某筐,流暢性是衡量性能的一個重要指標(biāo)。如果cell中要加載多張大圖啥酱,比如每張圖超過2~3M爹凹,那么在滑動的時候系統(tǒng)一邊要處理滑動,一邊又要渲染這么大的圖片多張镶殷,肯定會卡禾酱,這是毫無疑問的。
那么這種卡頓現(xiàn)象是怎么造成的呢绘趋?這不是異步加載的問題颤陶,通俗一點說是主線程更新UI的問題。
我們知道每個線程都有一個RunLoop陷遮,主線程的RunLoop是系統(tǒng)默認(rèn)開啟的滓走。而RunLoop在不處理事件的時候就會休眠。那就引出一個概念帽馋,我個人稱之為 RunLoop的“工作周期”(類比控制器的生命周期)搅方,各種狀態(tài)如下:
【
/* Run Loop Observer Activities */
typedef CF_OPTIONS(CFOptionFlags, CFRunLoopActivity) {
kCFRunLoopEntry = (1UL << 0),
kCFRunLoopBeforeTimers = (1UL << 1),
kCFRunLoopBeforeSources = (1UL << 2),
kCFRunLoopBeforeWaiting = (1UL << 5), //即將休眠
kCFRunLoopAfterWaiting = (1UL << 6),
kCFRunLoopExit = (1UL << 7),
kCFRunLoopAllActivities = 0x0FFFFFFFU
};
】
這里有個狀態(tài)是 kCFRunLoopBeforeWaiting(即將休眠)比吭,我們設(shè)置一個 timer 每隔 0.01s 響應(yīng)一次,并添加到 RunLoop 的默認(rèn)模式中姨涡,那么時鐘在列表滑動的UI事件結(jié)束之后才會有效衩藤,對應(yīng)本例中就是列表滑動結(jié)束之后時鐘跳動一次即將結(jié)束的時候,這個時候再讓 RunLoop 來做加載圖片涛漂,就不會跟滑動沖突了赏表,也就不會造成列表卡頓了。(注意:列表滑動的時候是不加載圖片的匈仗。)
這就是本文討論的“延遲加載圖片”瓢剿。
怎么知道什么時候是 kCFRunLoopBeforeWaiting 狀態(tài)呢?在CFRunLoop中锚沸,有個監(jiān)聽者,我們創(chuàng)建這個監(jiān)聽者通過函數(shù)回調(diào)(C語言中回調(diào)一般都是函數(shù)指針)來監(jiān)聽狀態(tài)處理任務(wù)涕癣。
代碼如下:
這里理一下思路:
背景環(huán)境:控制器中有一個tableView哗蜈,有幾百個cell,每個cell中有3個imageView坠韩,每個imageView要加載 3M 的大圖片距潘。
在cellForRow的數(shù)據(jù)源方法中不直接給cell.imageView設(shè)置圖片,而是利用runloop的 kCFRunLoopBeforeWaiting 狀態(tài)的回調(diào)函數(shù)執(zhí)行設(shè)置圖片的任務(wù)只搁。
在viewDidLoad中添加監(jiān)聽者監(jiān)聽runloop的狀態(tài)音比;
設(shè)置時鐘,添加到runloop的 “默認(rèn)模式” 執(zhí)行氢惋,一定要默認(rèn)模式才是本文思路的關(guān)鍵洞翩;
-
設(shè)置一個屬性--存放任務(wù)的數(shù)組tasks,在cellForRow中焰望,把給imageView設(shè)置圖片的代碼作為任務(wù)用block包裝骚亿,將這個block任務(wù)存放到tasks數(shù)組中;
偽代碼如下:
-
回調(diào)函數(shù)Callback 中(回調(diào)函數(shù)是C語言的函數(shù))熊赖,從tasks數(shù)組中取出任務(wù)并執(zhí)行来屠,隨即更新tasks。
這樣震鹉,就達(dá)到了延遲執(zhí)行的效果俱笛。
需要的注意的是,這樣子會造成內(nèi)存瘋長传趾,處理方法是在cellForRow的數(shù)據(jù)源方法中迎膜,移除cell.contentView中的子控件,這里其實就是3個imageView浆兰,在 添加任務(wù)的block任務(wù)中星虹,再給 contentView 動態(tài)添加 3 個 imageView零抬,用CPU的性能換來內(nèi)存空間,就不會使內(nèi)存瘋長了宽涌。