SDWebImage学习

本文深入介绍了SDWebImage的使用方法及内部实现原理,包括不同缓存策略的应用、图片下载流程和多线程处理机制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

SDWebImage学习

github托管地址:https://github.com/rs/SDWebImage
导入头文件:UIImageView+WebCache.h


使用sd_setImageWithURL:缓存图片:

1 . 方法sd_setImageWithURL:

//简单的加载url中的图片
[self.image sd_setImageWithURL:url]

2 . 方法 sd_setImageWithURL: placeholderImage:

//加载完成之前使用图片placeImage作为默认值
[self.image1 sd_setImageWithURL:url placeholderImage:placeImage];

3 . 方法sd_setImageWithURL:imagePath2 completed:

//在图片加载完成之后执行block
[self.image2 sd_setImageWithURL:imagePath2 completed:
 ^(UIImage *image, NSError *error,
   SDImageCacheType cacheType, NSURL *imageURL) {
    NSLog(@"Do something in here...");
}];

4 . 方法sd_setImageWithURL:placeholderImage:options:

//options表示缓存方式
[self.image sd_setImageWithURL:url
              placeholderImage:placeImage
                       options:SDWebImageRetryFailed];
options每个选项含义:

    //加载失败后重试
    SDWebImageRetryFailed = 1 << 0,
    //UI交互期间延时下载
    SDWebImageLowPriority = 1 << 1,
    //只进行内存缓存
    SDWebImageCacheMemoryOnly = 1 << 2,
    //逐步下载,显示图像也是逐步的
    SDWebImageProgressiveDownload = 1 << 3,
    //刷新缓存
    SDWebImageRefreshCached = 1 << 4,
    //后台下载
    SDWebImageContinueInBackground = 1 << 5,
    //通过设置NSMutableURLRequest.HTTPShouldHandleCookies = YES
    //把cookies存储在NSHTTPCookieStore
    SDWebImageHandleCookies = 1 << 6,
    //允许使用无效的SSL证书
    SDWebImageAllowInvalidSSLCertificates = 1 << 7,
    //优先下载
    SDWebImageHighPriority = 1 << 8,
    //延迟显示占位符
    SDWebImageDelayPlaceholder = 1 << 9,
    //进行任意图形变换
    SDWebImageTransformAnimatedImage = 1 << 10,

SDWebImage中我们使用较多的是它提供的UIImageView分类,支持从远程服务器下载并缓存图片。从iOS5.0开始,NSURLCache也可以处理磁盘缓存,那么SDWebImage有什么优势?首先,NSURLCache是缓存原始数据(raw data)到磁盘或内存,因此每次使用的时候需要将原始数据转换成具体的对象,如UIImage等,这会导致额外的数据解析以及内存占用等,而SDWebImage则是缓存UIImage对象在内存,缓存在NSCache中,同时直接保存压缩过的图片到磁盘中;其次,当你第一次在UIImageView中使用image对象的时候,图片的解码是在主线程中运行的!而SDWebImage会强制将解码操作放到子线程中。下图是SDWebImage简单的类图关系:
SDWebImage一些类的调用关系

图片加载调用函数:
- (void)sd_setImageWithURL:(NSURL *)url 
          placeholderImage:(UIImage *)placeholder 
                   options:(SDWebImageOptions)options 
                  progress:(SDWebImageDownloaderProgressBlock)progressBlock 
                 completed:(SDWebImageCompletionBlock)completedBlock {

    //取消正在下载的操作
    [self sd_cancelCurrentImageLoad];
    //创建对象self和对象url的关联
    objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
    /*...*/

    if (url) {
        //防止循环引用
        __weak UIImageView *wself = self;
        //由SDWebImageManager负责图片的获取
        id <SDWebImageOperation> operation =
        [SDWebImageManager.sharedManager downloadImageWithURL:url 
                                                      options:options 
                                                     progress:progressBlock 
                                                    completed:
         ^(UIImage *image, NSError *error, SDImageCacheType cacheType,
           BOOL finished, NSURL *imageURL) {
            /*获取图片到主线层显示*/
         }];

        [self sd_setImageLoadOperation:operation forKey:@"UIImageViewImageLoad"];
    } else {
        /*...*/
    }
}

由以上函数可知图片是从服务端、内存或者硬盘获取是由SDWebImageManager管理的,这个类有几个重要的属性:

//负责管理cache,涉及内存缓存和硬盘保存
@property (strong, nonatomic, readonly) SDImageCache *imageCache;
//负责从网络下载图片
@property (strong, nonatomic, readonly) SDWebImageDownloader *imageDownloader;

manager会根据URL先去imageCache中查找对应的图片,如果没有再使用downloader去下载,并在下载完成缓存图片到imageCache,接着看实现:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url
       options:(SDWebImageOptions)options
      progress:(SDWebImageDownloaderProgressBlock)progressBlock
     completed:(SDWebImageCompletionWithFinishedBlock)completedBlock {

    /*...*/
    //根据URL生成对应的key,没有特殊处理为[url absoluteString];
    NSString *key = [self cacheKeyForURL:url];
    //去imageCache中寻找图片
    operation.cacheOperation =
    [self.imageCache queryDiskCacheForKey:key
                                     done:^(UIImage *image, SDImageCacheType cacheType) {
        /*...*/
        //如果图片没有找到,或者采用的SDWebImageRefreshCached选项,则从网络下载
        if ((!image || options & SDWebImageRefreshCached) &&
            (![self.delegate respondsToSelector:@selector(
            imageManager:shouldDownloadImageForURL:)]
             || [self.delegate imageManager:self shouldDownloadImageForURL:url])) {
            if (image && options & SDWebImageRefreshCached) {
                dispatch_main_sync_safe(^{
                    //如果图片找到了,但是采用的SDWebImageRefreshCached选项,
                    //通知获取到了图片,并再次从网络下载,使NSURLCache重新刷新
                    completedBlock(image, nil, cacheType, YES, url);
                });
            }

                /*下载选项设置...*/
                //使用imageDownloader开启网络下载
                id <SDWebImageOperation> subOperation =
                [self.imageDownloader downloadImageWithURL:url
                                                   options:downloaderOptions
                                                  progress:progressBlock
                                                 completed:^(UIImage *downloadedImage,
                                                             NSData *data, NSError *error,
                                                             BOOL finished) {
                            /*...*/

                            if (downloadedImage && finished) {
                            //下载完成后,先将图片保存到imageCache中,然后主线程返回
                                recalculateFromImage:NO imageData:data 
                                forKey:key toDisk:cacheOnDisk];
                        }

                        dispatch_main_sync_safe(^{
                            if (!weakOperation.isCancelled) {
                                completedBlock(downloadedImage, nil,
                                SDImageCacheTypeNone, finished, url);
                            }
                        });
                    }
                }
            /*...*/
            else if (image) {
            //在cache中找到图片了,直接返回
            dispatch_main_sync_safe(^{
                if (!weakOperation.isCancelled) {
                    completedBlock(image, nil, cacheType, YES, url);
                }
            });
        /*...*/
        }
    }];

    return operation;
}

下面先看downloader从网络下载的过程,下载是放在NSOperationQueue中进行的,默认maxConcurrentOperationCount为6,timeout时间为15s:

- (id <SDWebImageOperation>)downloadImageWithURL:(NSURL *)url 
         options:(SDWebImageDownloaderOptions)options 
        progress:(SDWebImageDownloaderProgressBlock)progressBlock 
       completed:(SDWebImageDownloaderCompletedBlock)completedBlock {

    __block SDWebImageDownloaderOperation *operation;
    __weak SDWebImageDownloader *wself = self;

        /*...*/
        //防止NSURLCache和SDImageCache重复缓存
        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;
        }
        //SDWebImageDownloaderOperation派生自NSOperation,负责图片下载工作
        operation = [[wself.operationClass alloc]
                     initWithRequest:request
                     options:options
                     progress:^(NSInteger receivedSize, NSInteger expectedSize) {/*...*/}
                     completed:^(UIImage *image, NSData *data, 
                                 NSError *error, BOOL finished) {/*...*/}
                     cancelled:^{/*...*/}];
            /*...*/}];

    return operation;
}

SDWebImageDownloaderOperation派生自NSOperation,通过NSURLConnection进行图片的下载,为了确保能够处理下载的数据,需要在后台运行runloop:

- (void)start {
    @synchronized (self) {

        /*...*/
#if TARGET_OS_IPHONE && __IPHONE_OS_VERSION_MAX_ALLOWED >= __IPHONE_4_0
        /*开启后台下载*/
        if ([self shouldContinueWhenAppEntersBackground]) {
            __weak __typeof__ (self) wself = self;
            self.backgroundTaskId = [[UIApplication sharedApplication]
                                     beginBackgroundTaskWithExpirationHandler:^{
                __strong __typeof (wself) sself = wself;

                if (sself) {
                    [sself cancel];

                    [[UIApplication sharedApplication] 
                    endBackgroundTask:sself.backgroundTaskId];
                    sself.backgroundTaskId = UIBackgroundTaskInvalid;
                }
            }];
        }
#endif

        self.executing = YES;
        self.connection = [[NSURLConnection alloc]
                           initWithRequest:self.request delegate:self startImmediately:NO];
        self.thread = [NSThread currentThread];
    }

    [self.connection start];

    if (self.connection) {
        if (self.progressBlock) {
            self.progressBlock(0, NSURLResponseUnknownLength);
        }
        /*广播通知*/
        [[NSNotificationCenter defaultCenter]
         postNotificationName:SDWebImageDownloadStartNotification object:self];

        if (floor(NSFoundationVersionNumber) <= NSFoundationVersionNumber_iOS_5_1) {
            CFRunLoopRunInMode(kCFRunLoopDefaultMode, 10, false);
        }
        else {
            //在默认模式下运行当前runlooprun,直到调用CFRunLoopStop停止运行
            CFRunLoopRun();
        }

        if (!self.isFinished) {
            [self.connection cancel];
            [self connection:self.connection
            didFailWithError:[NSError errorWithDomain:NSURLErrorDomain
                                                 code:NSURLErrorTimedOut
                                             userInfo:
                    @{NSURLErrorFailingURLErrorKey : self.request.URL}]];
        }
    }
    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
    if (self.backgroundTaskId != UIBackgroundTaskInvalid) {
        [[UIApplication sharedApplication] endBackgroundTask:self.backgroundTaskId];
        self.backgroundTaskId = UIBackgroundTaskInvalid;
    }
#endif
}

下载过程中,在代理 - (void)connection:(NSURLConnection )connection didReceiveData:(NSData )data中将接收到的数据保存到NSMutableData中,[self.imageData appendData:data],下载完成后在该线程完成图片的解码,并在完成的completionBlock中进行imageCache的缓存:

- (void)connectionDidFinishLoading:(NSURLConnection *)aConnection {
    SDWebImageDownloaderCompletedBlock completionBlock = self.completedBlock;
    @synchronized(self) {
        //停止当前的runLoop
        CFRunLoopStop(CFRunLoopGetCurrent());
        /*...*/
    }
    /*...*/
    if (completionBlock) {
        if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached) {
            completionBlock(nil, nil, nil, YES);
        }
        else {
            UIImage *image = [UIImage sd_imageWithData:self.imageData];
            NSString *key = [[SDWebImageManager sharedManager] 
                            cacheKeyForURL:self.request.URL];
            image = [self scaledImageForKey:key image:image];

            if (!image.images) {
                //图片解码
                image = [UIImage decodedImageWithImage:image];
            }
            if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                completionBlock(nil, nil, [NSError errorWithDomain:@"SDWebImageErrorDomain" 
                  code:0 
              userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}], YES);
            }
            else {
                completionBlock(image, self.imageData, nil, YES);
            }
        }
    }
    self.completionBlock = nil;
    [self done];
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值