SDWebImage使用

SDWebImage的使用

    //设置图片,使用默认的下载策略,下载完成后,自动缓存到disk和内存中,图片填充到imageView上
    [imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString]];
    
    //设置图片,使用默认的下载策略,下载完成后,自动缓存到disk和内存中,图片填充到imageView上。
    [imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        //图片下载和填充完成后的回调,方便做一些其他操作
    }];
    
    //下载完成前,使用默认图片填充,下载完成后,自动缓存到disk和内存中,图片填充到imageView上。
    [imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"]];
    
    //下载完成前,使用默认图片填充,下载完成后,自动缓存到disk和内存中,图片填充到imageView上。
    [imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        //图片下载和填充完成后的回调,方便做一些其他操作
    }];
    
    //下载完成前,使用默认图片填充,下载完成后,使用options:SDWebImageRetryFailed 该下载策略,缓存数据
    [imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] options:SDWebImageRetryFailed];
    
    //下载完成前,使用默认图片填充,下载完成后,使用options:SDWebImageRetryFailed该下载策略,缓存数据
    [imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] options:SDWebImageRetryFailed completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
          //图片下载和填充完成后的回调,方便做一些其他操作
    }];
    
    //下载完成前,使用默认图片填充,下载完成后,使用options:SDWebImageRetryFailed该下载策略,缓存数据
    [imageView sd_setImageWithURL:[NSURL URLWithString:imageURLString] placeholderImage:[UIImage imageNamed:@"defaultImage.png"] options:SDWebImageRetryFailed progress:^(NSInteger receivedSize, NSInteger expectedSize) {
      //下载过程中的下载进度的回调,NSInteger receivedSize 已收到数据量, NSInteger expectedSize 总数据量
    } completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
        //图片下载和填充完成后的回调,方便做一些其他操作
    }];
    

SDWebImage的所有的下载策略

typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**默认情况下,当一个URL下载失败,该URL会被加入黑名单,不会尝试从新下载。
 *此标志禁用此黑名单。 
    * By default, when a URL fail to be downloaded, the URL is blacklisted so the library won't keep trying.
     * This flag disable this blacklisting.
     */
    SDWebImageRetryFailed = 1 << 0,

/**默认情况下,图像下载在UI交互期间启动,此标志禁用此功能。
导致例如UIScrollView减速的延迟下载

     * By default, image downloads are started during UI interactions, this flags disable this feature,
     * leading to delayed download on UIScrollView deceleration for instance.
     */
    SDWebImageLowPriority = 1 << 1,

    /**此标志禁用磁盘缓存
     * This flag disables on-disk caching
     */
    SDWebImageCacheMemoryOnly = 1 << 2,

/**此标志启用渐进式下载,图像将在下载期间逐步显示,就像浏览器那样
默认情况下,只有在下载完成后才会显示图像
     * This flag enables progressive download, the image is displayed progressively during download as a browser would do.
     * By default, the image is only displayed once completely downloaded.
     */
    SDWebImageProgressiveDownload = 1 << 3,

/**即使图片缓存,也要遵循HTTP响应缓存控制,如果需要,可以从远程位置刷新图像。
磁盘缓存将由NSURLCache而不是SDWebImage处理,导致轻微的性能降低。
此选项可帮助处理在同一请求网址之后更改的图片,例如Facebook图形api配置文件图片。
如果缓存的图片刷新,那么completion block将会在缓存图片上调用一次,最终图片上调用一次
     * Even if the image is cached, respect the HTTP response cache control, and refresh the image from remote location if needed.
     * The disk caching will be handled by NSURLCache instead of SDWebImage leading to slight performance degradation.
     * This option helps deal with images changing behind the same request URL, e.g. Facebook graph api profile pics.
     * If a cached image is refreshed, the completion block is called once with the cached image and again with the final image.
     *
     * Use this flag only if you can't make your URLs static with embedded cache busting parameter.
     */
    SDWebImageRefreshCached = 1 << 4,

    /**      *在iOS 4+中,如果应用程序转到后台,请继续下载图像。这是通过询问系统来实现的
     *需要额外的时间在后台让请求完成。如果后台任务过期,操作将被取消。
     * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
     * extra time in background to let the request finish. If the background task expires the operation will be cancelled.
     */
    SDWebImageContinueInBackground = 1 << 5,

/**
  *处理存储NSHTTPCookieStore中的cookie通过设置
     * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
     * Handles cookies stored in NSHTTPCookieStore by setting
     * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
     */
    SDWebImageHandleCookies = 1 << 6,

/**
    *启用以允许不受信任的SSL证书。
     *用于测试目的。在生产中小心使用。
     * Enable to allow untrusted SSL certificates.
     * Useful for testing purposes. Use with caution in production.
     */
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,

/**
    *默认情况下,图像按照它们排队的顺序加载。此标志将它们移动到
     *队列的前面。
     * By default, images are loaded in the order in which they were queued. This flag moves them to
     * the front of the queue.
     */
    SDWebImageHighPriority = 1 << 8,
    
    /**   *默认情况下,在加载图像时加载占位符图像。此标志将延迟加载
     *的占位符图像,直到图像完成加载。
     * By default, placeholder images are loaded while the image is loading. This flag will delay the loading
     * of the placeholder image until after the image has finished loading.
     */
    SDWebImageDelayPlaceholder = 1 << 9,

/**
     *我们通常不会在动画图像上调用transformDownloadedImage委托方法,
     *因为大多数转换代码会歪曲它。
     *使用此标志来转换它们。
     * We usually don't call transformDownloadedImage delegate method on animated images,
     * as most transformation code would mangle it.
     * Use this flag to transform them anyway.
     */
    SDWebImageTransformAnimatedImage = 1 << 10,
    
/**
     *默认情况下,图像在下载后添加到imageView。但在某些情况下,我们想
     *在设置图像之前处理(应用一个过滤器或者用交叉渐变动画添加它)
     *如果您想在成功时手动设置映像在完成中,请使用此标志
     * By default, image is added to the imageView after download. But in some cases, we want to
     * have the hand before setting the image (apply a filter or add it with cross-fade animation for instance)
     * Use this flag if you want to manually set the image in the completion when success
     */
    SDWebImageAvoidAutoSetImage = 1 << 11
};

SDWebImage实现逻辑分析

- (void)sd_setImageWithURL:(NSURL *)url placeholderImage:(UIImage *)placeholder options:(SDWebImageOptions)options progress:(SDWebImageDownloaderProgressBlock)progressBlock completed:(SDWebImageCompletionBlock)completedBlock {

     //取消当前正在进行的下载,避免重复下载
    [self sd_cancelCurrentImageLoad];
   
//使用运行时,使imageURLKey和self关联起来
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

  //如果不是延迟加载Placeholder的下载策略,就先把Placeholder显示在imageView上
    if (!(options & SDWebImageDelayPlaceholder)) {
   //该方法保证线程安全,更新UI的操作在主线程中完成
        dispatch_main_async_safe(^{
            self.image = placeholder;
        });
    }
    
   //如果存在url,做相应处理,如果不存在,下载完成回调中返回错误信息
    if (url) {
     
       //如果要求显示加载中的小风火轮,那么添加该视图
        // check if activityView is enabled or not
        if ([self showActivityIndicatorView]) {
            [self addActivityIndicator];
        }

        __weak __typeof(self)wself = self; //对self的若引用,避免循环引用的问题

      //SDWebImageManager的方法获取图片
        id  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;
             
       //如果有图片,下载策略是避免自动设置图片,并且有completedBlock,那么实现该block,并返回
                if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock)
                {
                    completedBlock(image, error, cacheType, url);
                    return;
                }
        //如果有图片,那么自动填充图片
                else if (image) {
                    wself.image = image;
                    [wself setNeedsLayout];
                } else {
                     //如果下载策略是延迟加载Placeholder,那么先填充Placeholder。也就是说该下载策略下,Placeholder图片的填充是在取得图片后(可能是下载的,也可能是从缓存或者是Disk中取得)
                    if ((options & SDWebImageDelayPlaceholder)) {
                        wself.image = placeholder;
                        [wself setNeedsLayout];
                    }
                }
               //如果有completedBlock,并且取照片的行为已完成,那么调用completedBlock
                if (completedBlock && finished) {
                    completedBlock(image, error, cacheType, url);
                }
            });
        }];
 
      //设置图像加载操作(存储在基于UIView的字典中)
        [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);
            }
        });
    }
}

总结一下:

主体逻辑

  • 取消正在进行的下载,避免重复下载
  • 使用运行时特性,把imageURLKey和self关联起来
  • 如果不是延迟加载Placeholder的下载策略,就先把Placeholder显示在ImageView上
  • 判断url是否存在,不存在,实现结束block,将失败原因作为参数返回
  • url存在,进行系列操作,逻辑如下
URL存在的逻辑
  • 判断是否加加载的小风火轮view
  • 生成一个遵循 的线程操作,用过来获取图片
  • 在获取图片完成得回调里,调用相关completedBlock
  • 设置图片加载操作(存储基于UIView的字典中)

下载图片downloadImage

- (id )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;

//是否是下载失败的url
    BOOL isFailedUrl = NO;
    @synchronized (self.failedURLs) {
        isFailedUrl = [self.failedURLs containsObject:url];
    }

//如果url不存在或者是,非忽略黑名单的黑名单上的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];
    }
 //把url作为key,查询缓存中是否有这个数据
    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句
        if ((!image || options & SDWebImageRefreshCached) && (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
//如果图片存在,并且缓存是SDWebImageRefreshCached
            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. 尝试从新下载图片以便NSURLCache能从服务器上更新图片
                    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  subOperation = [self.imageDownloader downloadImageWithURL:url options:downloaderOptions progress:progressBlock completed:^(UIImage *downloadedImage, NSData *data, NSError *error, BOOL finished) {
    //下载完成的block
                __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  如果我们将调用completedBlock,另一个completedBlock和这个为同一对象的结束block,存在竞争条件。因此,如果这个方法被调用两次,我们将覆盖新数据
                }
       //下载出错的情况
                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 图像刷新命中NSURLCache缓存,不调用完成块
                    }
                    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];
//将图片存到内存、disk
                                [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取消时,副operation也要取消
            operation.cancelBlock = ^{
                [subOperation cancel];
                
                @synchronized (self.runningOperations) {
                    __strong __typeof(weakOperation) strongOperation = weakOperation;
                    if (strongOperation) {
                        [self.runningOperations removeObject:strongOperation];
                    }
                }
            };
        }
//如果图片存在,进入此if句
        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];
            }
        }
//如果,图片不存在,并且self的代理也不响应相应的下载方法,进入此if句
        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;
}

SDWebImage的下载总结

主体逻辑
  • 生成新的operation
  • 判断url是否为空,如果url为空或者是url在不忽略黑名单策略的黑名单里,返回,有错误信息的completedBlock,
  • 把该operation加入正在运行的operation列表中
  • 从缓存中查询以该url为key的图
  • 在查询结束的block里做相应的操作
  • 返回 operation
从缓存中查询以该url为key的图的逻辑
  • 从内存中查询,有,返回图片
  • 内存中无,查询disk,有,返回图片
  • disk 中无,返回nil
在查询结束的block里做的操作
  • 如果operation被取消,将改operation从正在运行的operation列表中删除为,返回
  • Image不存在或者需要更新缓存(options & SDWebImageRefreshCached),并且self的代理响应(imageManager:shouldDownloadImageForURL:)方法时,开始下载图片,在下载完成的回调里:根据缓存策略,缓存内存或者是disk;根据错误提示和缓存策略,添加黑名单
  • Image存在,在完成block里返回Image,将将改operation从正在运行的operation列表中删除为
  • 其他情况,在完成block里返回nil,将将改operation从正在运行的operation列表中删除为

SDWebImage的下载策略

typedef NS_OPTIONS(NSUInteger, SDWebImageDownloaderOptions) {
     /*低优先级下载
     */
    SDWebImageDownloaderLowPriority = 1 << 0,
     /*逐步下载
     */
    SDWebImageDownloaderProgressiveDownload = 1 << 1,

    /**默认情况下,请求不使用NSURLCache的缓存策略,该选项NSURLCache作为默认的策略被使用
     * By default, request prevent the use of NSURLCache. With this flag, NSURLCache
     * is used with default policies.
     */
    SDWebImageDownloaderUseNSURLCache = 1 << 2,

    /**忽略缓存,直接从远端下载
     * Call completion block with nil image/imageData if the image was read from NSURLCache
     * (to be combined with `SDWebImageDownloaderUseNSURLCache`).
     */

    SDWebImageDownloaderIgnoreCachedResponse = 1 << 3,
    /**在iOS 4+的系统,如果设置了允许后台下载,该选项可以在程序后代的情况下,继续下载
     * In iOS 4+, continue the download of the image if the app goes to background. This is achieved by asking the system for
     * extra time in background to let the request finish. If the background task expires the operation will be cancelled.
     */

    SDWebImageDownloaderContinueInBackground = 1 << 4,

    /**通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES;来处理 cookie在NSHTTPCookieStore上的缓存
     * Handles cookies stored in NSHTTPCookieStore by setting 
     * NSMutableURLRequest.HTTPShouldHandleCookies = YES;
     */
    SDWebImageDownloaderHandleCookies = 1 << 5,

    /**允许非信任安全证书的请求。
       用做测试的目的。实际生产环境谨慎使用
     * Enable to allow untrusted SSL certificates.
     * Useful for testing purposes. Use with caution in production.
     */
    SDWebImageDownloaderAllowInvalidSSLCertificates = 1 << 6,

    /**高优先级下载
     * Put the image in the high priority queue.
     */
    SDWebImageDownloaderHighPriority = 1 << 7,
};

你可能感兴趣的:(SDWebImage使用)