转载

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

1. SDWebImage怎么实现缓存的?


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

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

- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDCacheQueryCompletedBlock)doneBlock {
  • 调用位置 -- SDWebImageManager.m

- (id )loadImageWithURL:(nullable NSURL *)url
                                    options:(SDWebImageOptions)options
                                   progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                  completed:(nullable SDInternalCompletionBlock)completedBlock {
iOS复习中有关SDWebImage可能知识点总结(2)
调用位置

(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 )loadImageWithURL:(nullable NSURL *)url
                                    options:(SDWebImageOptions)options
                                   progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
                                  completed:(nullable SDInternalCompletionBlock)completedBlock {
iOS复习中有关SDWebImage可能知识点总结(2)
调用位置
  • 实现原理 --- SDImageCache.m

iOS复习中有关SDWebImage可能知识点总结(2)
缓存数据
  • 其中,数据转换部分 原理为:

- (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当str1=str2时,返回0。
最后应该注意的是:两个字符串比较时,是按asiic码大小逐个比较的,当发现某一个大或者小时,就停止比较、返回一个值。否则比较到最后一个字母。

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

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


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

  • 管理下载操作的开始和取消

  • 下载之前查询图片的内存缓存和磁盘缓存

  • 下载之后保存图片到内存缓存和磁盘缓存

  • 返回一个操作对象给上级对象UIImageView+WebCache作为操作缓存数组属性中去

  • SDWebImageDownloader提供的关键API是downloadImageWithURL开头的,可见它仅仅管理下载的操作,没有缓存的管理功能。

正文到此结束
Loading...