SDWebImage 源码分析

来源:互联网 发布:四层横移编程 编辑:程序博客网 时间:2024/06/05 20:56

SDWebImage 源码分析总结

首先从入口开始

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options;

上面的方法最终调用的是下面的这个方法

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {    [self sd_cancelCurrentImageLoad];    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);    if (!(options & SDWebImageDelayPlaceholder)) {        dispatch_main_async_safe(^{            self.image = placeholder;        });    }    if (url) {        // check if activityView is enabled or not        if ([self showActivityIndicatorView]) {            [self addActivityIndicator];        }        __weak __typeof(self)wself = self;        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager downloadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {            [wself removeActivityIndicator];            if (!wself) return;            dispatch_main_sync_safe(^{                if (!wself) return;                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)                {                    completedBlock(image, error, cacheType, url);                    return;                }                else if (image) {                    wself.image = image;                    [wself setNeedsLayout];                } else {                    if ((options & SDWebImageDelayPlaceholder)) {                        wself.image = placeholder;                        [wself setNeedsLayout];                    }                }                if (completedBlock && finished) {                    completedBlock(image, error, cacheType, url);                }            });        }];        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];    } else {        dispatch_main_async_safe(^{            [self removeActivityIndicator];            if (completedBlock) {                NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:-1 userInfo:@{NSLocalizedDescriptionKey : @"Trying to load a nil url"}];                completedBlock(nil, error, SDImageCacheTypeNone, url);            }        });    }}

在上面的方法实现里,会先取消当前下载操作,然后显示 placeholderImage 。
然后用到了SDWebImageManager的downloadImageWithURL方法,在SDWebImageManager的初始化方法中,创建了SDImageCache和SDWebImageDownloader两个类,顾名思义,SDImageCache是跟缓存图片相关的,SDWebImageDownloader是跟下载图片相关的,这三个类是SDWebImage框架的核心类

以下是SDWebImageManager的downloadImageWithURL方法:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url                                         options:(SDWebImageOptions)options                                        progress:(SDWebImageDownloaderProgressBlock)progressBlock                                       completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {    // Invoking this method without a completedBlock is pointless    NSAssert(completedBlock != nil, @"If you mean to prefetch the image, use -[SDWebImagePrefetcher prefetchURLs] instead");    // Very common mistake is to send the URL using NSString object instead of NSURL. For some strange reason, XCode won't    // throw any warning for this type mismatch. Here we failsafe this error by allowing URLs to be passed as NSString.    if ([url isKindOfClass:NSString.class]) {        url = [NSURL URLWithString:(NSString *)url];    }    // Prevents app crashing on argument type error like sending NSNull instead of NSURL    if (![url isKindOfClass:NSURL.class]) {        url = nil;    }    __block SDWebImageCombinedOperation *operation = [SDWebImageCombinedOperation new];    __weak SDWebImageCombinedOperation *weakOperation = operation;    BOOL isFailedUrl = NO;    @synchronized (self.failedURLs) {        isFailedUrl = [self.failedURLs containsObject:url];    }    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {        dispatch_main_sync_safe(^{            NSError *error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil];            completedBlock(nil, error, SDImageCacheTypeNone, YES, url);        });        return operation;    }    @synchronized (self.runningOperations) {        [self.runningOperations addObject:operation];    }    NSString *key = [self cacheKeyForURL:url];    operation.cacheOperation = [self.imageCache queryDiskCacheForKey:key done:^(UIImage *image, SDImageCacheType cacheType) {        if (operation.isCancelled) {            @synchronized (self.runningOperations) {                [self.runningOperations removeObject:operation];            }            return;        }        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {            if (image && options & SDWebImageRefreshCached) {                dispatch_main_sync_safe(^{                    // If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image                    // AND try to re-download it in order to let a chance to NSURLCache to refresh it from server.                    completedBlock(image, nil, cacheType, YES, url);                });            }            // download if no image or requested to refresh anyway, and download allowed by delegate            SDWebImageDownloaderOptions downloaderOptions = 0;            if (options & SDWebImageLowPriority) downloaderOptions |= SDWebImageDownloaderLowPriority;            if (options & SDWebImageProgressiveDownload) downloaderOptions |= SDWebImageDownloaderProgressiveDownload;            if (options & SDWebImageRefreshCached) downloaderOptions |= SDWebImageDownloaderUseNSURLCache;            if (options & SDWebImageContinueInBackground) downloaderOptions |= SDWebImageDownloaderContinueInBackground;            if (options & SDWebImageHandleCookies) downloaderOptions |= SDWebImageDownloaderHandleCookies;            if (options & SDWebImageAllowInvalidSSLCertificates) downloaderOptions |= SDWebImageDownloaderAllowInvalidSSLCertificates;            if (options & SDWebImageHighPriority) downloaderOptions |= SDWebImageDownloaderHighPriority;            if (image && options & SDWebImageRefreshCached) {                // force progressive off if image already cached but forced refreshing                downloaderOptions &= ~SDWebImageDownloaderProgressiveDownload;                // ignore image read from NSURLCache if image if cached but force refreshing                downloaderOptions |= SDWebImageDownloaderIgnoreCachedResponse;            }            id <SDWebImageOperation> subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {                __strong __typeof(weakOperation) strongOperation = weakOperation;                if (!strongOperation || strongOperation.isCancelled) {                    // Do nothing if the operation was cancelled                    // See #699 for more details                    // if we would call the completedBlock, there could be a race condition between this block and another completedBlock for the same object, so if this one is called second, we will overwrite the new data                }                else if (error) {                    dispatch_main_sync_safe(^{                        if (strongOperation && !strongOperation.isCancelled) {                            completedBlock(nil, error, SDImageCacheTypeNone, finished, url);                        }                    });                    if (   error.code != NSURLErrorNotConnectedToInternet                        && error.code != NSURLErrorCancelled                        && error.code != NSURLErrorTimedOut                        && error.code != NSURLErrorInternationalRoamingOff                        && error.code != NSURLErrorDataNotAllowed                        && error.code != NSURLErrorCannotFindHost                        && error.code != NSURLErrorCannotConnectToHost) {                        @synchronized (self.failedURLs) {                            [self.failedURLs addObject:url];                        }                    }                }                else {                    if ((options & SDWebImageRetryFailed)) {                        @synchronized (self.failedURLs) {                            [self.failedURLs removeObject:url];                        }                    }                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);                    if (options & SDWebImageRefreshCached && image && !downloadedImage) {                        // Image refresh hit the NSURLCache cache, do not call the completion block                    }                    else if (downloadedImage && (!downloadedImage.images || (options & SDWebImageTransformAnimatedImage)) && [self.delegate respondsToSelector:@selector(imageManager:transformDownloadedImage:withURL:)]) {                        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{                            UIImage *transformedImage = [self.delegate imageManager:self transformDownloadedImage:downloadedImage withURL:url];                            if (transformedImage && finished) {                                BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];                                [self.imageCache storeImage:transformedImage recalculateFromImage:imageWasTransformed imageData:(imageWasTransformed ? nil : data) forKey:key toDisk:cacheOnDisk];                            }                            dispatch_main_sync_safe(^{                                if (strongOperation && !strongOperation.isCancelled) {                                    completedBlock(transformedImage, nil, SDImageCacheTypeNone, finished, url);                                }                            });                        });                    }                    else {                        if (downloadedImage && finished) {                            [self.imageCache storeImage:downloadedImage recalculateFromImage:NO imageData:data forKey:key toDisk:cacheOnDisk];                        }                        dispatch_main_sync_safe(^{                            if (strongOperation && !strongOperation.isCancelled) {                                completedBlock(downloadedImage, nil, SDImageCacheTypeNone, finished, url);                            }                        });                    }                }                if (finished) {                    @synchronized (self.runningOperations) {                        if (strongOperation) {                            [self.runningOperations removeObject:strongOperation];                        }                    }                }            }];            operation.cancelBlock = ^{                [subOperation cancel];                @synchronized (self.runningOperations) {                    __strong __typeof(weakOperation) strongOperation = weakOperation;                    if (strongOperation) {                        [self.runningOperations removeObject:strongOperation];                    }                }            };        }        else if (image) {            dispatch_main_sync_safe(^{                __strong __typeof(weakOperation) strongOperation = weakOperation;                if (strongOperation && !strongOperation.isCancelled) {                    completedBlock(image, nil, cacheType, YES, url);                }            });            @synchronized (self.runningOperations) {                [self.runningOperations removeObject:operation];            }        }        else {            // Image not in cache and download disallowed by delegate            dispatch_main_sync_safe(^{                __strong __typeof(weakOperation) strongOperation = weakOperation;                if (strongOperation && !weakOperation.isCancelled) {                    completedBlock(nil, nil, SDImageCacheTypeNone, YES, url);                }            });            @synchronized (self.runningOperations) {                [self.runningOperations removeObject:operation];            }        }    }];    return operation;}

上面的方法中SDWebImageManager 把图片 url交 给 SDImageCache 从缓存查找图片是否已经下载 ,根据图片的url作为key,调用queryDiskCacheForKey:done: 方法先从内存缓存查找是否已经缓存过该图片, 内存缓存主要是由NSCache实现,如果内存中已经有图片缓存, 则回调image图片和缓存类型SDImageCacheTypeMemory。

如果内存缓存中没有, 生成 NSOperation 添加到队列开始从沙盒查找图片是否已经缓存。根据 url key 在沙盒缓存目录下尝试读取图片文件。如果从沙盒读取到了图片, 并且设置了可以缓存到内存(默认shouldCacheImagesInMemory = YES),就会将图片添加到内存缓存中(如果空闲内存过小, 会先清空内存缓存,其实是缓存进NSCache里面)。进而回调展示图片。
- (NSOperation )queryDiskCacheForKey:(NSString )key done:(SDWebImageQueryCompletedBlock)doneBlock {
if (!doneBlock) {
return nil;
}

if (!key) {    doneBlock(nil, SDImageCacheTypeNone);    return nil;}// First check the in-memory cache...UIImage *image = [self imageFromMemoryCacheForKey:key];if (image) {    doneBlock(image, SDImageCacheTypeMemory);    return nil;}NSOperation *operation = [NSOperation new];dispatch_async(self.ioQueue, ^{    if (operation.isCancelled) {        return;    }    @autoreleasepool {        UIImage *diskImage = [self diskImageForKey:key];        if (diskImage && self.shouldCacheImagesInMemory) {            NSUInteger cost = SDCacheCostForImage(diskImage);            [self.memCache setObject:diskImage forKey:key cost:cost];        }        dispatch_async(dispatch_get_main_queue(), ^{            doneBlock(diskImage, SDImageCacheTypeDisk);        });    }});return operation;

}

如果从内存缓存和沙盒缓存目录都读取不到图片, 说明该图片不存在,需要下载, 然后就回调用下载器单例SDWebImageDownloader的downloadImageWithURL方法开始下载图片, 创建SDWebImageDownLoaderOperation操作对象, 将下载操作加到downloadQueue下载队列中。
如果没有设置过下载超时时间,在这里会被默认设置为15s。

这里使用了NSOperation和NSOperationQueue开启一个子线程去下载,在自定义的NSOperation(SDWebImageDownloaderOperation)中利用NSURLSession下载
主要的下载方法放在了SDWebImageDownloaderOperation的start方法里,下载完成后再一层层的回调回去,图片会缓存到内存和沙盒。

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url options:(SDWebImageDownloaderOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageDownloaderCompletedBlock)completedBlock {    __block SDWebImageDownloaderOperation *operation;    __weak __typeof(self)wself = self;    [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url createCallback:^{        NSTimeInterval timeoutInterval = wself.downloadTimeout;        if (timeoutInterval == 0.0) {            timeoutInterval = 15.0;        }        // In order to prevent from potential duplicate caching (NSURLCache + SDImageCache) we disable the cache for image requests if told otherwise        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:(options & SDWebImageDownloaderUseNSURLCache ? NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData) timeoutInterval:timeoutInterval];        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);        request.HTTPShouldUsePipelining = YES;        if (wself.headersFilter) {            request.allHTTPHeaderFields = wself.headersFilter(url, [wself.HTTPHeaders copy]);        }        else {            request.allHTTPHeaderFields = wself.HTTPHeaders;        }        operation = [[wself.operationClass alloc] initWithRequest:request                                                        inSession:self.session                                                          options:options                                                         progress:^(NSInteger receivedSize, NSInteger expectedSize) {                                                             SDWebImageDownloader *sself = wself;                                                             if (!sself) return;                                                             __block NSArray *callbacksForURL;                                                             dispatch_sync(sself.barrierQueue, ^{                                                                 callbacksForURL = [sself.URLCallbacks[url] copy];                                                             });                                                             for (NSDictionary *callbacks in callbacksForURL) {                                                                 dispatch_async(dispatch_get_main_queue(), ^{                                                                     SDWebImageDownloaderProgressBlock callback = callbacks[kProgressCallbackKey];                                                                     if (callback) callback(receivedSize, expectedSize);                                                                 });                                                             }                                                         }                                                        completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {                                                            SDWebImageDownloader *sself = wself;                                                            if (!sself) return;                                                            __block NSArray *callbacksForURL;                                                            dispatch_barrier_sync(sself.barrierQueue, ^{                                                                callbacksForURL = [sself.URLCallbacks[url] copy];                                                                if (finished) {                                                                    [sself.URLCallbacks removeObjectForKey:url];                                                                }                                                            });                                                            for (NSDictionary *callbacks in callbacksForURL) {                                                                SDWebImageDownloaderCompletedBlock callback = callbacks[kCompletedCallbackKey];                                                                if (callback) callback(image, data, error, finished);                                                            }                                                        }                                                        cancelled:^{                                                            SDWebImageDownloader *sself = wself;                                                            if (!sself) return;                                                            dispatch_barrier_async(sself.barrierQueue, ^{                                                                [sself.URLCallbacks removeObjectForKey:url];                                                            });                                                        }];        operation.shouldDecompressImages = wself.shouldDecompressImages;        if (wself.urlCredential) {            operation.credential = wself.urlCredential;        } else if (wself.username && wself.password) {            operation.credential = [NSURLCredential credentialWithUser:wself.username password:wself.password persistence:NSURLCredentialPersistenceForSession];        }        if (options & SDWebImageDownloaderHighPriority) {            operation.queuePriority = NSOperationQueuePriorityHigh;        } else if (options & SDWebImageDownloaderLowPriority) {            operation.queuePriority = NSOperationQueuePriorityLow;        }        [wself.downloadQueue addOperation:operation];        if (wself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {            // Emulate LIFO execution order by systematically adding new operations as last operation's dependency            [wself.lastAddedOperation addDependency:operation];            wself.lastAddedOperation = operation;        }    }];    return operation;}
- (id)initWithRequest:(NSURLRequest *)request            inSession:(NSURLSession *)session              options:(SDWebImageDownloaderOptions)options             progress:(SDWebImageDownloaderProgressBlock)progressBlock            completed:(SDWebImageDownloaderCompletedBlock)completedBlock            cancelled:(SDWebImageNoParamsBlock)cancelBlock {    if ((self = [super init])) {        _request = [request copy];        _shouldDecompressImages = YES;        _options = options;        _progressBlock = [progressBlock copy];        _completedBlock = [completedBlock copy];        _cancelBlock = [cancelBlock copy];        _executing = NO;        _finished = NO;        _expectedSize = 0;        _unownedSession = session;        responseFromCached = YES; // Initially wrong until `- URLSession:dataTask:willCacheResponse:completionHandler: is called or not called    }    return self;}- (void)start {    @synchronized (self) {        if (self.isCancelled) {            self.finished = YES;            [self reset];            return;        }#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0        Class UIApplicationClass = NSClassFromString(@"UIApplication");        BOOL hasApplication = UIApplicationClass && [UIApplicationClass respondsToSelector:@selector(sharedApplication)];        if (hasApplication && [self shouldContinueWhenAppEntersBackground]) {            __weak __typeof__ (self) wself = self;            UIApplication * app = [UIApplicationClass performSelector:@selector(sharedApplication)];            self.backgroundTaskId = [app beginBackgroundTaskWithExpirationHandler:^{                __strong __typeof (wself) sself = wself;                if (sself) {                    [sself cancel];                    [app endBackgroundTask:sself.backgroundTaskId];                    sself.backgroundTaskId = UIBackgroundTaskInvalid;                }            }];        }#endif        NSURLSession *session = self.unownedSession;        if (!self.unownedSession) {            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];            sessionConfig.timeoutIntervalForRequest = 15;            /**             *  Create the session for this task             *  We send nil as delegate queue so that the session creates a serial operation queue for performing all delegate             *  method calls and completion handler calls.             */            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig                                                              delegate:self                                                         delegateQueue:nil];            session = self.ownedSession;        }        self.dataTask = [session dataTaskWithRequest:self.request];        self.executing = YES;        self.thread = [NSThread currentThread];    }    [self.dataTask resume];    if (self.dataTask) {        if (self.progressBlock) {            self.progressBlock(0, NSURLResponseUnknownLength);        }        dispatch_async(dispatch_get_main_queue(), ^{            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:self];        });    }    else {        if (self.completedBlock) {            self.completedBlock(nil, nil, [NSError errorWithDomain:NSURLErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Connection can't be initialized"}], YES);        }    }#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0    Class UIApplicationClass = NSClassFromString(@"UIApplication");    if(!UIApplicationClass || ![UIApplicationClass respondsToSelector:@selector(sharedApplication)]) {        return;    }    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {        UIApplication * app = [UIApplication performSelector:@selector(sharedApplication)];        [app endBackgroundTask:self.backgroundTaskId];        self.backgroundTaskId = UIBackgroundTaskInvalid;    }#endif}

图片下载由 NSURLSession来实现, 目前最新版本4.x(从3.8.0版本开始替换了NSURLConnection), 实现相关 delegate 来判断图片下载中、下载完成和下载失败。数据下载完成后交给SDWebImageDecoder 做图片解码处理。