美文网首页iOS开发-开源类库
iOS复习中有关SDWebImage可能知识点总结(2)

iOS复习中有关SDWebImage可能知识点总结(2)

作者: 小码僧 | 来源:发表于2018-07-01 22:36 被阅读159次

    1. SDWebImage怎么实现缓存的?


    分为内存缓存(利用SDImageCache类的NSCache属性),磁盘缓存(利用NSFileManager),和操作缓存(利用runtime关联的字典属性)。下载之前先查询缓存,没有就下载并在下载后保存图片到缓存。

    (1). 查询图片缓存 的内部API

    - (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
    
    • 调用位置 -- SDWebImageManager.m
    - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(nullable SDInternalCompletionBlock)completedBlock {
    
    调用位置

    (2). 保存图片到缓存 的内部API

    - (void)storeImage:(nullable UIImage *)image
             imageData:(nullable NSData *)imageData
                forKey:(nullable NSString *)key
                toDisk:(BOOL)toDisk
            completion:(nullable SDWebImageNoParamsBlock)completionBlock {
    
    • 调用位置 -- SDWebImageManager.m
    - (id <SDWebImageOperation>)loadImageWithURL:(nullable NSURL *)url
                                         options:(SDWebImageOptions)options
                                        progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                       completed:(nullable SDInternalCompletionBlock)completedBlock {
    
    调用位置
    • 实现原理 --- SDImageCache.m
    缓存数据
    • 其中,数据转换部分 原理为:
    - (nullable NSData *)sd_imageDataAsFormat:(SDImageFormat)imageFormat {
        NSData *imageData = nil;
        if (self) {
    #if SD_UIKIT || SD_WATCH
            int alphaInfo = CGImageGetAlphaInfo(self.CGImage);
            BOOL hasAlpha = !(alphaInfo == kCGImageAlphaNone ||
                              alphaInfo == kCGImageAlphaNoneSkipFirst ||
                              alphaInfo == kCGImageAlphaNoneSkipLast);
            
            BOOL usePNG = hasAlpha;
            
            // the imageFormat param has priority here. But if the format is undefined, we relly on the alpha channel
            if (imageFormat != SDImageFormatUndefined) {
                usePNG = (imageFormat == SDImageFormatPNG);
            }
            
            if (usePNG) {
                imageData = UIImagePNGRepresentation(self);
            } else {
                imageData = UIImageJPEGRepresentation(self, (CGFloat)1.0);
            }
    #else
            NSBitmapImageFileType imageFileType = NSJPEGFileType;
            if (imageFormat == SDImageFormatGIF) {
                imageFileType = NSGIFFileType;
            } else if (imageFormat == SDImageFormatPNG) {
                imageFileType = NSPNGFileType;
            }
            
            imageData = [NSBitmapImageRep representationOfImageRepsInArray:self.representations
                                                                 usingType:imageFileType
                                                                properties:@{}];
    #endif
        }
        return imageData;
    }
    

    其中,保存到沙盒部分 原理为:

    - (void)storeImageDataToDisk:(nullable NSData *)imageData forKey:(nullable NSString *)key {
        if (!imageData || !key) {
            return;
        }
        
        [self checkIfQueueIsIOQueue];
        
        if (![_fileManager fileExistsAtPath:_diskCachePath]) {
            [_fileManager createDirectoryAtPath:_diskCachePath withIntermediateDirectories:YES attributes:nil error:NULL];
        }
        
        // get cache Path for image key
        NSString *cachePathForKey = [self defaultCachePathForKey:key];
        // transform to NSUrl
        NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
        
        [_fileManager createFileAtPath:cachePathForKey contents:imageData attributes:nil];
        
        // disable iCloud backup
        if (self.config.shouldDisableiCloud) {
            [fileURL setResourceValue:@YES forKey:NSURLIsExcludedFromBackupKey error:nil];
        }
    }
    

    2. SDWebImage下载后的图片在什么时候用到解码?


    在NSURLSession下载完成后的代理方法中,具体文件是SDWebImageDownloaderOperation.m。

    • 内部API
    + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
    
    • 调用位置1 -- SDWebImageDownloaderOperation.m代理1
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
        @synchronized(self) {
            self.dataTask = nil;
            dispatch_async(dispatch_get_main_queue(), ^{
                [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadStopNotification object:self];
                if (!error) {
                    [[NSNotificationCenter defaultCenter] postNotificationName:SDWebImageDownloadFinishNotification object:self];
                }
            });
        }
        
        if (error) {
            [self callCompletionBlocksWithError:error];
        } else {
            if ([self callbacksForKey:kCompletedCallbackKey].count > 0) {
                /**
                 *  See #1608 and #1623 - apparently, there is a race condition on `NSURLCache` that causes a crash
                 *  Limited the calls to `cachedResponseForRequest:` only for cases where we should ignore the cached response
                 *    and images for which responseFromCached is YES (only the ones that cannot be cached).
                 *  Note: responseFromCached is set to NO inside `willCacheResponse:`. This method doesn't get called for large images or images behind authentication
                 */
                if (self.options & SDWebImageDownloaderIgnoreCachedResponse && responseFromCached && [[NSURLCache sharedURLCache] cachedResponseForRequest:self.request]) {
                    // hack
                    [self callCompletionBlocksWithImage:nil imageData:nil error:nil finished:YES];
                } else if (self.imageData) {
                    UIImage *image = [UIImage sd_imageWithData:self.imageData];
                    NSString *key = [[SDWebImageManager sharedManager] cacheKeyForURL:self.request.URL];
                    image = [self scaledImageForKey:key image:image];
                    
                    // Do not force decoding animated GIFs
                    if (!image.images) {
                        if (self.shouldDecompressImages) {
                            if (self.options & SDWebImageDownloaderScaleDownLargeImages) {
    #if SD_UIKIT || SD_WATCH
                                image = [UIImage decodedAndScaledDownImageWithImage:image];
                                [self.imageData setData:UIImagePNGRepresentation(image)];
    #endif
                            } else {
                                image = [UIImage decodedImageWithImage:image];
                            }
                        }
                    }
                    if (CGSizeEqualToSize(image.size, CGSizeZero)) {
                        [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
                    } else {
                        [self callCompletionBlocksWithImage:image imageData:self.imageData error:nil finished:YES];
                    }
                } else {
                    [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:0 userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
                }
            }
        }
        [self done];
    }
    
    • 调用位置2 -- SDWebImageDownloaderOperation.m代理1
    - (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didCompleteWithError:(NSError *)error {
    //省略...
    
    • 实现原理 -- SDWebImageDecoder.m
    + (nullable UIImage *)decodedImageWithImage:(nullable UIImage *)image {
        if (![UIImage shouldDecodeImage:image]) {
            return image;
        }
        
        // autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
        // on iOS7, do not forget to call [[SDImageCache sharedImageCache] clearMemory];
        @autoreleasepool{
            
            CGImageRef imageRef = image.CGImage;
            CGColorSpaceRef colorspaceRef = [UIImage colorSpaceForImageRef:imageRef];
            
            size_t width = CGImageGetWidth(imageRef);
            size_t height = CGImageGetHeight(imageRef);
            size_t bytesPerRow = kBytesPerPixel * width;
    
            // kCGImageAlphaNone is not supported in CGBitmapContextCreate.
            // Since the original image here has no alpha info, use kCGImageAlphaNoneSkipLast
            // to create bitmap graphics contexts without alpha info.
            CGContextRef context = CGBitmapContextCreate(NULL,
                                                         width,
                                                         height,
                                                         kBitsPerComponent,
                                                         bytesPerRow,
                                                         colorspaceRef,
                                                         kCGBitmapByteOrderDefault|kCGImageAlphaNoneSkipLast);
            if (context == NULL) {
                return image;
            }
            
            // Draw the image into the context and retrieve the new bitmap image without alpha
            CGContextDrawImage(context, CGRectMake(0, 0, width, height), imageRef);
            CGImageRef imageRefWithoutAlpha = CGBitmapContextCreateImage(context);
            UIImage *imageWithoutAlpha = [UIImage imageWithCGImage:imageRefWithoutAlpha
                                                             scale:image.scale
                                                       orientation:image.imageOrientation];
            
            CGContextRelease(context);
            CGImageRelease(imageRefWithoutAlpha);
            
            return imageWithoutAlpha;
        }
    }
    

    3. 怎样安全地在主线程执行一个Block?

    有时候会把将要执行的内容放到主线程里面执行,但如果已经是主线程里面的代码调用dispatch_async的时候偶尔会出现crash,所以就需要判断是否已经在主线程里面了。

    老版本的SDWebImage这样封装了一个宏:

    //主线程同步队列
    #define dispatch_main_sync_safe(block)\
        if ([NSThread isMainThread]) {\
            block();\
        } else {\
            dispatch_sync(dispatch_get_main_queue(), block);\
        }
    //主线程异步队列
    #define dispatch_main_async_safe(block)\
        if ([NSThread isMainThread]) {\
            block();\
        } else {\
            dispatch_async(dispatch_get_main_queue(), block);\
        }
    
    //用法
      dispatch_main_async_safe(^{
                        //需要执行的代码片段;
                    });
    

    新版本的SDWebImage是这样封装的宏:

    #ifndef dispatch_main_async_safe
    #define dispatch_main_async_safe(block)\
        if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(dispatch_get_main_queue())) == 0) {\
            block();\
        } else {\
            dispatch_async(dispatch_get_main_queue(), block);\
        }
    #endif
    

    其中的strcmp是字符串比较函数:

    int strcmp(char *str1, char *str2)
    当str1>str2时,返回一个正数;
    当str1<str2时,返回一个负数;
    当str1=str2时,返回0。
    最后应该注意的是:两个字符串比较时,是按asiic码大小逐个比较的,当发现某一个大或者小时,就停止比较、返回一个值。否则比较到最后一个字母。

    注意的问题是,宏里面的block是无法打断点调试的。你如果步进查看,可以发现会跳到汇编的文件里面步进。

    4. 怎样区分SDWebImageDownloader和SDWebImageManager的工作?

    • SDWebImageManager提供的关键API是loadImageWithURL开头的,负责加载的,加载load这个词跟下载download不同,比它更广,加载负责管理下载之前的操作:

      • 管理下载操作的开始和取消
      • 下载之前查询图片的内存缓存和磁盘缓存
      • 下载之后保存图片到内存缓存和磁盘缓存
      • 返回一个操作对象给上级对象UIImageView+WebCache作为操作缓存数组属性中去
    • SDWebImageDownloader提供的关键API是downloadImageWithURL开头的,可见它仅仅管理下载的操作,没有缓存的管理功能。

    相关文章

      网友评论

      • 没有梦想_何必远方:我问几个手淘的面试问题哈,你前面写的基础的部分都没问。图片体积非常大的时候,怎么办?如果一个页面列表中有很多图片体积都非常大怎么优化?有没有在这个过程中做手动gc的优化或其他?

      本文标题:iOS复习中有关SDWebImage可能知识点总结(2)

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