美文网首页
SDWebImage详解

SDWebImage详解

作者: 陌巷先森 | 来源:发表于2019-02-28 10:15 被阅读0次

    SDWebImage特性

    • 提供了UIImageView,UIButton,MKAnnotationView的分类,用来显示网络图片,以及缓存管理
    • 采用异步方式来下载图片
    • 采用异步方式使用memory+disk来缓存网络图片,自动管理缓存
    • 支持GIF和webP格式
    • 使用GCD和ARC,不会阻塞主线程
    • 同一个URL不会重复下载
    • 自动识别无效URL,不会反复重试

    SDWebImage用法

    为UIImageView加载图片

    1.sd_setImageWithURL:

    //直接加载网络图片
    [iv1 sd_setImageWithURL:[NSURL URLWithString:imageArray[0]]];
    

    2.sd_setImageWithURL: placeholderImage:

    //先展示默认图片,当网络图片加载完成后替换
    [iv2 sd_setImageWithURL:[NSURL URLWithString:imageArray[1]] placeholderImage:[UIImage imageNamed:@"defaultImage"]];
    

    3.sd_setImageWithURL: placeholderImage: options:

    //先展示默认图片,并设置网络图片处理方式
    [iv3 sd_setImageWithURL:[NSURL URLWithString:imageArray[2]] placeholderImage:[UIImage imageNamed:@"defaultImage"] options:SDWebImageCacheMemoryOnly];
    

    4.sd_setImageWithURL: placeholderImage: options: completed:

    //展示默认图片,设置图片处理方式,以及执行图片加载完成后的block内部代码
    [iv4 sd_setImageWithURL:[NSURL URLWithString:imageArray[3]] placeholderImage:[UIImage imageNamed:@"defaultImage"] options:SDWebImageRefreshCached completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, NSURL *imageURL) {
            NSLog(@"imageSize--height:%f,width:%f",image.size.height,image.size.width);
        }];
    

    其他加载方式不一一介绍,可在SDWebImage源码中查看

    SDWebImage内部原理

    架构图

    架构图

    流程图

    流程图

    核心逻辑

    1.给 UIImageView 设置图片,然后 SDWebImage 调用这个最终的图片加载方法。

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

    以上方法主要进行以下几件事:

    • 取消当前正在进行的加载任务operation
    • 设置placeholder
    • 如果URL不为nil,就通过SDWebImageManager单例开启图片加载任务operation,SDWebImageManager的图片加载方法中会返回一个SDWebImageOperation对象,这个对象包含一个cacheOperation和一个cancelBlock。

    2.进入 SDWebImageManager类进行图片加载

    在上面代码中的downloadImageWithURL:options:progress:completed:方法中会先拿图片的key(默认是图片的url)去SDImageCache单例中通过queryDiskCacheForKey: done:方法读取内存缓存,如果有,就返回给SDWebImageManager;如果内存缓存没有,就开启异步线程,拿经过MD5处理的key去读取磁盘缓存,如果找到则同步到内存缓存,然后返回给SDWebImageManager。
    如果内存缓存和磁盘缓存中都没有,SDWebImageManager就会调用SDWebImageDownloader单例的downloadImageWithURL:options:progress:completed:方法去下载。

    - (NSOperation *)queryDiskCacheForKey:(NSString *)key done:(SDWebImageQueryCompletedBlock)doneBlock {
        if (!doneBlock) {
            return nil;
        }
    //判断key是否为空
        if (!key) {
            doneBlock(nil, SDImageCacheTypeNone);
            return nil;
        }
    
        // 首先检查内存缓存
        UIImage *image = [self imageFromMemoryCacheForKey:key];
        if (image) {
            doneBlock(image, SDImageCacheTypeMemory);
            return nil;
        }
    
        NSOperation *operation = [NSOperation new];
        dispatch_async(self.ioQueue, ^{
            if (operation.isCancelled) {
                return;
            }
           //通过key检查磁盘缓存
            @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;
    }
    

    3. SDWebImageDownloader进行图片下载

    - (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
                                                              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;
    }
    

    downloadImageWithURL:options:progress:completed:方法中会将传入的progressBlock和completedBlock保存起来,并在第一次下载该URL的图片时,创建一个NSMutableURLRequest对象和一个SDWebImageDownloaderOperation对象,并将该对象添加到SDWebImageDownloader的downloadQueue来启动异步下载任务。
    SDWebImageDownloaderOperation中封装了一个NSURLConnection的网络请求,并通过runloop来保持NSURLConnection在start后、收到响应前不被干掉,下载图片时,监听NSURLConnection回调的connection:didReceiveData:方法中会负责progress相关的处理和回调,connectionDidFinishLoading:方法中会负责将data转为image,以及图片解码操作,并最终回调completedBlock。

    4.图片下载完成后的回调

    SDWebImageDownloaderOperation 中的图片下载请求完成后,会回调给 SDWebImageDownloader,然后 SDWebImageDownloader 再回调给 SDWebImageManager,SDWebImageManager 中再将图片分别缓存到内存和磁盘上(可选),并回调给 UIImageView,UIImageView 中再回到主线程设置 image 属性。至此,图片的下载和缓存操作就圆满结束了。

    参考:完整项目资料下载

    相关文章

      网友评论

          本文标题:SDWebImage详解

          本文链接:https://www.haomeiwen.com/subject/wyzxjftx.html