SDWebImage详解

2019-02-28  本文已影响0人  陌巷先森

SDWebImage特性

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

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

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 属性。至此,图片的下载和缓存操作就圆满结束了。

参考:完整项目资料下载

上一篇下一篇

猜你喜欢

热点阅读