YYWebImage:
1恶复、相比SDWebImage播放gif圖片時(shí)內(nèi)存占用大大降低。
SDWebImage中對(duì)gif的圖片處理是,將gif圖片的每一幀存放到數(shù)組之中朽色,在賦值給UIImageView對(duì)象播放動(dòng)畫。注意组题,此時(shí)存放在數(shù)組中的UIImage對(duì)象并未解碼占用內(nèi)存空間葫男,而要等到具體在屏幕上播放時(shí)才會(huì)解碼。例如一個(gè)gif圖片有100幀崔列,采用UIImageView播放時(shí)梢褐,可以觀察到隨著播放的進(jìn)行內(nèi)存不斷增加,直至一個(gè)循環(huán)播放完成。因?yàn)閁IImageView強(qiáng)引用數(shù)組對(duì)象盈咳,數(shù)組中的對(duì)象隨著播放的進(jìn)行不斷解碼占用巨大內(nèi)存空間而無(wú)法釋放趣效,容易導(dǎo)致內(nèi)存爆炸。
YYWebImage中播放gif圖片猪贪,采用的是定時(shí)器策略跷敬。YYAnimatedImageView是UIImageView的子類。用字典將gif圖片的每一幀緩存起來(lái)热押,緩存格式為
@{
@1: UIImage,
@2: UIImage.....
}
利用CADisplayLink定時(shí)器不斷改變索引值1西傀、2、3桶癣,從緩存字典中取出對(duì)應(yīng)的UIImage對(duì)象拥褂,賦值給YYAnimatedImageView的layer.contents屬性,從而生成動(dòng)畫牙寞。因?yàn)榫彺孀值鋵?duì)象內(nèi)部的UIImage都沒(méi)有解碼饺鹃,所以沒(méi)有內(nèi)存爆炸問(wèn)題。
以上解釋有誤间雀,以下面為準(zhǔn)
SDWebImage將gif資源中的每一張image寫入到內(nèi)存中悔详,通過(guò)animatedImageWithImages的方式播放動(dòng)畫,這樣做的好處是gif輪播時(shí)直接從內(nèi)存中讀取資源就好惹挟,降低了CPU的使用茄螃,以空間換取流暢度,但是這也會(huì)導(dǎo)致當(dāng)同時(shí)加載的gif數(shù)量增加時(shí)內(nèi)存問(wèn)題暴露的尤其明顯连锯。因?yàn)椴シ艜r(shí)間采用的是總時(shí)間归苍,每張圖片播放的都是平均時(shí)間,播放效果差运怖。
YYWebImage采用NSOperation后臺(tái)線程解壓圖片并緩存拼弃,用定時(shí)器從緩存中讀取圖片實(shí)現(xiàn)gif圖片播放。這必然需要消耗CPU摇展,所以YYWebImage相比較SDWebImage更消耗CPU吻氧。根據(jù)內(nèi)存使用情況控制緩存圖片字典數(shù)據(jù)量控制內(nèi)存,內(nèi)存得到控制吗购。因?yàn)橛枚〞r(shí)器医男,每個(gè)圖片播放時(shí)間基本就是gif中所占用的時(shí)間,播放效果好捻勉。
2镀梭、實(shí)現(xiàn)隔行掃描interlaced,在圖片未下載完成時(shí)顯示更好的效果
在下載圖片時(shí)踱启,首先用 CGImageSourceCreateIncremental(NULL) 創(chuàng)建一個(gè)空的圖片源报账,隨后在獲得新數(shù)據(jù)時(shí)調(diào)用CGImageSourceUpdateData(data, false) 來(lái)更新圖片源研底,最后在用 CGImageSourceCreateImageAtIndex() 創(chuàng)建圖片來(lái)顯示。SDWebImage未采用此方法解壓圖片透罢,圖片下載完成后顯示很突兀榜晦,效果差。
3羽圃、圖片為何在顯示前都要進(jìn)行解碼操作乾胶?
各個(gè)圖片框架在下載完圖片后都會(huì)在后臺(tái)線程進(jìn)行解碼操作,但是不解碼圖片也是能正常顯示的朽寞,原因是系統(tǒng)會(huì)在圖片顯示的時(shí)候自動(dòng)解碼识窿。框架事先在子線程解碼能避免系統(tǒng)自動(dòng)的在主線程解碼操作脑融,由此可能會(huì)導(dǎo)致的主線程堵塞喻频,閃圖等現(xiàn)象。
事先將圖片解碼肘迎,會(huì)導(dǎo)致圖片的內(nèi)存占用大大增加甥温。框架又都會(huì)有內(nèi)存和磁盤緩存妓布,對(duì)大圖片姻蚓,有可能存在內(nèi)存太大,甚至來(lái)不及釋放內(nèi)存導(dǎo)致的閃退現(xiàn)象秋茫,可如下方法解決史简。
- 對(duì)大圖片禁止事先解壓操作,框架都考慮了這一點(diǎn)肛著,有個(gè)屬性設(shè)置為NO即可。
- 圖片內(nèi)存大跟圖片的尺寸有關(guān)跺讯,可以在解壓前先將圖片等比例壓縮枢贿,占用空間會(huì)大大縮小。
- 手動(dòng)清空內(nèi)存緩存刀脏,設(shè)置內(nèi)存緩存最大容量等
YYModel:
1局荚、為何效率很高?
- 因?yàn)槟P偷慕Y(jié)構(gòu)一般是固定的愈污,緩存了運(yùn)行時(shí)獲取模型類以及父類的所有屬性(property耀态、method、setter/getter暂雹、type等)首装。
- 采用枚舉獲取屬性修飾符(strong等)和type(NSArray等),相比NSScanner效率更高杭跪。
- 調(diào)用model的setter為每一個(gè)屬性賦值仙逻,性能比KVC要高驰吓。
- 大量使用CF框架函數(shù),性能更佳系奉。
- 使用dispatch_semaphore_t保證線程安全
YYCache:
1檬贰、內(nèi)存緩存
雙向鏈表和CFMutableDictionaryRef配合存儲(chǔ),前者處理順序缺亮,后者處理查詢翁涤。實(shí)現(xiàn)LRU算法,優(yōu)先淘汰使用時(shí)間最早的節(jié)點(diǎn)萌踱、即鏈表末尾的數(shù)據(jù)迷雪。例如依次有A、B虫蝶、C章咧、D四個(gè)數(shù)據(jù),當(dāng)查詢C的時(shí)候能真,CFMutableDictionaryRef保證最快的查詢速度赁严,鏈表保證最快的移動(dòng)速度將C移動(dòng)至head,兩者配合天衣無(wú)縫粉铐。
當(dāng)內(nèi)存報(bào)警或者用戶退到后臺(tái)疼约,采用子線程釋放對(duì)象技術(shù)清理緩存數(shù)據(jù),該技術(shù)原理如下
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [Person new];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[p class]
});
}
如上蝙泼,正常情況下p對(duì)象會(huì)在主線程釋放程剥,但是若用子線程block捕獲p對(duì)象,則viewDidLoad執(zhí)行完后block為最后一個(gè)擁有p的場(chǎng)所汤踏,p會(huì)在子線程runloop運(yùn)行結(jié)束時(shí)釋放织鲸,達(dá)到了對(duì)象在子線程釋放的目的,避免主線程資源占用溪胶。
2频祝、磁盤緩存
小文件(默認(rèn)是小于20kb)采用sqlite绞吁,大文件采用歸檔后寫文件的方式存儲(chǔ)棕所,效率最高擂啥。值得一提的是對(duì)sqlite語(yǔ)句做了緩存
- (sqlite3_stmt *)_dbPrepareStmt:(NSString *)sql {
if (![self _dbCheck] || sql.length == 0 || !_dbStmtCache) return NULL;
// 從_dbStmtCache取出已緩存的sqlite3_stmt
sqlite3_stmt *stmt = (sqlite3_stmt *)CFDictionaryGetValue(_dbStmtCache, (__bridge const void *)(sql));
if (!stmt) {
// 沒(méi)有緩存再創(chuàng)建
sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
// 緩存
CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);
} else {
sqlite3_reset(stmt);
}
return stmt;
}
sqlite3_stmt: 該對(duì)象的表示已經(jīng)編譯成二進(jìn)制形式并準(zhǔn)備執(zhí)行的單個(gè) SQL 語(yǔ)句。緩存了這個(gè)才避,可以避免sql語(yǔ)句重復(fù)生成該可執(zhí)行語(yǔ)句的開銷橱夭,性能優(yōu)化。
對(duì)大文件桑逝,采用的是文件整體部分用寫文件方式保存棘劣,而配合用sqlite保存該文件的基礎(chǔ)信息,比如文件路徑肢娘、文件大小呈础、保存時(shí)間等舆驶。