Avoid sending out multiple requests for the same image

Reviewed By: majak

Differential Revision: D3755074

fbshipit-source-id: fea782fcb99e6b977fb52231d378aaab4685d480
This commit is contained in:
Pieter De Baets 2016-08-26 13:49:21 -07:00 committed by Facebook Github Bot
parent dadfe40f55
commit 86fbf2386b
1 changed files with 48 additions and 35 deletions

View File

@ -52,7 +52,6 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source)
@interface RCTImageView () @interface RCTImageView ()
@property (nonatomic, strong) RCTImageSource *imageSource;
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart; @property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
@property (nonatomic, copy) RCTDirectEventBlock onProgress; @property (nonatomic, copy) RCTDirectEventBlock onProgress;
@property (nonatomic, copy) RCTDirectEventBlock onError; @property (nonatomic, copy) RCTDirectEventBlock onError;
@ -64,6 +63,13 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source)
@implementation RCTImageView @implementation RCTImageView
{ {
__weak RCTBridge *_bridge; __weak RCTBridge *_bridge;
// The image source that's currently displayed
RCTImageSource *_imageSource;
// The image source that's being loaded from the network
RCTImageSource *_pendingImageSource;
CGSize _targetSize; CGSize _targetSize;
/** /**
@ -98,10 +104,10 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source)
RCT_NOT_IMPLEMENTED(- (instancetype)init) RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)updateImage - (void)updateWithImage:(UIImage *)image
{ {
UIImage *image = self.image;
if (!image) { if (!image) {
self.image = nil;
return; return;
} }
@ -116,6 +122,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
// Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired // Applying capInsets of 0 will switch the "resizingMode" of the image to "tile" which is undesired
image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch]; image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch];
} }
// Apply trilinear filtering to smooth out mis-sized images // Apply trilinear filtering to smooth out mis-sized images
self.layer.minificationFilter = kCAFilterTrilinear; self.layer.minificationFilter = kCAFilterTrilinear;
self.layer.magnificationFilter = kCAFilterTrilinear; self.layer.magnificationFilter = kCAFilterTrilinear;
@ -126,9 +133,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)setImage:(UIImage *)image - (void)setImage:(UIImage *)image
{ {
image = image ?: _defaultImage; image = image ?: _defaultImage;
if (image != super.image) { if (image != self.image) {
super.image = image; [self updateWithImage:image];
[self updateImage];
} }
} }
@ -150,7 +156,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
[self reloadImage]; [self reloadImage];
} else { } else {
_capInsets = capInsets; _capInsets = capInsets;
[self updateImage]; [self updateWithImage:self.image];
} }
} }
} }
@ -159,7 +165,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
{ {
if (_renderingMode != renderingMode) { if (_renderingMode != renderingMode) {
_renderingMode = renderingMode; _renderingMode = renderingMode;
[self updateImage]; [self updateWithImage:self.image];
} }
} }
@ -171,12 +177,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
} }
} }
- (BOOL)sourceNeedsReload
{
// If capInsets are set, image doesn't need reloading when resized
return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero);
}
- (void)setResizeMode:(RCTResizeMode)resizeMode - (void)setResizeMode:(RCTResizeMode)resizeMode
{ {
if (_resizeMode != resizeMode) { if (_resizeMode != resizeMode) {
@ -190,7 +190,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
self.contentMode = (UIViewContentMode)resizeMode; self.contentMode = (UIViewContentMode)resizeMode;
} }
if ([self sourceNeedsReload]) { if ([self shouldReloadImageSourceAfterResize]) {
[self reloadImage]; [self reloadImage];
} }
} }
@ -203,6 +203,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
previousCancellationBlock(); previousCancellationBlock();
_reloadImageCancellationBlock = nil; _reloadImageCancellationBlock = nil;
} }
_pendingImageSource = nil;
} }
- (void)clearImage - (void)clearImage
@ -229,6 +231,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
if (![self hasMultipleSources]) { if (![self hasMultipleSources]) {
return _imageSources.firstObject; return _imageSources.firstObject;
} }
// Need to wait for layout pass before deciding. // Need to wait for layout pass before deciding.
if (CGSizeEqualToSize(size, CGSizeZero)) { if (CGSizeEqualToSize(size, CGSizeZero)) {
return nil; return nil;
@ -252,9 +255,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
return bestSource; return bestSource;
} }
- (BOOL)desiredImageSourceDidChange - (BOOL)shouldReloadImageSourceAfterResize
{ {
return ![[self imageSourceForSize:self.frame.size] isEqual:_imageSource]; // If capInsets are set, image doesn't need reloading when resized
return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero);
}
- (BOOL)shouldChangeImageSource
{
// We need to reload if the desired image source is different from the current image
// source AND the image load that's pending
RCTImageSource *desiredImageSource = [self imageSourceForSize:self.frame.size];
return ![desiredImageSource isEqual:_imageSource] &&
![desiredImageSource isEqual:_pendingImageSource];
} }
- (void)reloadImage - (void)reloadImage
@ -262,9 +275,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
[self cancelImageLoad]; [self cancelImageLoad];
RCTImageSource *source = [self imageSourceForSize:self.frame.size]; RCTImageSource *source = [self imageSourceForSize:self.frame.size];
_imageSource = source; _pendingImageSource = source;
if (_imageSource && self.frame.size.width > 0 && self.frame.size.height > 0) { if (source && self.frame.size.width > 0 && self.frame.size.height > 0) {
if (_onLoadStart) { if (_onLoadStart) {
_onLoadStart(nil); _onLoadStart(nil);
} }
@ -284,7 +297,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) { if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
// Don't resize images that use capInsets // Don't resize images that use capInsets
imageSize = CGSizeZero; imageSize = CGSizeZero;
imageScale = _imageSource.scale; imageScale = source.scale;
} }
__weak RCTImageView *weakSelf = self; __weak RCTImageView *weakSelf = self;
@ -307,7 +320,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)imageLoaderLoadedImage:(UIImage *)loadedImage error:(NSError *)error forImageSource:(RCTImageSource *)source - (void)imageLoaderLoadedImage:(UIImage *)loadedImage error:(NSError *)error forImageSource:(RCTImageSource *)source
{ {
if (![source isEqual:_imageSource]) { if (![source isEqual:_pendingImageSource]) {
// Bail out if source has changed since we started loading // Bail out if source has changed since we started loading
return; return;
} }
@ -323,6 +336,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
} }
void (^setImageBlock)(UIImage *) = ^(UIImage *image) { void (^setImageBlock)(UIImage *) = ^(UIImage *image) {
self->_imageSource = source;
self->_pendingImageSource = nil;
if (image.reactKeyframeAnimation) { if (image.reactKeyframeAnimation) {
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"]; [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else { } else {
@ -361,21 +377,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
{ {
[super reactSetFrame:frame]; [super reactSetFrame:frame];
if (!self.image || self.image == _defaultImage) { // If we didn't load an image yet, or the new frame triggers a different image source
// to be loaded, reload to swap to the proper image source.
if ([self shouldChangeImageSource]) {
_targetSize = frame.size; _targetSize = frame.size;
[self reloadImage]; [self reloadImage];
} else if ([self sourceNeedsReload]) { } else if ([self shouldReloadImageSourceAfterResize]) {
CGSize imageSize = self.image.size; CGSize imageSize = self.image.size;
CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size, CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size,
RCTScreenScale(), (RCTResizeMode)self.contentMode, YES); RCTScreenScale(), (RCTResizeMode)self.contentMode, YES);
if ([self desiredImageSourceDidChange]) { if (RCTShouldReloadImageForSizeChange(imageSize, idealSize) &&
// Reload to swap to the proper image source. RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) {
_targetSize = idealSize; RCTLogInfo(@"Reloading image %@ as size %@", [_imageSources firstObject].request.URL.absoluteString, NSStringFromCGSize(idealSize));
[self reloadImage];
} else if (RCTShouldReloadImageForSizeChange(imageSize, idealSize)) {
if (RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) {
RCTLogInfo(@"[PERF IMAGEVIEW] Reloading image %@ as size %@", _imageSource.request.URL.absoluteString, NSStringFromCGSize(idealSize));
// If the existing image or an image being loaded are not the right // If the existing image or an image being loaded are not the right
// size, reload the asset in case there is a better size available. // size, reload the asset in case there is a better size available.
@ -384,7 +398,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
} }
} }
} }
}
- (void)didMoveToWindow - (void)didMoveToWindow
{ {
@ -396,7 +409,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
// requests that have gotten "stuck" from the queue, unblocking other images // requests that have gotten "stuck" from the queue, unblocking other images
// from loading. // from loading.
[self cancelImageLoad]; [self cancelImageLoad];
} else if (!self.image || self.image == _defaultImage) { } else if ([self shouldChangeImageSource]) {
[self reloadImage]; [self reloadImage];
} }
} }