Creating FLAnimatedImage in global queue to improve displaying gif performance (#2047)

* Creating FLAnimatedImage in global queue to improve displaying gif performance
* Added `context` dictionary param to `sd_internalSetImageWithURL` that allows sending context options. For now, one can pass `SDWebImageInternalSetImageInGlobalQueueKey` with true value so the `setImage` op is executed on a global queue.
This commit is contained in:
DreamPiggy 2017-10-25 14:05:46 +08:00 committed by Bogdan Poplauschi
parent 6bab618acc
commit 85cb2fd6ad
6 changed files with 102 additions and 30 deletions

View File

@ -52,17 +52,25 @@
options:options options:options
operationKey:nil operationKey:nil
setImageBlock:^(UIImage *image, NSData *imageData) { setImageBlock:^(UIImage *image, NSData *imageData) {
// This setImageBlock may not called from main queue
SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData]; SDImageFormat imageFormat = [NSData sd_imageFormatForImageData:imageData];
FLAnimatedImage *animatedImage;
if (imageFormat == SDImageFormatGIF) { if (imageFormat == SDImageFormatGIF) {
weakSelf.animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData]; animatedImage = [FLAnimatedImage animatedImageWithGIFData:imageData];
}
dispatch_main_async_safe(^{
if (animatedImage) {
weakSelf.animatedImage = animatedImage;
weakSelf.image = nil; weakSelf.image = nil;
} else { } else {
weakSelf.image = image; weakSelf.image = image;
weakSelf.animatedImage = nil; weakSelf.animatedImage = nil;
} }
});
} }
progress:progressBlock progress:progressBlock
completed:completedBlock]; completed:completedBlock
context:@{SDWebImageInternalSetImageInGlobalQueueKey: @(YES)}];
} }
@end @end

View File

@ -11,13 +11,13 @@
/** /**
Global object holding the array of coders, so that we avoid passing them from object to object. Global object holding the array of coders, so that we avoid passing them from object to object.
Uses a priority queue behind scenes, which means the latest added coders have priority. Uses a priority queue behind scenes, which means the latest added coders have the highest priority.
This is done so when encoding/decoding something, we go through the list and ask each coder if they can handle the current data. This is done so when encoding/decoding something, we go through the list and ask each coder if they can handle the current data.
That way, users can add their custom coders while preserving our existing prebuilt ones That way, users can add their custom coders while preserving our existing prebuilt ones
Note: the `coders` getter will return the coders in their reversed order Note: the `coders` getter will return the coders in their reversed order
Example: Example:
- by default we internally set coders = `IOCoder`, `WebPCoder` - by default we internally set coders = `IOCoder`, `WebPCoder`. (`GIFCoder` is not recommended to add only if you want to get GIF support without `FLAnimatedImage`)
- calling `coders` will return `@[WebPCoder, IOCoder]` - calling `coders` will return `@[WebPCoder, IOCoder]`
- call `[addCoder:[MyCrazyCoder new]]` - call `[addCoder:[MyCrazyCoder new]]`
- calling `coders` now returns `@[MyCrazyCoder, WebPCoder, IOCoder]` - calling `coders` now returns `@[MyCrazyCoder, WebPCoder, IOCoder]`

View File

@ -13,7 +13,8 @@
Built in coder using ImageIO that supports GIF encoding/decoding Built in coder using ImageIO that supports GIF encoding/decoding
@note `SDWebImageIOCoder` supports GIF but only as static (will use the 1st frame). @note `SDWebImageIOCoder` supports GIF but only as static (will use the 1st frame).
@note Use `SDWebImageGIFCoder` for fully animated GIFs - less performant than `FLAnimatedImage` @note Use `SDWebImageGIFCoder` for fully animated GIFs - less performant than `FLAnimatedImage`
@note The recommended approach for animated GIFs is using `FLAnimatedImage` @note If you decide to make all `UIImageView`(including `FLAnimatedImageView`) instance support GIF. You should add this coder to `SDWebImageCodersManager` and make sure that it has a higher priority than `SDWebImageIOCoder`
@note The recommended approach for animated GIFs is using `FLAnimatedImage`. It's more performant than `UIImageView` for GIF displaying
*/ */
@interface SDWebImageGIFCoder : NSObject <SDWebImageCoder> @interface SDWebImageGIFCoder : NSObject <SDWebImageCoder>

View File

@ -100,7 +100,7 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
SDImageFormat format = [NSData sd_imageFormatForImageData:data]; SDImageFormat format = [NSData sd_imageFormatForImageData:data];
if (format == SDImageFormatGIF) { if (format == SDImageFormatGIF) {
// static single GIF need to be created animated for FLAnimatedImageView logic // static single GIF need to be created animated for `FLAnimatedImage` logic
// GIF does not support EXIF image orientation // GIF does not support EXIF image orientation
image = [UIImage animatedImageWithImages:@[image] duration:image.duration]; image = [UIImage animatedImageWithImages:@[image] duration:image.duration];
return image; return image;

View File

@ -12,6 +12,8 @@
#import "SDWebImageManager.h" #import "SDWebImageManager.h"
FOUNDATION_EXPORT NSString * _Nonnull const SDWebImageInternalSetImageInGlobalQueueKey;
typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData); typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable imageData);
@interface UIView (WebCache) @interface UIView (WebCache)
@ -50,6 +52,34 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock; completed:(nullable SDExternalCompletionBlock)completedBlock;
/**
* Set the imageView `image` with an `url` and optionally a placeholder image.
*
* The download is asynchronous and cached.
*
* @param url The url for the image.
* @param placeholder The image to be set initially, until the image request finishes.
* @param options The options to use when downloading the image. @see SDWebImageOptions for the possible values.
* @param operationKey A string to be used as the operation key. If nil, will use the class name
* @param setImageBlock Block used for custom set image code
* @param progressBlock A block called while image is downloading
* @note the progress block is executed on a background queue
* @param completedBlock A block called when operation has been completed. This block has no return value
* and takes the requested UIImage as first parameter. In case of error the image parameter
* is nil and the second parameter may contain an NSError. The third parameter is a Boolean
* indicating if the image was retrieved from the local cache or from the network.
* The fourth parameter is the original image url.
* @param context A context with extra information to perform specify changes or processes.
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context;
/** /**
* Cancel the current download * Cancel the current download
*/ */

View File

@ -13,6 +13,8 @@
#import "objc/runtime.h" #import "objc/runtime.h"
#import "UIView+WebCacheOperation.h" #import "UIView+WebCacheOperation.h"
NSString * const SDWebImageInternalSetImageInGlobalQueueKey = @"setImageInGlobalQueue";
static char imageURLKey; static char imageURLKey;
#if SD_UIKIT #if SD_UIKIT
@ -34,6 +36,17 @@ static char TAG_ACTIVITY_SHOW;
setImageBlock:(nullable SDSetImageBlock)setImageBlock setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock { completed:(nullable SDExternalCompletionBlock)completedBlock {
return [self sd_internalSetImageWithURL:url placeholderImage:placeholder options:options operationKey:operationKey setImageBlock:setImageBlock progress:progressBlock completed:completedBlock context:nil];
}
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
operationKey:(nullable NSString *)operationKey
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDWebImageDownloaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock
context:(nullable NSDictionary *)context {
NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]); NSString *validOperationKey = operationKey ?: NSStringFromClass([self class]);
[self sd_cancelImageLoadOperationWithKey:validOperationKey]; [self sd_cancelImageLoadOperationWithKey:validOperationKey];
objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC); objc_setAssociatedObject(self, &imageURLKey, url, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
@ -54,28 +67,48 @@ static char TAG_ACTIVITY_SHOW;
id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) { id <SDWebImageOperation> operation = [SDWebImageManager.sharedManager loadImageWithURL:url options:options progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
__strong __typeof (wself) sself = wself; __strong __typeof (wself) sself = wself;
[sself sd_removeActivityIndicator]; [sself sd_removeActivityIndicator];
if (!sself) { if (!sself) { return; }
return; BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
} BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
dispatch_main_async_safe(^{ (!image && !(options & SDWebImageDelayPlaceholder)));
if (!sself) { SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
return; if (!sself) { return; }
} if (!shouldNotSetImage) {
if (image && (options & SDWebImageAvoidAutoSetImage) && completedBlock) {
completedBlock(image, error, cacheType, url);
return;
} else if (image) {
[sself sd_setImage:image imageData:data basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout];
} else {
if ((options & SDWebImageDelayPlaceholder)) {
[sself sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock];
[sself sd_setNeedsLayout]; [sself sd_setNeedsLayout];
} }
} if (completedBlock && shouldCallCompletedBlock) {
if (completedBlock && finished) {
completedBlock(image, error, cacheType, url); completedBlock(image, error, cacheType, url);
} }
};
// case 1a: we got an image, but the SDWebImageAvoidAutoSetImage flag is set
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
return;
}
UIImage *targetImage = nil;
NSData *targetData = nil;
if (image) {
// case 2a: we got an image and the SDWebImageAvoidAutoSetImage is not set
targetImage = image;
targetData = data;
} else if (options & SDWebImageDelayPlaceholder) {
// case 2b: we got no image and the SDWebImageDelayPlaceholder flag is set
targetImage = placeholder;
targetData = nil;
}
BOOL shouldUseGlobalQueue = NO;
if (context && [context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey]) {
shouldUseGlobalQueue = [[context valueForKey:SDWebImageInternalSetImageInGlobalQueueKey] boolValue];
}
dispatch_queue_t targetQueue = shouldUseGlobalQueue ? dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0) : dispatch_get_main_queue();
dispatch_async(targetQueue, ^{
[sself sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock];
dispatch_main_async_safe(callCompletedBlockClojure);
}); });
}]; }];
[self sd_setImageLoadOperation:operation forKey:validOperationKey]; [self sd_setImageLoadOperation:operation forKey:validOperationKey];