底层使用NSUrlConnection方式,多线程异步加载网络图片;
- 加载时先从内存缓存区寻找,再从磁盘缓存区寻找,如果没有,从服务器加载,加载完先放入内存缓存区,再放入磁盘,再进行显示;
基本使用:
/* 加载方式:渐变处理,显示方式:模糊->清晰,显示网络状态,
progress:处理进度条
completion:下载完成回调
placeholder:占位图
**/
[_webImageView setImageWithURL:url
placeholder:nil
options:YYWebImageOptionProgressiveBlur
| YYWebImageOptionShowNetworkActivity
| YYWebImageOptionSetImageWithFadeAnimation
progress:^(NSInteger receivedSize, NSInteger expectedSize) {
if (expectedSize > 0 && receivedSize > 0) {
CGFloat progress = (CGFloat)receivedSize / expectedSize;
progress = progress < 0 ? 0 : progress > 1 ? 1 : progress;
if (_self.progressLayer.hidden) _self.progressLayer.hidden = NO;
_self.progressLayer.strokeEnd = progress;
}
} transform:nil
completion:^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
if (stage == YYWebImageStageFinished) {
_self.progressLayer.hidden = YES;
[_self.indicator stopAnimating];
_self.indicator.hidden = YES;
if (!image) _self.label.hidden = NO;
}
}];
- (void)setImageWithURL:(NSURL *)imageURL
placeholder:(UIImage *)placeholder
options:(YYWebImageOptions)options
manager:(YYWebImageManager *)manager
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
if ([imageURL isKindOfClass:[NSString class]]) imageURL = [NSURL URLWithString:(id)imageURL];
manager = manager ? manager : [YYWebImageManager sharedManager];
/*
YYWebImage 类绑定 setter
这里需要的仅仅是 _YYWebImageSetterKey 的地址,所以并没有给 _YYWebImageSetterKey 初始化值,因为并不关心值是什么
**/
_YYWebImageSetter *setter = objc_getAssociatedObject(self, &_YYWebImageSetterKey);
if (!setter) {
setter = [_YYWebImageSetter new];
objc_setAssociatedObject(self, &_YYWebImageSetterKey, setter, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
//取消正在处理的 NSOperation,并设置新的 url , setter.sentinel 自增1
int32_t sentinel = [setter cancelWithNewURL:imageURL];
//涉及ui更新 主队列异步执行
dispatch_async_on_main_queue(^{
if ((options & YYWebImageOptionSetImageWithFadeAnimation) &&
!(options & YYWebImageOptionAvoidSetImage)) {
if (!self.highlighted) {
//移除正在播放的动画
[self.layer removeAnimationForKey:_YYWebImageFadeAnimationKey];
}
}
if (!imageURL) {
//如果url为空,并且需要设置占位图
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
return;
}
//首先从缓存中获取image
UIImage *imageFromMemory = nil;
//不用url缓存,并没有设置刷新缓存(从缓存中找image)
if (manager.cache &&
!(options & YYWebImageOptionUseNSURLCache) &&
!(options & YYWebImageOptionRefreshImageCache)) {
imageFromMemory = [manager.cache getImageForKey:[manager cacheKeyForURL:imageURL] withType:YYImageCacheTypeMemory];
}
if (imageFromMemory) {
if (!(options & YYWebImageOptionAvoidSetImage)) {
self.image = imageFromMemory;
}
if(completion) completion(imageFromMemory, imageURL, YYWebImageFromMemoryCacheFast, YYWebImageStageFinished, nil);
return;
}
if (!(options & YYWebImageOptionIgnorePlaceHolder)) {
self.image = placeholder;
}
__weak typeof(self) _self = self;
/*
创建串行队列 “com.ibireme.yykit.webimage.setter”,并异步执行串行队列
**/
dispatch_async([_YYWebImageSetter setterQueue], ^{
YYWebImageProgressBlock _progress = nil;
if (progress) _progress = ^(NSInteger receivedSize, NSInteger expectedSize) {
/*
progress 需要更新视图,所以需要在主队列中执行
**/
dispatch_async(dispatch_get_main_queue(), ^{
progress(receivedSize, expectedSize);
});
};
__block int32_t newSentinel = 0;
__block __weak typeof(setter) weakSetter = nil;
YYWebImageCompletionBlock _completion = ^(UIImage *image, NSURL *url, YYWebImageFromType from, YYWebImageStage stage, NSError *error) {
__strong typeof(_self) self = _self;
//已完成或正在处理中
BOOL setImage = (stage == YYWebImageStageFinished || stage == YYWebImageStageProgress) && image && !(options & YYWebImageOptionAvoidSetImage);
dispatch_async(dispatch_get_main_queue(), ^{
/*
newSentinel 是 setter setOperationWithSentinel 方法执行之后的标识,
一般 newSentinel 和 weakSetter.sentinel 是相等的,除非setter实例被dealloc ,
则 weakSetter.sentinel != newSentinel
**/
BOOL sentinelChanged = weakSetter && weakSetter.sentinel != newSentinel;
// 首次设置image
if (setImage && self && !sentinelChanged) {
BOOL showFade = ((options & YYWebImageOptionSetImageWithFadeAnimation) && !self.highlighted);
// 如果设置了渐变,并且不是高亮,则做渐变动画
if (showFade) {
CATransition *transition = [CATransition animation];
transition.duration = stage == YYWebImageStageFinished ? _YYWebImageFadeTime : _YYWebImageProgressiveFadeTime;
transition.timingFunction = [CAMediaTimingFunction functionWithName:kCAMediaTimingFunctionEaseInEaseOut];
transition.type = kCATransitionFade;
[self.layer addAnimation:transition forKey:_YYWebImageFadeAnimationKey];
}
self.image = image;
}
if (completion) {
if (sentinelChanged) {
completion(nil, url, YYWebImageFromNone, YYWebImageStageCancelled, nil);
} else {
completion(image, url, from, stage, error);
}
}
});
};
newSentinel = [setter setOperationWithSentinel:sentinel url:imageURL options:options manager:manager progress:_progress transform:transform completion:_completion];
weakSetter = setter;
});
});
}
//创建 operation
- (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;
}
//创建下载操作 ,manager 把任务加到队列里,或默认开启
NSOperation *operation = [manager requestImageWithURL:imageURL options:options progress:progress transform:transform completion:completion];
//创建 operation 失败
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) {
//_operation 指向上一次 operation,当执行下一次操作前,取消上一次operation
if (_operation) [_operation cancel];
_operation = operation;
sentinel = OSAtomicIncrement32(&_sentinel);
} else {
[operation cancel];
}
dispatch_semaphore_signal(_lock);
return sentinel;
}
//创建 request 并加入队列
- (YYWebImageOperation *)requestImageWithURL:(NSURL *)url
options:(YYWebImageOptions)options
progress:(YYWebImageProgressBlock)progress
transform:(YYWebImageTransformBlock)transform
completion:(YYWebImageCompletionBlock)completion {
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
//超时时间
request.timeoutInterval = _timeout;
//是否使用 cookies(cookie可做持久化缓存)
request.HTTPShouldHandleCookies = (options & YYWebImageOptionHandleCookies) != 0;
request.allHTTPHeaderFields = [self headersForURL:url];
/*
是否开启多通道
The WWDC session described it as a huge performance win if the servers support it.
WWDC 描述,如果服务器支持的话,这会有很大的性能提升;
如果服务器支持通道操作的话,性能会有所提升,因为所有后续的请求不必等待第一个完成;
http://stackoverflow.com/questions/14810890/what-are-the-disadvantages-of-using-http-pipelining
**/
request.HTTPShouldUsePipelining = YES;
/* 设置缓存策略
NSURLRequestUseProtocolCachePolicy:使用缓存;
NSURLRequestReloadIgnoringLocalCacheData:忽略本地缓存,重新加载
**/
request.cachePolicy = (options & YYWebImageOptionUseNSURLCache) ?
NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
YYWebImageOperation *operation = [[YYWebImageOperation alloc] initWithRequest:request
options:options
cache:_cache
cacheKey:[self cacheKeyForURL:url]
progress:progress
transform:transform ? transform : _sharedTransformBlock
completion:completion];
if (_username && _password) {
//身份认证,服务器要求用户名和密码时需要提供
operation.credential = [NSURLCredential credentialWithUser:_username password:_password persistence:NSURLCredentialPersistenceForSession];
}
if (operation) {
//可以把队列设置nil , 这样会直接执行任务
NSOperationQueue *queue = _queue;
if (queue) {
[queue addOperation:operation];
} else {
[operation start];
}
}
return operation;
}
//开始执行operation
- (void)start {
@autoreleasepool {
[_lock lock];
self.started = YES;
if ([self isCancelled]) {
//在 network 线程上执行网络相关操作
[self performSelector:@selector(_cancelOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
self.finished = YES;
//是否当前操作可以被执行
} else if ([self isReady] && ![self isFinished] && ![self isExecuting]) {
if (!_request) {
self.finished = YES;
if (_completion) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{NSLocalizedDescriptionKey:@"request in nil"}];
_completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
} else {
self.executing = YES;
[self performSelector:@selector(_startOperation) onThread:[[self class] _networkThread] withObject:nil waitUntilDone:NO modes:@[NSDefaultRunLoopMode]];
if ((_options & YYWebImageOptionAllowBackgroundTask) && ![UIApplication isAppExtension]) {
__weak __typeof__ (self) _self = self;
//如果当前任务是无效的任务请求(如执行 _cancelOperation 操作) 则需要取消这个操作(取消必选执行,所以把 self转成 __strong)
if (_taskID == UIBackgroundTaskInvalid) {
_taskID = [[UIApplication sharedExtensionApplication] beginBackgroundTaskWithExpirationHandler:^{
//避免 _self 执行中被释放,因为这里要执行 [_connection cancel] 操作,必须要执行完毕才能释放
__strong __typeof (_self) self = _self;
if (self) {
[self cancel];
self.finished = YES;
}
}];
}
}
}
}
[_lock unlock];
}
}
//取消操作,执行 _completion,并结束正在执行的后台任务
- (void)_cancelOperation {
@autoreleasepool {
if (_connection) {
if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
[[UIApplication sharedExtensionApplication] decrementNetworkActivityCount];
}
}
[_connection cancel];
_connection = nil;
if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageCancelled, nil);
[self _endBackgroundTask];
}
}
//结束当前任务
- (void)_endBackgroundTask {
[_lock lock];
if (_taskID != UIBackgroundTaskInvalid) {
// 注意 :必须调用此方法结束任务,否则app进入进入后台模式,如果有长时间运行的任务,系统会杀死当前应用
[[UIApplication sharedExtensionApplication] endBackgroundTask:_taskID];
// 标记当前任务id为不可用, 这个表示会再 start 里进行判断
_taskID = UIBackgroundTaskInvalid;
}
[_lock unlock];
}
// 从缓存中取image,并执行 _completion
- (void)_startOperation {
if ([self isCancelled]) return;
@autoreleasepool {
// 先从缓存获取image
if (_cache &&
!(_options & YYWebImageOptionUseNSURLCache) &&
!(_options & YYWebImageOptionRefreshImageCache)) {
//先取内存
UIImage *image = [_cache getImageForKey:_cacheKey withType:YYImageCacheTypeMemory];
if (image) {
[_lock lock];
//???? 第一步不是已经过滤了吗?
if (![self isCancelled]) {
if (_completion) _completion(image, _request.URL, YYWebImageFromMemoryCache, YYWebImageStageFinished, nil);
}
[self _finish];
[_lock unlock];
return;
}
//再取磁盘缓存,取磁盘缓存耗时,异步执行,取到后存入内存cache,取不到从服务器获取
if (!(_options & YYWebImageOptionIgnoreDiskCache)) {
__weak typeof(self) _self = self;
dispatch_async([self.class _imageQueue], ^{
__strong typeof(_self) self = _self;
if (!self || [self isCancelled]) return;
UIImage *image = [self.cache getImageForKey:self.cacheKey withType:YYImageCacheTypeDisk];
//如果从缓存取到image,则将磁盘缓存放入内存缓存,以便下次获取,
//如果没有获取到image,则从服务器加载image
if (image) {
[self.cache setImage:image imageData:nil forKey:self.cacheKey withType:YYImageCacheTypeMemory];
//执行 _completion
[self performSelector:@selector(_didReceiveImageFromDiskCache:) onThread:[self.class _networkThread] withObject:image waitUntilDone:NO];
} else {
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
});
return;
}
}
}
[self performSelector:@selector(_startRequest:) onThread:[self.class _networkThread] withObject:nil waitUntilDone:NO];
}
//从磁盘加载完图片后,执行 _completion ,结束流程
- (void)_didReceiveImageFromDiskCache:(UIImage *)image {
@autoreleasepool {
[_lock lock];
if (![self isCancelled]) {
// 磁盘取到值,结束流程,取不到值,继续请求
if (image) {
if (_completion) _completion(image, _request.URL, YYWebImageFromDiskCache, YYWebImageStageFinished, nil);
[self _finish];
} else {
[self _startRequest:nil];
}
}
[_lock unlock];
}
}
// 创建 _connection
- (void)_startRequest:(id)object {
if ([self isCancelled]) return;
@autoreleasepool {
//URL 有误,或url在黑名单内,则结束流程,执行 _completion
if ((_options & YYWebImageOptionIgnoreFailedURL) && URLBlackListContains(_request.URL)) {
NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
[_lock lock];
if (![self isCancelled]) {
if (_completion) _completion(nil, _request.URL, YYWebImageFromNone, YYWebImageStageFinished, error);
}
[self _finish];
[_lock unlock];
return;
}
//获取文件下载总大小(bytes), _expectedSize 用于计算加载的百分比
if (_request.URL.isFileURL) {
NSArray *keys = @[NSURLFileSizeKey];
NSDictionary *attr = [_request.URL resourceValuesForKeys:keys error:nil];
NSNumber *fileSize = attr[NSURLFileSizeKey];
_expectedSize = fileSize ? fileSize.unsignedIntegerValue : -1;
}
// request image from web
[_lock lock];
if (![self isCancelled]) {
_connection = [[NSURLConnection alloc] initWithRequest:_request delegate:[YYWeakProxy proxyWithTarget:self]];
if (![_request.URL isFileURL] && (_options & YYWebImageOptionShowNetworkActivity)) {
[[UIApplication sharedExtensionApplication] incrementNetworkActivityCount];
}
}
[_lock unlock];
}
}
//从服务器接收完图片
- (void)_didReceiveImageFromWeb:(UIImage *)image {
@autoreleasepool {
[_lock lock];
if (![self isCancelled]) {
//从服务器加载完的图片先放到内存缓存中
if (_cache) {
if (image || (_options & YYWebImageOptionRefreshImageCache)) {
NSData *data = _data;
//耗时操作需要异步执行
dispatch_async([YYWebImageOperation _imageQueue], ^{
[_cache setImage:image imageData:data forKey:_cacheKey withType:YYImageCacheTypeAll];
});
}
}
_data = nil;
NSError *error = nil;
if (!image) {
error = [NSError errorWithDomain:@"com.ibireme.yykit.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }];
if (_options & YYWebImageOptionIgnoreFailedURL) {
if (URLBlackListContains(_request.URL)) {
error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }];
} else {
//用户没有忽略此url ,但是加载失败,则加入黑名单,则下次不进行加载
URLInBlackListAdd(_request.URL);
}
}
}
//回调 _completion
if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error);
// 结束后台正在执行的任务;
[self _finish];
}
[_lock unlock];
}
}