SDWebImage的主流程

SDWebImage提供了UIImageView的分类来实现从网络端下载数据并缓存到内存和磁盘。非常的流行,现在就主流程分析下.

主要的学习链接 :SDWebImage源码解读 - 简书
 

SDWebImage有如下特点:

  • 提供了UIImageView和UIButton的分类。以支持加载网络图片并缓存。
  • 一个异步的图片下载器
  • 提供异步的内存和磁盘缓存,并自动处理缓存过期和缓存大小设置。
  • 后台图片解压缩处理。
  • 确保同一个URL不会被下载多次。
  • 确保主线程永远不会阻塞。

一.储备知识

SDWebImage中每一个下载任务都是一个SDWebImageDownloaderOperation,而SDWebImageDownloaderOperation又是继承自NSOperation,所以每一个下载任务对应一个NSOperation。在SDWebImage中使用SDWebImageDownloader来管理
多个下载任务,在SDWebImageDownloader中有一个downloadedQueue这个属性,这个属性是NSOperationQueue类型的,也就是用NSOperationQueue来管理NSOperation

NSOperation和NSOperationQueue

NSOperation是一个抽象类,用来表示与单个任务相关联的代码和数据。

我们可以把一个NSOperation对象加入到一个operation queue中,让这个operation queue去决定什么时候执行这个operation。当使用operation queue去管理operation时,轮到某个operation执行时实际是去执行这个operation的start方法,所以我们一个operation对象实际要执行的任务应该放在start方法里面。如果我们不想使用operation queue,也可以通过手动调用NSOperation的start方法来执行任务。

只要当一个operation对象的所有依赖都执行完成的时候,其才可以变成熟ready状态,然后才可以被执行。如果一个operation没有添加依赖,直接加入了operation queue中,那么就会按照加入队列的先后顺序,当这个operation的前一个operation执行完成以后,其状态才会变成ready,才能被执行。

NSOperation对象有下列四个比较重要的状态:

  • isCancelled
  • isExecuting
  • isFinished
  • isReady
    其中isExecutingisFinishedisReady这三种状态相当于是operation对象的生命周期:

框架的主要类和一次图片加载的主要流程

框架的主要类

从上图也可以看出,整个框架主要分为管理者 , 图片的下载,图片的缓存,和处理解压图片相关的类。

下载

  • SDWebImageDownloader:实际的下载功能和配置提供者,使用了单例的设计模式。
  • SDWebImageDownloaderOperation:继承自NSOperation,是一个异步的NSOperation,封装了NSURLSession进行实际的下载任务。

缓存处理

  • SDImageCache:继承自NSCache,实际处理内存cache和磁盘cache。
  • SDImageCacheConfig:缓存处理的配置。
  • SDWebImageCoder:定义了编码解码的协议,从而可以实现面向协议编程。

功能类

  • SDWebImageManager:宏观的从整体上管理整个框架的类。
  • SDWebImagePrefetcher:图片的预加载管理。

图片的编码解码处理

Category

  • 类别用来为UIView和UIImageView等”添加”属性来存储必要的信息,同时暴露出接口,进行实际的操作。

一次图片加载的主要流程

针对上图中一次图片加载的主要流程,每一步做介绍:

  • 1.SDWebImage为UIImagView创建了一个分类UIImageView (WebCache),然后UIImageView对象可以调用这个分类的方法来下载图片:
    [imageView sd_setImageWithURL:[NSURL URLWithString:@""]];
  • 2.UIImageView (WebCache)- (void)sd_setImageWithURL:(nullable NSURL *)url方法实际调用了UIView (WebCache)的下列方法:
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock;
  • 3.UIView (WebCache)的上述方法在实现时会创建一个SDWebImageManager的实例对象,然后调用其下列方法来加载图片:
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)completedBlock;
  • 4.在SDWebImageManager对象的上述方法里,首先会查询在缓存中有没有这个图片,然后根据各种option的判断决定是否要从网络端下载。查询缓存中有没有是通过调用SDImageCache对象的实例方法来实现的:
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key 
                                            options:(SDImageCacheOptions)options 
                                               done:(nullable SDCacheQueryCompletedBlock)doneBlock;
  • 5.返回缓存查询的结果
  • 6.如果需要下载图片,那么调用SDWebImageDownloader对象的下列方法进行下载:
- (SDWebImageDownloadToken *)downloadImageWithURL:(NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(SDWebImageDownloaderCompletedBlock)completedBlock;
  • 7.获取从网络端下载的图片。
  • 8.判断是否要将下载的图片进行缓存,如果需要,则缓存。
  • 9.把通过SDWebImageManager对象获取的图片显示在UIImageView上。

源码分析

这一部分我们进行详细的源码分析。

1. UIImageVIew的类别

// 1.1 UIImageView类别给出的接口
- (void)sd_setImageWithURL:(nullable NSURL *)url placeholderImage:(nullable UIImage *)placeholder {
    [self sd_setImageWithURL:url placeholderImage:placeholder options:0 progress:nil completed:nil];
}

// 1.2 调到了这里,这里有调到UIVIew的类别
- (void)sd_setImageWithURL:(nullable NSURL *)url
          placeholderImage:(nullable UIImage *)placeholder
                   options:(SDWebImageOptions)options
                  progress:(SDWebImageDownloaderProgressBlock)progressBlock
                 completed:(SDExternalCompletionBlock)completedBlock {

    [self sd_internalSetImageWithURL:url
                    placeholderImage:placeholder
                             options:options
                        operationKey:nil
                       setImageBlock:nil
                            progress:progressBlock
                           completed:completedBlock];
}

2. 调到了UIView的类别

// 2.UIView的类别
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
                  placeholderImage:(nullable UIImage *)placeholder
                           options:(SDWebImageOptions)options
                      operationKey:(nullable NSString *)operationKey
                     setImageBlock:(nullable SDSetImageBlock)setImageBlock
                          progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                         completed:(nullable SDExternalCompletionBlock)completedBlock {

    NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
    [self sd_cancelImageLoadOperationWithKey:validOperationKey];
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    
    if (!(options & SDWebImageDelayPlaceholder)) {
        dispatch_main_async_safe(^{
            [self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
        });
    }
    
    if (url) {
        // check if activityView is enabled or not
        if ([self sd_showActivityIndicatorView]) {
            [self sd_addActivityIndicator];
        }
        
        __weak __typeof(self)wself = self;
        // 找manager问图片,然后处理找到的情况
        id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock 
           completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
            // 假设已经找到了
            __strong __typeof (wself) sself = wself;
            [sself sd_removeActivityIndicator];
            if (!sself) {
                return;
            }
            dispatch_main_async_safe(^{
                if (!sself) {
                    return;
                }
                // 找到了就回调
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
                    completedBlock(image, error, cacheType, url);
                    return;
                } else if (image) {
                    // 找了图片,但是completedBlock为nil,用默认的回调
                    [sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                    [sself sd_setNeedsLayout];
                } else {
                    // 没找到图片,还是用占位图
                    if ((options & SDWebImageDelayPlaceholder)) {
                        [sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
                        [sself sd_setNeedsLayout];
                    }
                }
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
        [self sd_setImageLoadOperation:operation forKey:validOperationKey];
    } else {
        dispatch_main_async_safe(^{
            [self sd_removeActivityIndicator];
            if (completedBlock) {
                completedBlock(nil, error, SDImageCacheTypeNone, url);
            }
        });
    }
}


前面的调用比较简单,现在到了第3步SDWebImageManager类的loadImageWithURL:方法看起:

/// 3.在这里加载图片,核心的核心. 检查内存,硬盘,网络下载
- (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                     options:(SDWebImageOptions)options
                                    progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                   completed:(nullable SDInternalCompletionBlock)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;
    if (url) {
        @synchronized (self.failedURLs) {
            isFailedUrl = [self.failedURLs containsObject:url];
        }
    }

    if (url.absoluteString.length == 0 || (!(options & SDWebImageRetryFailed) && isFailedUrl)) {
        [self callCompletionBlockForOperation:operation 
completion:completedBlock error:[NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:nil] url:url];
        return operation;
    }
    // 把operation添加进入调度数组
    @synchronized (self.runningOperations) {
        [self.runningOperations addObject:operation];
    }
    NSString *key = [self cacheKeyForURL:url];
    // 生成一个查找缓存队列的NSOperation , 
    operation.cacheOperation = [self.imageCache queryCacheOperationForKey:key 
done:^(UIImage *cachedImage, NSData *cachedData, SDImageCacheType cacheType) {
        
        // 这里是查询结果,可能找到了图片,也可能没找到
        
        if (operation.isCancelled) {
            [self safelyRemoveOperationFromRunning:operation];
            return;
        }
        // 图片没有找到,去网络下载
        if (....) {
            
            if (cachedImage && options & SDWebImageRefreshCached) {
                // 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.
                [self callCompletionBlockForOperation:weakOperation 
completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            }

           .......
        
            
            // 图片没有找到,去网络下载
            SDWebImageDownloadToken *subOperationToken = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock 
completed:^(UIImage *downloadedImage, NSData *downloadedData, 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
                } else if (error) {
                    // 下载出错了,把这个URL存入下载失败的URL中,以后不重复尝试了
                    [self callCompletionBlockForOperation:strongOperation 
completion:completedBlock error:error url:url];
                    
                    if (   ....  ) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs addObject:url];
                        }
                    }
                }
                else {
                    // 下载成功,下载失败的URL中移除掉
                    if ((options & SDWebImageRetryFailed)) {
                        @synchronized (self.failedURLs) {
                            [self.failedURLs removeObject:url];
                        }
                    }
                    
                    BOOL cacheOnDisk = !(options & SDWebImageCacheMemoryOnly);

                    if (options & SDWebImageRefreshCached && cachedImage && !downloadedImage) {
                        // Image refresh hit the NSURLCache cache, do not call the completion block
                    } else if (.....) {
                        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];
                                // pass nil if the image was transformed, so we can recalculate the data from the image
                                [self.imageCache storeImage:transformedImage imageData:(imageWasTransformed ? nil : downloadedData) forKey:key toDisk:cacheOnDisk completion:nil];
                            }
                            
                            [self callCompletionBlockForOperation:strongOperation 
completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                        });
                    } else {
                        // 下载成功,存入内存和硬盘
                        if (downloadedImage && finished) {
                            [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key toDisk:cacheOnDisk completion:nil];
                        }
                        [self callCompletionBlockForOperation:strongOperation 
completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
                    }
                }
                // 下载完成了,从活动operation中移除这个
                if (finished) {
                    [self safelyRemoveOperationFromRunning:strongOperation];
                }
            }];
            operation.cancelBlock = ^{
                [self.imageDownloader cancel:subOperationToken];
                __strong __typeof(weakOperation) strongOperation = weakOperation;
                [self safelyRemoveOperationFromRunning:strongOperation];
            };
        } else if (cachedImage) {
            // 图片在内存或者硬盘中找到了,调用回调
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:cachedImage data:cachedData error:nil cacheType:cacheType finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        } else {
            // Image not in cache and download disallowed by delegate
            __strong __typeof(weakOperation) strongOperation = weakOperation;
            [self callCompletionBlockForOperation:strongOperation completion:completedBlock image:nil data:nil error:nil cacheType:SDImageCacheTypeNone finished:YES url:url];
            [self safelyRemoveOperationFromRunning:operation];
        }
    }];

    return operation;
}

总结一下SDWebImageManagerloadImageWithURL:所做的事情:

其实在loadImageWithURL:里面做了加载图片的完整流程。首先检查传入的NSURL的有效性。然后开始从缓存中查找是否有这个图片,得到查询结果之后再根据查询结果和设置的option判断是否需要进行下载操作,如果不需要下载操作那么就直接使用cache image进行下载回调。如果需要进行下载操作那么就开始下载,下载完成后按照设置的option将图片缓存到内存和磁盘,最后进行完成的回调。

然后我们看一下第4步 , 查询缓存的具体过程,也就是SDImageCache这个类的queryCacheOperationForKey:方法:
这里也是采用注释的方式

/// 4.SDImageCache重要的外界接口,根据key查找出图片,并回调回去,本类的核心方法
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key 
done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    if (!key) {
        if (doneBlock) {
            doneBlock(nil, nil, SDImageCacheTypeNone);
        }
        return nil;
    }

    // First check the in-memory cache...  先从内存中找
    UIImage *image = [self imageFromMemoryCacheForKey:key];
    if (image) {
        NSData *diskData = nil;
        if ([image isGIF]) {
            diskData = [self diskImageDataBySearchingAllPathsForKey:key];
        }
        if (doneBlock) {
            doneBlock(image, diskData, SDImageCacheTypeMemory);
        }
        return nil;
    }

    // 在io线程,查找内存,如果在内存中找到了,缓存一份到cache中
    NSOperation *operation = [NSOperation new];
    dispatch_async(self.ioQueue, ^{
        if (operation.isCancelled) {
            // do not call the completion if cancelled
            return;
        }

        @autoreleasepool {
            NSData *diskData = [self diskImageDataBySearchingAllPathsForKey:key];
            UIImage *diskImage = [self diskImageForKey:key];
            if (diskImage && self.config.shouldCacheImagesInMemory) {
                NSUInteger cost = SDCacheCostForImage(diskImage);
                [self.memCache setObject:diskImage forKey:key cost:cost];
            }

            if (doneBlock) {
                dispatch_async(dispatch_get_main_queue(), ^{
                    doneBlock(diskImage, diskData, SDImageCacheTypeDisk);
                });
            }
        }
    });

    return operation;
}

总结一下queryCacheOperationForKey:方法所做的事情:

SDImageCache这个类是专门负责缓存相关的问题的,包括查询缓存和将图片进行缓存。SDImageCache使用了一个NSCache对象来进行内存缓存,磁盘缓存则是把图片数据存放在应用沙盒的 Caches/default/com.hackemist.SDWebImageCache.default/ ...具体的图片文件

首先查询内存缓存,内存缓存查询完了以后再判断是否需要查询磁盘缓存。如果查询内存缓存已经有了结果并且没有设置一定要查询磁盘缓存,那么就不查询磁盘缓存,否则就要查询磁盘缓存。内存缓存没有查询到图片,并且磁盘缓存查询到了图片,那么就要把这个内容缓存到内存缓存中。

图片的缓存查询完成后我们再来看一下第6步 , 下载操作,即SDWebImageDownloaderdownloadImageWithURL:方法

/// 6.真正的下载开始了,生成了下载的request和对应的operation
- (nullable SDWebImageDownloadToken *)downloadImageWithURL:(NSURL *)url
                                                   options:(SDWebImageDownloaderOptions)options
                                                  progress:(SDWebImageDownloaderProgressBlock)progressBlock
                                                 completed:(SDWebImageDownloaderCompletedBlock)completedBlock {
    __weak SDWebImageDownloader *wself = self;

    return [self addProgressCallback:progressBlock completedBlock:completedBlock forURL:url 
         createCallback:^SDWebImageDownloaderOperation *{
        
        // 生成一个下载的Request,添加进入下载队列中
        __strong __typeof (wself) sself = wself;
        NSTimeInterval timeoutInterval = sself.downloadTimeout;
        if (timeoutInterval == 0.0) {
            timeoutInterval = 15.0;
        }

        NSURLRequestCachePolicy cachePolicy = options & SDWebImageDownloaderUseNSURLCache ?
 NSURLRequestUseProtocolCachePolicy : NSURLRequestReloadIgnoringLocalCacheData;
        NSMutableURLRequest *request = [[NSMutableURLRequest alloc] initWithURL:url
                                                                    cachePolicy:cachePolicy
                                                                timeoutInterval:timeoutInterval];
        
        request.HTTPShouldHandleCookies = (options & SDWebImageDownloaderHandleCookies);
        request.HTTPShouldUsePipelining = YES;
        if (sself.headersFilter) {
            request.allHTTPHeaderFields = sself.headersFilter(url, [sself.HTTPHeaders copy]);
        }
        else {
            request.allHTTPHeaderFields = sself.HTTPHeaders;
        }
        SDWebImageDownloaderOperation *operation = [[sself.operationClass alloc] initWithRequest:request 
      inSession:sself.session options:options];
        operation.shouldDecompressImages = sself.shouldDecompressImages;
        
        if (sself.urlCredential) {
            operation.credential = sself.urlCredential;
        } else if (sself.username && sself.password) {
            operation.credential = [NSURLCredential credentialWithUser:sself.username password:sself.password 
persistence:NSURLCredentialPersistenceForSession];
        }
        
        if (options & SDWebImageDownloaderHighPriority) {
            operation.queuePriority = NSOperationQueuePriorityHigh;
        } else if (options & SDWebImageDownloaderLowPriority) {
            operation.queuePriority = NSOperationQueuePriorityLow;
        }

        [sself.downloadQueue addOperation:operation];
        if (sself.executionOrder == SDWebImageDownloaderLIFOExecutionOrder) {
            [sself.lastAddedOperation addDependency:operation];
            sself.lastAddedOperation = operation;
        }

        return operation;
    }];
}

SDWebImageDownloader这个类是专门管理下载的,它有一个属性是downloadQueue,这是一个NSOperationQueue,每创建一个新的下载任务都把它加入到这个downloadQueue中,让downloadQueue去管理任务的开始,取消,结束。

上面的方法其实做的事情很简单,就是创建了一个下载图片的operation,然后把它加入到了downloadQueue中去。

下面的过程就是下载了  ,即SDWebImageDownloader类的addProgressCallback:方法:

/// 生成operation对应的token,然后把进度和完成的回调放到operation上
- (nullable SDWebImageDownloadToken *)addProgressCallback:(SDWebImageDownloaderProgressBlock)progressBlock
                                           completedBlock:(SDWebImageDownloaderCompletedBlock)completedBlock
                                                   forURL:(nullable NSURL *)url
                                           createCallback:(SDWebImageDownloaderOperation *))createCallback {

    if (url == nil) {
        if (completedBlock != nil) {
            completedBlock(nil, nil, nil, NO);
        }
        return nil;
    }

    __block SDWebImageDownloadToken *token = nil;

    dispatch_barrier_sync(self.barrierQueue, ^{
        SDWebImageDownloaderOperation *operation = self.URLOperations[url];
        if (!operation) {
            operation = createCallback();
            self.URLOperations[url] = operation;

            __weak SDWebImageDownloaderOperation *woperation = operation;
            operation.completionBlock = ^{
				dispatch_barrier_sync(self.barrierQueue, ^{
					SDWebImageDownloaderOperation *soperation = woperation;
					if (!soperation) return;
					if (self.URLOperations[url] == soperation) {
						[self.URLOperations removeObjectForKey:url];
					};
				});
            };
        }
        id downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];

        token = [SDWebImageDownloadToken new];
        token.url = url;
        token.downloadOperationCancelToken = downloadOperationCancelToken;
    });

    return token;
}

我们知道,NSOperation类的真正执行任务是在其start方法里面,那么我们看一下SDWebImageDownloaderOperationstart方法的具体实现:
代码比较长,我在关键部分加了注释

/// 这个SDWebImageDownloaderOperation任务启动了
- (void)start {
    @synchronized (self) {
        if (self.isCancelled) {
            self.finished = YES;
            [self reset];
            return;
        }

#if SD_UIKIT
        //这一部分就是解决在后台仍然进行下载的问题
        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
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse) {
            // Grab the cached data for later check  从系统的URL缓存中找下
            NSCachedURLResponse *cachedResponse = [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request];
            if (cachedResponse) {
                self.cachedData = cachedResponse.data;
            }
        }
        
        NSURLSession *session = self.unownedSession;
        if (!self.unownedSession) {
            NSURLSessionConfiguration *sessionConfig = [NSURLSessionConfiguration defaultSessionConfiguration];
            sessionConfig.timeoutIntervalForRequest = 15;
            
            //创建一个session对象,因为后面要创建NSURLSessionTask,需要session对象
            self.ownedSession = [NSURLSession sessionWithConfiguration:sessionConfig
                                                              delegate:self
                                                         delegateQueue:nil];
            session = self.ownedSession;
        }
        
        self.dataTask = [session dataTaskWithRequest:self.request];
        self.executing = YES;
    }
    // 开启下载任务
    [self.dataTask resume];

    if (self.dataTask) {
        // 通知进度,当前下载大小0,未知总长度,下载的URL
        for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
            progressBlock(0, NSURLResponseUnknownLength, self.request.URL);
        }
        // 发出通知,开始下载
        __weak typeof(self) weakSelf = self;
        dispatch_async(dispatch_get_main_queue(), ^{
            [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStartNotification object:weakSelf];
        });
    } 

#if SD_UIKIT
    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
}

这里就是通过一个session对象和一个request对象创建了一个dataTask对象,这个dataTask对象才是真正用来下载的,然后调用[self.dataTask resume]执行下载。

当然下载的过程中会通过回调返回当前的下载进度

- (void)URLSession:(NSURLSession *)session dataTask:(NSURLSessionDataTask *)dataTask didReceiveData:(NSData *)data {
    [self.imageData appendData:data];
    
    // 对图片的二进制流进行处理,应该对对图片编解码的,看不太懂
    if ((self.options & SDWebImageDownloaderProgressiveDownload) && self.expectedSize > 0) {
        ......
    }
    // 下载进度的回调
    for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
        progressBlock(self.imageData.length, self.expectedSize, self.request.URL);
    }
}

用来存储回调的数据结构是一个NSMutableDictionary,其中key是图片的url,value是回调的数组,数组中是一个个的block
举个例子,存储后应该是这样的,

@{
        @"http://imageurl":[
                            @{
                                @"progress":progressBlock1,
                                @"completed":completedBlock1,
                            },
                            @{
                                @"progress":progressBlock2,
                                @"completed":completedBlock2,
                              },
                           ],
            //其他
}

对于二进制文件 都会有头信息,SDWebImage就根据头部信息来获取图片类型


+ (SDImageFormat)sd_imageFormatForImageData:(nullable NSData *)data {
    if (!data) {
        return SDImageFormatUndefined;
    }
    
    uint8_t c;
    [data getBytes:&c length:1];
    switch (c) {
        case 0xFF:
            return SDImageFormatJPEG;
        case 0x89:
            return SDImageFormatPNG;
        case 0x47:
            return SDImageFormatGIF;
        case 0x49:
        case 0x4D:
            return SDImageFormatTIFF;
        case 0x52:
            // R as RIFF for WEBP
            if (data.length < 12) {
                return SDImageFormatUndefined;
            }
            
            NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(0, 12)] encoding:NSASCIIStringEncoding];
            if ([testString hasPrefix:@"RIFF"] && [testString hasSuffix:@"WEBP"]) {
                return SDImageFormatWebP;
            }
    }
    return SDImageFormatUndefined;
}

到这里SDWebImage的源码分析就结束啦。SDWebImage每个类都很小(超过500行的都不多,配合流程图还是比较好理解的),最后放下github上官方给出的流程和各个类之间的联系.


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值