底層使用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];
}
}