From 86fbf2386b155a6f2fce2d42bc367882170194ca Mon Sep 17 00:00:00 2001 From: Pieter De Baets Date: Fri, 26 Aug 2016 13:49:21 -0700 Subject: [PATCH] Avoid sending out multiple requests for the same image Reviewed By: majak Differential Revision: D3755074 fbshipit-source-id: fea782fcb99e6b977fb52231d378aaab4685d480 --- Libraries/Image/RCTImageView.m | 83 ++++++++++++++++++++-------------- 1 file changed, 48 insertions(+), 35 deletions(-) diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 459ade2c0..0948dde11 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -52,7 +52,6 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source) @interface RCTImageView () -@property (nonatomic, strong) RCTImageSource *imageSource; @property (nonatomic, copy) RCTDirectEventBlock onLoadStart; @property (nonatomic, copy) RCTDirectEventBlock onProgress; @property (nonatomic, copy) RCTDirectEventBlock onError; @@ -64,6 +63,13 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source) @implementation RCTImageView { __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; /** @@ -98,10 +104,10 @@ static NSDictionary *onLoadParamsForSource(RCTImageSource *source) RCT_NOT_IMPLEMENTED(- (instancetype)init) -- (void)updateImage +- (void)updateWithImage:(UIImage *)image { - UIImage *image = self.image; if (!image) { + self.image = nil; 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 image = [image resizableImageWithCapInsets:_capInsets resizingMode:UIImageResizingModeStretch]; } + // Apply trilinear filtering to smooth out mis-sized images self.layer.minificationFilter = kCAFilterTrilinear; self.layer.magnificationFilter = kCAFilterTrilinear; @@ -126,9 +133,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (void)setImage:(UIImage *)image { image = image ?: _defaultImage; - if (image != super.image) { - super.image = image; - [self updateImage]; + if (image != self.image) { + [self updateWithImage:image]; } } @@ -150,7 +156,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) [self reloadImage]; } else { _capInsets = capInsets; - [self updateImage]; + [self updateWithImage:self.image]; } } } @@ -159,7 +165,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) { if (_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 { if (_resizeMode != resizeMode) { @@ -190,7 +190,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) self.contentMode = (UIViewContentMode)resizeMode; } - if ([self sourceNeedsReload]) { + if ([self shouldReloadImageSourceAfterResize]) { [self reloadImage]; } } @@ -203,6 +203,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) previousCancellationBlock(); _reloadImageCancellationBlock = nil; } + + _pendingImageSource = nil; } - (void)clearImage @@ -229,6 +231,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) if (![self hasMultipleSources]) { return _imageSources.firstObject; } + // Need to wait for layout pass before deciding. if (CGSizeEqualToSize(size, CGSizeZero)) { return nil; @@ -252,9 +255,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) 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 @@ -262,9 +275,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) [self cancelImageLoad]; 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) { _onLoadStart(nil); } @@ -284,7 +297,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) { // Don't resize images that use capInsets imageSize = CGSizeZero; - imageScale = _imageSource.scale; + imageScale = source.scale; } __weak RCTImageView *weakSelf = self; @@ -307,7 +320,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) - (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 return; } @@ -323,6 +336,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) } void (^setImageBlock)(UIImage *) = ^(UIImage *image) { + self->_imageSource = source; + self->_pendingImageSource = nil; + if (image.reactKeyframeAnimation) { [self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"]; } else { @@ -361,27 +377,24 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) { [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; [self reloadImage]; - } else if ([self sourceNeedsReload]) { + } else if ([self shouldReloadImageSourceAfterResize]) { CGSize imageSize = self.image.size; CGSize idealSize = RCTTargetSize(imageSize, self.image.scale, frame.size, RCTScreenScale(), (RCTResizeMode)self.contentMode, YES); - if ([self desiredImageSourceDidChange]) { - // Reload to swap to the proper image source. + if (RCTShouldReloadImageForSizeChange(imageSize, idealSize) && + RCTShouldReloadImageForSizeChange(_targetSize, idealSize)) { + RCTLogInfo(@"Reloading image %@ as size %@", [_imageSources firstObject].request.URL.absoluteString, NSStringFromCGSize(idealSize)); + + // 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. _targetSize = 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 - // size, reload the asset in case there is a better size available. - _targetSize = idealSize; - [self reloadImage]; - } } } } @@ -396,7 +409,7 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init) // requests that have gotten "stuck" from the queue, unblocking other images // from loading. [self cancelImageLoad]; - } else if (!self.image || self.image == _defaultImage) { + } else if ([self shouldChangeImageSource]) { [self reloadImage]; } }