Skip to content

Commit

Permalink
Avoid sending out multiple requests for the same image
Browse files Browse the repository at this point in the history
Reviewed By: majak

Differential Revision: D3755074

fbshipit-source-id: fea782fcb99e6b977fb52231d378aaab4685d480
  • Loading branch information
javache authored and Facebook Github Bot committed Aug 26, 2016
1 parent dadfe40 commit 86fbf23
Showing 1 changed file with 48 additions and 35 deletions.
83 changes: 48 additions & 35 deletions Libraries/Image/RCTImageView.m
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,6 @@ static BOOL RCTShouldReloadImageForSizeChange(CGSize currentSize, CGSize idealSi

@interface RCTImageView ()

@property (nonatomic, strong) RCTImageSource *imageSource;
@property (nonatomic, copy) RCTDirectEventBlock onLoadStart;
@property (nonatomic, copy) RCTDirectEventBlock onProgress;
@property (nonatomic, copy) RCTDirectEventBlock onError;
Expand All @@ -64,6 +63,13 @@ @interface RCTImageView ()
@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;

/**
Expand Down Expand Up @@ -98,10 +104,10 @@ - (void)dealloc

RCT_NOT_IMPLEMENTED(- (instancetype)init)

- (void)updateImage
- (void)updateWithImage:(UIImage *)image
{
UIImage *image = self.image;
if (!image) {
self.image = nil;
return;
}

Expand All @@ -116,6 +122,7 @@ - (void)updateImage
// 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;
Expand All @@ -126,9 +133,8 @@ - (void)updateImage
- (void)setImage:(UIImage *)image
{
image = image ?: _defaultImage;
if (image != super.image) {
super.image = image;
[self updateImage];
if (image != self.image) {
[self updateWithImage:image];
}
}

Expand All @@ -150,7 +156,7 @@ - (void)setCapInsets:(UIEdgeInsets)capInsets
[self reloadImage];
} else {
_capInsets = capInsets;
[self updateImage];
[self updateWithImage:self.image];
}
}
}
Expand All @@ -159,7 +165,7 @@ - (void)setRenderingMode:(UIImageRenderingMode)renderingMode
{
if (_renderingMode != renderingMode) {
_renderingMode = renderingMode;
[self updateImage];
[self updateWithImage:self.image];
}
}

Expand All @@ -171,12 +177,6 @@ - (void)setImageSources:(NSArray<RCTImageSource *> *)imageSources
}
}

- (BOOL)sourceNeedsReload
{
// If capInsets are set, image doesn't need reloading when resized
return UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero);
}

- (void)setResizeMode:(RCTResizeMode)resizeMode
{
if (_resizeMode != resizeMode) {
Expand All @@ -190,7 +190,7 @@ - (void)setResizeMode:(RCTResizeMode)resizeMode
self.contentMode = (UIViewContentMode)resizeMode;
}

if ([self sourceNeedsReload]) {
if ([self shouldReloadImageSourceAfterResize]) {
[self reloadImage];
}
}
Expand All @@ -203,6 +203,8 @@ - (void)cancelImageLoad
previousCancellationBlock();
_reloadImageCancellationBlock = nil;
}

_pendingImageSource = nil;
}

- (void)clearImage
Expand All @@ -229,6 +231,7 @@ - (RCTImageSource *)imageSourceForSize:(CGSize)size
if (![self hasMultipleSources]) {
return _imageSources.firstObject;
}

// Need to wait for layout pass before deciding.
if (CGSizeEqualToSize(size, CGSizeZero)) {
return nil;
Expand All @@ -252,19 +255,29 @@ - (RCTImageSource *)imageSourceForSize:(CGSize)size
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
{
[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);
}
Expand All @@ -284,7 +297,7 @@ - (void)reloadImage
if (!UIEdgeInsetsEqualToEdgeInsets(_capInsets, UIEdgeInsetsZero)) {
// Don't resize images that use capInsets
imageSize = CGSizeZero;
imageScale = _imageSource.scale;
imageScale = source.scale;
}

__weak RCTImageView *weakSelf = self;
Expand All @@ -307,7 +320,7 @@ - (void)reloadImage

- (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;
}
Expand All @@ -323,6 +336,9 @@ - (void)imageLoaderLoadedImage:(UIImage *)loadedImage error:(NSError *)error for
}

void (^setImageBlock)(UIImage *) = ^(UIImage *image) {
self->_imageSource = source;
self->_pendingImageSource = nil;

if (image.reactKeyframeAnimation) {
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else {
Expand Down Expand Up @@ -361,27 +377,24 @@ - (void)reactSetFrame:(CGRect)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;
[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];
}
}
}
}
Expand All @@ -396,7 +409,7 @@ - (void)didMoveToWindow
// 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];
}
}
Expand Down

0 comments on commit 86fbf23

Please sign in to comment.