前言
使用TABAnimated集成骨架屏的開發(fā)者,大概都知道其原理是基于原視圖映射生成骨架層,在細(xì)節(jié)上不滿意的地方可以通過預(yù)處理回調(diào)進(jìn)行異步調(diào)整。
- 本文內(nèi)容:TABAnimated的緩存策略
- 使用TABAnimated的開發(fā)者,此文檔建議閱讀
TABAnimated緩存的是什么?
緩存的是通過映射機(jī)制生成的骨架屏單元管理對(duì)象TABComponentManager
,
對(duì)該對(duì)象使用一個(gè)plist文件來解釋。同時(shí),通過計(jì)數(shù)的方式,逐漸篩選出該用戶經(jīng)常加載的骨架屏,提高緩存命中率。
TABAnimated緩存功能有什么作用?
- 相同版本的代碼,避開了重復(fù)使用映射機(jī)制
- 避開了映射采用的遞歸操作,降低了CPU峰值,提高系統(tǒng)性能
- 省去部分場景的視圖預(yù)填充成本及其他耗時(shí)操作,穩(wěn)定性得到有效提升
- 預(yù)處理回調(diào)需要重新調(diào)整CALayer的部分值,有效降低GPU繪制成本
性能評(píng)測(cè)
- 評(píng)測(cè)工具:Instruments
- 評(píng)測(cè)環(huán)境:單線程
- 評(píng)測(cè)機(jī)型:iPhone 6s
分析
其實(shí),從峰值數(shù)據(jù)來看,GPU、CPU的數(shù)值本身并不高,緩存功能也并沒有將他們的峰值降低很多,但是我為什么還要引入緩存策略呢?
答:
- 緩存策略更多地是優(yōu)化了映射機(jī)制的耗時(shí)。對(duì)于CPU性能不高的設(shè)備,在極端的情況下,不能在下一次Vsyc信號(hào)來臨時(shí),將骨架屏的數(shù)據(jù)計(jì)算好并交給GPU處理,此時(shí)造成掉幀情況。
當(dāng)然,掉幀是非常極端的情況。
例如:iPhone6 + 自適應(yīng)高度 + 不規(guī)范的頁面布局
- 映射機(jī)制,是基于當(dāng)前開發(fā)好的視圖。如果我的App打包上線了(除了熱更新),那么此時(shí)骨架屏的映射結(jié)果也已經(jīng)確定了,那么我還有必要每次都去映射一遍嗎?
答案顯然是不需要的。
TABAnimated采取的是:把映射結(jié)果存儲(chǔ)在本地plist文件(大小約4kb)。唯一注意的是,TABAnimated會(huì)讀取你的App版本,當(dāng)你的App版本發(fā)生了變動(dòng),會(huì)重新映射一次。
正文目錄
- 集成注意
- 緩存流程
- 存儲(chǔ)結(jié)構(gòu)
- 線程處理
- 特殊場景
一、集成注意
考慮到有些用戶主要關(guān)注對(duì)后續(xù)使用會(huì)有什么影響,所以該點(diǎn)放到第一位。
集成只強(qiáng)調(diào)一點(diǎn)?。?!也是最重要的一點(diǎn),反復(fù)強(qiáng)調(diào)?。?!
TABAnimated新增closeCache
屬性。
- debug 環(huán)境下,默認(rèn)關(guān)閉緩存功能(為了方便通過預(yù)處理回調(diào)調(diào)試)
- release 環(huán)境下,默認(rèn)開啟緩存功能
- 如果你想在 debug 環(huán)境下測(cè)試緩存功能,可以強(qiáng)制置為NO。但是這個(gè)時(shí)候請(qǐng)注意,預(yù)處理回調(diào)再做修改,無效!
- 如果你始終都不想使用緩存功能,可以強(qiáng)制置為YES
二、緩存流程
下面是流程圖中相關(guān)說明:
1. 全局字典
App在啟動(dòng)時(shí)(圖左側(cè)),會(huì)預(yù)讀取對(duì)于該用戶來說加載次數(shù)最多的一部分?jǐn)?shù)據(jù)到全局字典。
全局字典內(nèi)容:key為plist文件名,value為解釋TABAnimatedManager
對(duì)象的plist文件內(nèi)容
2. plist文件名
plist文件名用于唯一標(biāo)識(shí)骨架屏管理對(duì)象。
起初,僅根據(jù)className唯一標(biāo)識(shí)。但是有些class會(huì)在多個(gè)地方,不同
adjustBlock
中出現(xiàn),即這種方式無法唯一定位某個(gè)骨架屏視圖。最好的方式是將原視圖的className+預(yù)處理回調(diào)字符串化(學(xué)過java的應(yīng)該都用過toString()吧),但是如果回調(diào)處理的東西過多,會(huì)浪費(fèi)大量資源。
于是,采取的方案是:在啟動(dòng)動(dòng)畫后,獲取當(dāng)前控制視圖(control view)的
UIViewController
的className,
將其合并。
3. 描述"加載次數(shù)最多"、loadCount?
為了描述加載次數(shù)最多
,引入了TABAnimatedCacheModel
,
TABAnimatedCacheModel
同樣是用一個(gè)plist文件解釋。與TABAnimatedManager
同名
- 一個(gè)描述
TABAnimatedCacheModel
的plist文件,大小約為300bytes - 一個(gè)描述
TABAnimatedManager
的plist文件,大小約為3kb
loadCount字段作用:
App啟動(dòng)后,讀取沙盒中所有的TABCacheModel
文件,根據(jù)loadCount
降序排列TABCacheModel
數(shù)組,并加載數(shù)組中前n個(gè)TABComponentManager
到內(nèi)存中,存儲(chǔ)方式是全局字典。(n默認(rèn)為20)
loadCount更新機(jī)制:
啟動(dòng)動(dòng)畫后,在下一次runloop執(zhí)行時(shí),放到串行隊(duì)列中。
4. 為什么要通過TABAnimatedCacheModel
計(jì)數(shù),而不是TABAnimatedManager
自計(jì)數(shù)?
一個(gè)用于解釋TABAnimatedManager
的plist文件大概是4kb,如果僅僅為了更新一個(gè)字段頻繁寫入,很明顯浪費(fèi)資源。
而TABAnimatedCacheModel
僅需要300bytes。
5. 版本控制
如果開發(fā)者在待發(fā)布的版本,對(duì)某個(gè)已經(jīng)在沙盒中存在的plist文件,其對(duì)應(yīng)視圖和預(yù)處理回調(diào)做了修改,此時(shí)需要重新寫入。所以在讀取緩存對(duì)象前,需要進(jìn)行版本校對(duì),如果不一致,需要重新使用映射機(jī)制。
太長不看???????
三、存儲(chǔ)結(jié)構(gòu)
- 默認(rèn)生成文件夾
TABAnimated
- 默認(rèn)在
TABAnimated
文件夾里生成2個(gè)文件夾cacheModel
和cacheManager
-
cacheModel
用于存儲(chǔ)TABAnimatedCacheModel
對(duì)象,該對(duì)象只有2個(gè)字段,一個(gè)用于索引cacheManager
,一個(gè)用于描述cacheManager
的加載次數(shù),即loadCount
-
cacheManager
存儲(chǔ)的就是能夠解釋骨架屏的骨架管理對(duì)象
存儲(chǔ)路徑:沙盒根目錄/Document/TABAnimated/
四、線程處理
- 默認(rèn)創(chuàng)建一個(gè)串行隊(duì)列,調(diào)度plist文件寫入、更新任務(wù)。
- 默認(rèn)開辟一個(gè)常駐的子線程。該線程主要負(fù)責(zé)執(zhí)行plist文件寫入、更新任務(wù)。通過添加
NSMachPort
端口保證該線程的runLoop不會(huì)退出。 - 當(dāng)加載了新的(沙盒中沒有的)骨架對(duì)象,該骨架對(duì)象會(huì)立即寫入全局字典(即內(nèi)存中),但是并不急著寫入沙盒,會(huì)將該任務(wù)交由已經(jīng)創(chuàng)建好的串行隊(duì)列調(diào)度。
五、特殊場景
如果你的列表數(shù)據(jù),需要針對(duì)每一個(gè)row做特殊處理。
框架內(nèi)部重寫了getter
方法,檢測(cè)到這種情況后,每次都會(huì)強(qiáng)制使用映射機(jī)制。
當(dāng)然后續(xù)會(huì)繼續(xù)探索更好的處理方式,目前先這樣處理,不會(huì)存在太大的問題~