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 ()
@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];
}
}