YYImage和SDWebIMage的功能是相同的,通过为系统的UIImageView、UIButton、CALayer添加分类方法继而提供图像的下载、展示、缓存等功能,另外YYImage还支持GIF、APNG、WebP格式的动画图片。
入口的选择
YYImage的使用方法同SDWebImage相同,都可以通过调用原生UI控件的分类方法获取其提供的功能。
如果你的图像展示区域需要响应UIEvent事件,可以选择使用UIImageView或UIButton,然后调用UI控件对应的分类方法。
如果你的图像展示区域不需要响应UIEvent事件,只是单纯的显示图像内容,可以选择使用CALayer,使用CALayer可以减少屏幕上UI控件的层级,进而减少CPU和GPU的计算和渲染压力,提高性能,特别对于UIScrollView及其派生类来说,可提高滑动流畅性。
图片的下载
如何避免重复下载
UITableViewCell频繁在屏幕中出现时如果不加限制会重复发送下载图片的网络请求,在YYImage中为避免图片重复下载,每次调用setImageWithURL:开头的分类方法时,对于每个有效的下载操作,OSAtomicIncrement32都会以原子方式对_sentinel递增32位值。
避免重复下载的操作被封装在了分类方法和_YYWebImageSetter类中。
_YYWebImageSetter类有几个成员变量:
@implementation _YYWebImageSetter {
dispatch_semaphore_t _lock;
NSURL *_imageURL;
NSOperation *_operation;
int32_t _sentinel;
}
变量_lock用来控制并发操作保证线程安全。
变量_imageURL表示当前下载操作所下载图片的URL。
变量_operation表示当前下载操作。
变量_sentinel译为哨兵,用来比对两次下载操作是否为同一个。
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageHighlightedSetterKey);
if (!setter) {
setter = [_YYWebImageSetter new];
objc_setAssociatedObject(self, &_YYWebImageHighlightedSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
int32_t sentinel = [setter cancelWithNewURL:imageURL];
- (int32_t)cancelWithNewURL:(NSURL *)imageURL {
int32_t sentinel;
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
if (_operation) {
[_operation cancel];
_operation = nil;
}
_imageURL = imageURL;
sentinel = OSAtomicIncrement32(&_sentinel);
dispatch_semaphore_signal(_lock);
return sentinel;
}
分类通过runtime将_YYWebImageSetter对象(下称setter对象)绑定到了自己身上,每次调用setImageWithURL:方法时都会获取到这个setter对象,如果setter对象已经开始了一个下载操作,就会将这个下载操作cancel。然后更新_imageURL为新的值,并将_sentinel递增,返回递增后的新值,新值将用于在后续的创建图片下载操作时与旧值进行比对。
下载图片
接下来,会到YYImageCache中根据URL获取UIImage对象,YYImageCache封装了YYMemoryCache和YYDiskCache,所以UIImage的查找操作会先到内存缓存中查找,内存缓存里没有会到磁盘缓存里去找。
如果没找到,会将placeholder赋值给当前分类的image属性,展示占位图;然后切换到指定的串行队列进行下载任务,在这个任务中,会创建YYWebImageProgressBlock和YYWebImageCompletionBlock两个block用于图片下载中和下载完成的回调,任务的最后,会调用setter对象的方法,在这个方法中创建一个图片下载操作,方法实现如下:
- (int32_t)setOperationWithSentinel:(int32_t)sentinel
url:(NSURL *)imageURL
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if (sentinel != _sentinel) {
if (completion) completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
return _sentinel;
}
NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion];
if (!operation && completion) {
NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : @"YYWebImageOperation create failed." };
completion(nil, imageURL, YYWebImageFromNone, YYWebImageStageFinished, [NSError errorWithDomain:@"com.ibireme.yykit.webimage" code:-1 userInfo:userInfo]);
}
dispatch_semaphore_wait(_lock, DISPATCH_TIME_FOREVER);
if (sentinel == _sentinel) {
if (_operation) [_operation cancel];
_operation = operation;
sentinel = OSAtomicIncrement32(&_sentinel);
} else {
[operation cancel];
}
dispatch_semaphore_signal(_lock);
return sentinel;
}
在方法实现中,首先会比对新旧两个哨兵变量的值(ps:新值是我们在前面说到的通过调用setter对象的cancelWithNewURL:方法获取的到的),如果两个值不相等,则会执行completion block,告诉调用方本次图片下载操作被取消了,如果两个值相等,表明该图片是第一次下载,通过YYWebImageManager创建一个下载操作(YYWebImageOperation类型),接下来,会再次判断新旧两个哨兵变量的值(可能存在来回滑动UITableView的操作导致cell频繁出现在屏幕内),如果两次值相同,就会取消上一次的图片下载操作,将_opration赋值为新的operation,并原子递增_sentinel;如果两次值不同,就取消本次下载操作。
在利用YYWebImageManager生成新的operation的同时,方法内部创建完operation后就会将其放入到专门用来做下载任务的队列,然后执行其任务。
在上一步中,每个operation的类型都是YYWebImageOperation,在YYWebImageOperation中,封装了下载操作的具体实现细节,值得一提的是,当前版本的图片下载操作仍然使用的是NSURLConnection,所以这里利用runloop开启了一条常驻线程,保证下载图片操作不会中断。
这个类中其他的方法实现,就是根据当前操作的executing、finished、cancelled、started等状态执行不同的操作,这里有一个值得注意的细节,就是对于下载图片的操作以及取消操作这些任务都是放在runloop的NSDefaultRunLoopMode下执行的。
图片缓存
YYImage的缓存类是YYImageCache,和SDWebImage相比,最大的不通是SDWebImage是基于NSCache做的图片缓存,YYImageCache是基于YYKit的另一个组件库YYCache做的图片缓存。
YYImageCache直接内置了YYMemoryCache和YYDiskCache,对内存缓存和磁盘缓存的操作都是基于这两个类来做的。
关于YYCache的源码分析,这里是入口。
图片解码
该支持解码动画WebP,APNG,GIF和系统图像格式,如PNG,JPG,JP2,BMP,TIFF,PIC,ICNS和ICO。 它可以使用解码完整的图像数据,或解码图像期间的增量图像数据下载,并且这个类是线程安全的。
对图像解码有兴趣的可以看看YYImageDecoder这个类,因为这个类代码较多,并不是所有代码都值得看,这里直接分享一些关于图片解压缩的资源,看完资源相信你就会对YYImageDecoder所做的事有彻底的了解。
https://github.com/path/FastImageCache
https://developer.apple.com/library/content/documentation/GraphicsImaging/Conceptual/drawingwithquartz2d/Introduction/Introduction.html
https://www.cocoanetics.com/2011/10/avoiding-image-decompression-sickness/
http://stackoverflow.com/questions/23790837/what-is-byte-alignment-cache-line-alignment-for-core-animation-why-it-matters
YYImage
YYImage对象是显示动画图像数据的高级方法。
它是一个完全兼容的UIImage子类。它扩展了UIImage支持动画WebP,APNG和GIF格式图像数据解码。 它也是支持NSCoding协议来存档和取消归档多帧图像数据。
如果图像是从多帧图像数据创建的,并且您想要播放动画,尝试用YYAnimatedImageView替换UIImageView。
YYImage有4个类方法:
+ (nullable YYImage *)imageNamed:(NSString *)name; // no cache!
+ (nullable YYImage *)imageWithContentsOfFile:(NSString *)path;
+ (nullable YYImage *)imageWithData:(NSData *)data;
+ (nullable YYImage *)imageWithData:(NSData *)data scale:(CGFloat)scale;
前面3个方法最终都会调用最后一类个方法,最后一个类方法实现如下:
- (instancetype)initWithData:(NSData *)data scale:(CGFloat)scale {
if (data.length == 0) return nil;
if (scale <= 0) scale = [UIScreen mainScreen].scale;
_preloadedLock = dispatch_semaphore_create(1);
@autoreleasepool {
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
UIImage *image = frame.image;
if (!image) return nil;
self = [self initWithCGImage:image.CGImage scale:decoder.scale orientation:image.imageOrientation];
if (!self) return nil;
_animatedImageType = decoder.type;
if (decoder.frameCount > 1) {
_decoder = decoder;
_bytesPerFrame = CGImageGetBytesPerRow(image.CGImage) * CGImageGetHeight(image.CGImage);
_animatedImageMemorySize = _bytesPerFrame * decoder.frameCount;
}
self.isDecodedForDisplay = YES;
}
return self;
}
其中最核心的两行代码:
YYImageDecoder *decoder = [YYImageDecoder decoderWithData:data scale:scale];
YYImageFrame *frame = [decoder frameAtIndex:0 decodeForDisplay:YES];
在YYImageDecoder的decoderWithData:scale:方法中,会经过一系列的方法调用,对UIImage的二进制数据进行处理,利用YYImageDetectType函数获取图片类型(png、jpeg、webP等等),对于不同的图片类型,生成不同的_YYImageDecoderFrame对象,在_YYImageDecoderFrame对象的_frameAtIndex:decodeForDisplay:方法调用栈中,会利用CPU对图像数据进行强制编解码生成位图,根据位图再生成UIImage对象,这些都是同步操作。
所以,假如你想使用contentOfFile:方法从沙盒读取图片时,建议使用YYImage的同名方法,YYImage会提前对图像数据进行编解码,避免等到真正需要显示的时候才去进行编解码。











网友评论