YYKit
YYWebImage:
1、相比SDWebImage播放gif图片时内存占用大大降低。
SDWebImage中对gif的图片处理是,将gif图片的每一帧存放到数组之中,在赋值给UIImageView对象播放动画。注意,此时存放在数组中的UIImage对象并未解码占用内存空间,而要等到具体在屏幕上播放时才会解码。例如一个gif图片有100帧,采用UIImageView播放时,可以观察到随着播放的进行内存不断增加,直至一个循环播放完成。因为UIImageView强引用数组对象,数组中的对象随着播放的进行不断解码占用巨大内存空间而无法释放,容易导致内存爆炸。
YYWebImage中播放gif图片,采用的是定时器策略。YYAnimatedImageView是UIImageView的子类。用字典将gif图片的每一帧缓存起来,缓存格式为
@{
@1: UIImage,
@2: UIImage.....
}
利用CADisplayLink定时器不断改变索引值1、2、3,从缓存字典中取出对应的UIImage对象,赋值给YYAnimatedImageView的layer.contents属性,从而生成动画。因为缓存字典对象内部的UIImage都没有解码,所以没有内存爆炸问题。
以上解释有误,以下面为准
SDWebImage将gif资源中的每一张image写入到内存中,通过animatedImageWithImages的方式播放动画,这样做的好处是gif轮播时直接从内存中读取资源就好,降低了CPU的使用,以空间换取流畅度,但是这也会导致当同时加载的gif数量增加时内存问题暴露的尤其明显。因为播放时间采用的是总时间,每张图片播放的都是平均时间,播放效果差。
YYWebImage采用NSOperation后台线程解压图片并缓存,用定时器从缓存中读取图片实现gif图片播放。这必然需要消耗CPU,所以YYWebImage相比较SDWebImage更消耗CPU。根据内存使用情况控制缓存图片字典数据量控制内存,内存得到控制。因为用定时器,每个图片播放时间基本就是gif中所占用的时间,播放效果好。
2、实现隔行扫描interlaced,在图片未下载完成时显示更好的效果
在下载图片时,首先用 CGImageSourceCreateIncremental(NULL) 创建一个空的图片源,随后在获得新数据时调用CGImageSourceUpdateData(data, false) 来更新图片源,最后在用 CGImageSourceCreateImageAtIndex() 创建图片来显示。SDWebImage未采用此方法解压图片,图片下载完成后显示很突兀,效果差。
3、图片为何在显示前都要进行解码操作?
各个图片框架在下载完图片后都会在后台线程进行解码操作,但是不解码图片也是能正常显示的,原因是系统会在图片显示的时候自动解码。框架事先在子线程解码能避免系统自动的在主线程解码操作,由此可能会导致的主线程堵塞,闪图等现象。
事先将图片解码,会导致图片的内存占用大大增加。框架又都会有内存和磁盘缓存,对大图片,有可能存在内存太大,甚至来不及释放内存导致的闪退现象,可如下方法解决。
- 对大图片禁止事先解压操作,框架都考虑了这一点,有个属性设置为NO即可。
- 图片内存大跟图片的尺寸有关,可以在解压前先将图片等比例压缩,占用空间会大大缩小。
- 手动清空内存缓存,设置内存缓存最大容量等
YYModel:
1、为何效率很高?
- 因为模型的结构一般是固定的,缓存了运行时获取模型类以及父类的所有属性(property、method、setter/getter、type等)。
- 采用枚举获取属性修饰符(strong等)和type(NSArray等),相比NSScanner效率更高。
- 调用model的setter为每一个属性赋值,性能比KVC要高。
- 大量使用CF框架函数,性能更佳。
- 使用dispatch_semaphore_t保证线程安全
YYCache:
1、内存缓存
双向链表和CFMutableDictionaryRef配合存储,前者处理顺序,后者处理查询。实现LRU算法,优先淘汰使用时间最早的节点、即链表末尾的数据。例如依次有A、B、C、D四个数据,当查询C的时候,CFMutableDictionaryRef保证最快的查询速度,链表保证最快的移动速度将C移动至head,两者配合天衣无缝。
当内存报警或者用户退到后台,采用子线程释放对象技术清理缓存数据,该技术原理如下
- (void)viewDidLoad {
[super viewDidLoad];
Person *p = [Person new];
dispatch_async(dispatch_get_global_queue(0, 0), ^{
[p class]
});
}
如上,正常情况下p对象会在主线程释放,但是若用子线程block捕获p对象,则viewDidLoad执行完后block为最后一个拥有p的场所,p会在子线程runloop运行结束时释放,达到了对象在子线程释放的目的,避免主线程资源占用。
2、磁盘缓存
小文件(默认是小于20kb)采用sqlite,大文件采用归档后写文件的方式存储,效率最高。值得一提的是对sqlite语句做了缓存
- (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) {
// 没有缓存再创建
sqlite3_prepare_v2(_db, sql.UTF8String, -1, &stmt, NULL);
// 缓存
CFDictionarySetValue(_dbStmtCache, (__bridge const void *)(sql), stmt);
} else {
sqlite3_reset(stmt);
}
return stmt;
}
sqlite3_stmt: 该对象的表示已经编译成二进制形式并准备执行的单个 SQL 语句。缓存了这个,可以避免sql语句重复生成该可执行语句的开销,性能优化。
对大文件,采用的是文件整体部分用写文件方式保存,而配合用sqlite保存该文件的基础信息,比如文件路径、文件大小、保存时间等。