根據下面的比較,能夠看出圖片後處理方面,PINRemoteImage > YYWebImage > SDWebImagehtml
YYWebImage:node
/** Set the view's `image` with a specified URL. @param imageURL The image url (remote or local file path). @param placeholder he image to be set initially, until the image request finishes. @param options The options to use when request the image. @param manager The manager to create image request operation. @param progress The block invoked (on main thread) during image request. @param transform The block invoked (on background thread) to do additional image process. @param completion The block invoked (on main thread) when image request completed. */ - (void)yy_setImageWithURL:(nullable NSURL *)imageURL placeholder:(nullable UIImage *)placeholder options:(YYWebImageOptions)options manager:(nullable YYWebImageManager *)manager progress:(nullable YYWebImageProgressBlock)progress transform:(nullable YYWebImageTransformBlock)transform completion:(nullable YYWebImageCompletionBlock)completion;
PINRemoteImage:ios
/** Set placeholder on view and retrieve the image from the given URL, process it using the passed in processor block and set result on view. Call completion after image has been fetched, processed and set on view. @param url NSURL to fetch from. @param placeholderImage PINImage to set on the view while the image at URL is being retrieved. @param processorKey NSString key to uniquely identify processor. Used in caching. @param processor PINRemoteImageManagerImageProcessor processor block which should return the processed image. @param completion Called when url has been retrieved and set on view. */ - (void)pin_setImageFromURL:(nullable NSURL *)url placeholderImage:(nullable PINImage *)placeholderImage processorKey:(nullable NSString *)processorKey processor:(nullable PINRemoteImageManagerImageProcessor)processor completion:(nullable PINRemoteImageManagerImageCompletion)completion;
<!--more-->git
根據下面的比較,能夠看出圖片格式支持方面,YYWebImage = SDWebImage = PINRemoteImagegithub
另外對於 WebP 的支持,須要下載 google 的 libwebp pod,這就須要先配置命令行代理了,才能安裝此 pod,命令行代理的配置就不在此說明了。web
而 YYImage 是提早先把編譯 webp 的源碼,並打包成了 framework,直接引入到了項目裏了,避免了配置代理的繁瑣工做。編譯 webp 成 framework 能夠參考此文。api
YYWebImage:緩存
SDWebImage:網絡
PINRemoteImage:app
根據下面的比較,能夠看出圖片解碼控制方面,PINRemoteImage > YYWebImage > SDWebImage
YYWebImage:
YYWebImageOptionIgnoreImageDecoding
來控制不解碼。代碼位置: YYWebImageOperation(connectionDidFinishLoading:) BOOL shouldDecode = (self.options & YYWebImageOptionIgnoreImageDecoding) == 0; BOOL allowAnimation = (self.options & YYWebImageOptionIgnoreAnimatedImage) == 0; UIImage *image; BOOL hasAnimation = NO; if (allowAnimation) { image = [[YYImage alloc] initWithData:self.data scale:[UIScreen mainScreen].scale]; if (shouldDecode) image = [image yy_imageByDecoded]; if ([((YYImage *)image) animatedImageFrameCount] > 1) { hasAnimation = YES; } } else { YYImageDecoder *decoder = [YYImageDecoder decoderWithData:self.data scale:[UIScreen mainScreen].scale]; image = [decoder frameAtIndex:0 decodeForDisplay:shouldDecode].image; }
[_cache setImage:image imageData:data forKey:_cacheKey withType:YYImageCacheTypeAll];
會針對沒有解碼的圖片,進行一次解碼,並存入緩存。因此對於大圖的解碼,仍是會佔用很大的內存。代碼位置: YYWebImageOperation(_didReceiveImageFromWeb:) - (void)_didReceiveImageFromWeb:(UIImage *)image { @autoreleasepool { [_lock lock]; if (![self isCancelled]) { if (_cache) { if (image || (_options & YYWebImageOptionRefreshImageCache)) { NSData *data = _data; dispatch_async([YYWebImageOperation _imageQueue], ^{ // 保存圖片到緩存中 [_cache setImage:image imageData:data forKey:_cacheKey withType:YYImageCacheTypeAll]; }); } } _data = nil; NSError *error = nil; if (!image) { error = [NSError errorWithDomain:@"com.ibireme.image" code:-1 userInfo:@{ NSLocalizedDescriptionKey : @"Web image decode fail." }]; if (_options & YYWebImageOptionIgnoreFailedURL) { if (URLBlackListContains(_request.URL)) { error = [NSError errorWithDomain:NSURLErrorDomain code:NSURLErrorFileDoesNotExist userInfo:@{ NSLocalizedDescriptionKey : @"Failed to load URL, blacklisted." }]; } else { URLInBlackListAdd(_request.URL); } } } if (_completion) _completion(image, _request.URL, YYWebImageFromRemote, YYWebImageStageFinished, error); [self _finish]; } [_lock unlock]; } }
代碼位置: YYImageCache(setImage:imageData:) - (void)setImage:(UIImage *)image imageData:(NSData *)imageData forKey:(NSString *)key withType:(YYImageCacheType)type { if (!key || (image == nil && imageData.length == 0)) return; __weak typeof(self) _self = self; if (type & YYImageCacheTypeMemory) { // add to memory cache if (image) { if (image.yy_isDecodedForDisplay) { [_memoryCache setObject:image forKey:key withCost:[_self imageCost:image]]; } else { // 若是沒有解碼過,解碼圖片,並保存到內存緩存 dispatch_async(YYImageCacheDecodeQueue(), ^{ __strong typeof(_self) self = _self; if (!self) return; [self.memoryCache setObject:[image yy_imageByDecoded] forKey:key withCost:[self imageCost:image]]; }); } } else if (imageData) { dispatch_async(YYImageCacheDecodeQueue(), ^{ __strong typeof(_self) self = _self; if (!self) return; UIImage *newImage = [self imageFromData:imageData]; [self.memoryCache setObject:newImage forKey:key withCost:[self imageCost:newImage]]; }); } } if (type & YYImageCacheTypeDisk) { // add to disk cache if (imageData) { if (image) { [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:imageData]; } [_diskCache setObject:imageData forKey:key]; } else if (image) { dispatch_async(YYImageCacheIOQueue(), ^{ __strong typeof(_self) self = _self; if (!self) return; NSData *data = [image yy_imageDataRepresentation]; [YYDiskCache setExtendedData:[NSKeyedArchiver archivedDataWithRootObject:@(image.scale)] toObject:data]; [self.diskCache setObject:data forKey:key]; }); } } }
PINRemoteImage:
PINRemoteImageManagerDownloadOptionsSkipDecode
來控制不解碼。而且在不解碼時緩存不解碼的圖片數據,解碼時緩存解碼的圖片數據。能夠根據場景來決定是否須要提早解碼圖片。代碼位置: PINRemoteImageManager(materializeAndCacheObject:(id)object cacheInDisk:(NSData *)diskData additionalCost:(NSUInteger)additionalCost url:(NSURL *)url key:(NSString *)key options:(PINRemoteImageManagerDownloadOptions)options outImage:(PINImage **)outImage outAltRep:(id *)outAlternateRepresentation) //takes the object from the cache and returns an image or animated image. //if it's a non-alternative representation and skipDecode is not set it also decompresses the image. - (BOOL)materializeAndCacheObject:(id)object cacheInDisk:(NSData *)diskData additionalCost:(NSUInteger)additionalCost url:(NSURL *)url key:(NSString *)key options:(PINRemoteImageManagerDownloadOptions)options outImage:(PINImage **)outImage outAltRep:(id *)outAlternateRepresentation { NSAssert(object != nil, @"Object should not be nil."); if (object == nil) { return NO; } BOOL alternateRepresentationsAllowed = (PINRemoteImageManagerDisallowAlternateRepresentations & options) == 0; BOOL skipDecode = (options & PINRemoteImageManagerDownloadOptionsSkipDecode) != 0; __block id alternateRepresentation = nil; __block PINImage *image = nil; __block NSData *data = nil; __block BOOL updateMemoryCache = NO; PINRemoteImageMemoryContainer *container = nil; if ([object isKindOfClass:[PINRemoteImageMemoryContainer class]]) { container = (PINRemoteImageMemoryContainer *)object; [container.lock lockWithBlock:^{ data = container.data; }]; } else { // 緩存圖片原始數據 updateMemoryCache = YES; // don't need to lock the container here because we just init it. container = [[PINRemoteImageMemoryContainer alloc] init]; if ([object isKindOfClass:[PINImage class]]) { data = diskData; container.image = (PINImage *)object; } else if ([object isKindOfClass:[NSData class]]) { data = (NSData *)object; } else { //invalid item in cache updateMemoryCache = NO; data = nil; container = nil; } container.data = data; } if (alternateRepresentationsAllowed) { alternateRepresentation = [_alternateRepProvider alternateRepresentationWithData:data options:options]; } if (alternateRepresentation == nil) { //we need the image [container.lock lockWithBlock:^{ image = container.image; }]; if (image == nil && container.data) { image = [PINImage pin_decodedImageWithData:container.data skipDecodeIfPossible:skipDecode]; if (url != nil) { image = [PINImage pin_scaledImageForImage:image withKey:key]; } if (skipDecode == NO) { [container.lock lockWithBlock:^{ // 須要緩存圖片解碼後的數據 updateMemoryCache = YES; container.image = image; }]; } } } if (updateMemoryCache) { [container.lock lockWithBlock:^{ NSUInteger cacheCost = additionalCost; cacheCost += [container.data length]; CGImageRef imageRef = container.image.CGImage; NSAssert(container.image == nil || imageRef != NULL, @"We only cache a decompressed image if we decompressed it ourselves. In that case, it should be backed by a CGImageRef."); if (imageRef) { cacheCost += CGImageGetHeight(imageRef) * CGImageGetBytesPerRow(imageRef); } [self.cache setObjectInMemory:container forKey:key withCost:cacheCost]; }]; } if (diskData) { // 緩存原始圖片數據到磁盤 [self.cache setObjectOnDisk:diskData forKey:key]; } if (outImage) { *outImage = image; } if (outAlternateRepresentation) { *outAlternateRepresentation = alternateRepresentation; } if (image == nil && alternateRepresentation == nil) { PINLog(@"Invalid item in cache"); [self.cache removeObjectForKey:key completion:nil]; return NO; } return YES; }
根據下面的比較,能夠看出網絡請求方面,PINRemoteImage = SDWebImage > YYWebImage
NSURLConnection
NSURLSession
NSURLSession
關於性能比較,只是簡單的使用 FPS 工具,在列表頁面,分別使用三種圖片庫去看效果,滑動效果比較結果以下:
YYWebImage > SDWebImage ~= PINRemoteImage
使用 YYWebImage 加載圖片時的滑動效果是最好的。SDWebImage 和 PINRemoteImage 加載圖片時的滑動效果是差很少的。
因爲想要嘗試使用 ASDisplayKit 去作局部列表頁面的優化,可是這個庫使用的圖片庫是 PINRemoteImage 庫,而項目中使用的是 YYWebImage 庫去加載圖片的,這樣的話就會有兩套圖片庫,因此爲了統一就須要讓ASNetworkImageNode
也要使用 YYWebImage 庫去加載圖片。這也是我爲何要作這個比較的緣由。
代碼以下:
#import <AsyncDisplayKit/AsyncDisplayKit.h> #import <YYWebImage/YYWebImage.h> @interface YYWebImageManager(ASNetworkImageNode)<ASImageCacheProtocol, ASImageDownloaderProtocol> @end @implementation YYWebImageManager(ASNetworkImageNode) - (nullable id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion { // 這裏傳入的 YYWebImageOptionIgnoreImageDecoding,是指望圖片不須要解碼,由於 ASNetworkImageNode 內部本身會在合適的時機進行解碼的。可是正如我上面說的,YYWebImage 不能很好的控制解碼,因此這個參數是不起做用的。 __weak typeof(YYWebImageOperation) *operation = nil; operation = [self requestImageWithURL:URL options:YYWebImageOptionIgnoreImageDecoding progress:^(NSInteger receivedSize, NSInteger expectedSize) { dispatch_async(callbackQueue, ^{ CGFloat progress = expectedSize == 0 ? 0.0 : (CGFloat)receivedSize / expectedSize; if (downloadProgress) { downloadProgress(progress); } }); } transform:nil completion:^(UIImage * _Nullable image, NSURL * _Nonnull url, YYWebImageFromType from, YYWebImageStage stage, NSError * _Nullable error) { dispatch_async(callbackQueue, ^{ if (completion) { completion(image, error, operation); } }); }]; return operation; } - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { if ([downloadIdentifier isKindOfClass:[YYWebImageOperation class]]) { [(YYWebImageOperation *)downloadIdentifier cancel]; } } - (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier { if ([downloadIdentifier isKindOfClass:[YYWebImageOperation class]]) { [(YYWebImageOperation *)downloadIdentifier cancel]; } } - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL { YYImageCache *cache = self.cache; dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ NSString *key = [self cacheKeyForURL:URL transformKey:nil]; [cache.memoryCache removeObjectForKey:key]; }); } - (void)cachedImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue completion:(ASImageCacherCompletion)completion { [self.cache getImageForKey:[self cacheKeyForURL:URL transformKey:nil] withType:YYImageCacheTypeAll withBlock:^(UIImage * _Nullable image, YYImageCacheType type) { dispatch_async(callbackQueue, ^{ if (completion) { completion(image); } }); }]; } @end @interface ASNetworkImageNode(YYWebImageManager) + (ASNetworkImageNode *)lk_imageNode; @end @implementation ASNetworkImageNode(YYWebImageManager) + (ASNetworkImageNode *)lk_imageNode { return [[ASNetworkImageNode alloc] initWithCache:[YYWebImageManager sharedManager] downloader:[YYWebImageManager sharedManager]]; } @end // 使用方式以下: ASNetworkImageNode *node = [ASNetworkImageNode lk_imageNode]; node.URL = [NSURL URLWithString:@""];
能夠看到我在下載的回調方法裏傳入了 YYWebImageOptionIgnoreImageDecoding,是指望圖片不須要解碼,由於 ASNetworkImageNode 內部本身會在合適的時機進行解碼的。可是正如我上面說的,YYWebImage 不能很好的控制解碼,因此這個參數是不起做用的。
因爲這個不能控制解碼的問題,會致使在快速滑動的過程當中,幾十張圖片同時解碼,內存會飆升,YYMemoryCache 的內存警告也來不及回收,最終很容易引發 OOM 而讓 app 崩潰。
因此針對這個問題,目前仍是保留使用兩套圖片庫。
這裏只是可用度方面進行了比較,並無作全面的評估,因此實際項目仍是須要根據本身的需求來決定。
](https://github.com/ibireme/YY...