Skip to content

Commit

Permalink
Add some defensive cleanup of cancelBlock in RCTImageLoader
Browse files Browse the repository at this point in the history
Reviewed By: mmmulani

Differential Revision: D3742177

fbshipit-source-id: 1cd16c2519052ec5811ecadf2530a5846b4ae1bc
  • Loading branch information
javache authored and Facebook Github Bot 0 committed Aug 19, 2016
1 parent 0082517 commit 1418828
Show file tree
Hide file tree
Showing 4 changed files with 102 additions and 86 deletions.
64 changes: 35 additions & 29 deletions Libraries/Image/RCTImageLoader.m
Original file line number Diff line number Diff line change
Expand Up @@ -294,10 +294,6 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate))completionBlock
{
__block volatile uint32_t cancelled = 0;
__block dispatch_block_t cancelLoad = nil;
__weak RCTImageLoader *weakSelf = self;

{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[NSURLProtocol setProperty:@"RCTImageLoader"
Expand All @@ -316,9 +312,14 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ?
[loadHandler requiresScheduling] : YES;

__block volatile uint32_t cancelled = 0;
__block dispatch_block_t cancelLoad = nil;
void (^completionHandler)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate) {
cancelLoad = nil;

BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ?
[loadHandler shouldCacheLoadedImages] : YES;

// If we've received an image, we should try to set it synchronously,
// if it's data, do decoding on a background thread.
if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) {
Expand Down Expand Up @@ -352,6 +353,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
[self setUp];
}

__weak RCTImageLoader *weakSelf = self;
dispatch_async(_URLRequestQueue, ^{
__typeof(self) strongSelf = weakSelf;
if (cancelled || !strongSelf) {
Expand All @@ -364,7 +366,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
scale:scale
resizeMode:resizeMode
progressHandler:progressHandler
completionHandler:^(NSError *error, UIImage *image){
completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil);
}];
} else {
Expand All @@ -378,6 +380,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest
return ^{
if (cancelLoad) {
cancelLoad();
cancelLoad = nil;
}
OSAtomicOr32Barrier(1, &cancelled);
};
Expand Down Expand Up @@ -464,7 +467,6 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
// Prepare for next task
[strongSelf dequeueTasks];
});

}];

task.downloadProgressBlock = progressHandler;
Expand All @@ -488,48 +490,57 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image
scale:(CGFloat)scale
clipped:(BOOL)clipped
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
__block volatile uint32_t cancelled = 0;
__block void(^cancelLoad)(void) = nil;
__weak RCTImageLoader *weakSelf = self;
__block dispatch_block_t cancelLoad = nil;
dispatch_block_t cancellationBlock = ^{
if (cancelLoad) {
cancelLoad();
}
OSAtomicOr32Barrier(1, &cancelled);
};

__weak RCTImageLoader *weakSelf = self;
void (^completionHandler)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
__typeof(self) strongSelf = weakSelf;
if (cancelled || !strongSelf) {
return;
}

if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
cancelLoad = nil;
completionBlock(error, imageOrData);
return;
}

// Check decoded image cache
if (cacheResult) {
UIImage *image = [[strongSelf imageCache] imageForUrl:imageURLRequest.URL.absoluteString
size:size
scale:scale
resizeMode:resizeMode
responseDate:fetchDate];
size:size
scale:scale
resizeMode:resizeMode
responseDate:fetchDate];
if (image) {
cancelLoad = nil;
completionBlock(nil, image);
return;
}
}

// Store decoded image in cache
RCTImageLoaderCompletionBlock cacheResultHandler = ^(NSError *error_, UIImage *image) {
if (image) {
RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {
if (cacheResult && image) {
// Store decoded image in cache
[[strongSelf imageCache] addImageToCache:image
URL:imageURLRequest.URL.absoluteString
size:size
scale:scale
resizeMode:resizeMode
responseDate:fetchDate];
URL:imageURLRequest.URL.absoluteString
size:size
scale:scale
resizeMode:resizeMode
responseDate:fetchDate];
}

cancelLoad = nil;
completionBlock(error_, image);
};

Expand All @@ -538,21 +549,16 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image
scale:scale
clipped:clipped
resizeMode:resizeMode
completionBlock:cacheResult ? cacheResultHandler: completionBlock];
completionBlock:decodeCompletionHandler];
};

cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest
size:size
scale:scale
resizeMode:resizeMode
progressBlock:progressHandler
progressBlock:progressBlock
completionBlock:completionHandler];
return ^{
if (cancelLoad) {
cancelLoad();
}
OSAtomicOr32Barrier(1, &cancelled);
};
return cancellationBlock;
}

- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
Expand Down
104 changes: 57 additions & 47 deletions Libraries/Image/RCTImageView.m
Original file line number Diff line number Diff line change
Expand Up @@ -273,66 +273,76 @@ - (void)reloadImage
}

RCTImageSource *source = _imageSource;
CGFloat blurRadius = _blurRadius;
__weak RCTImageView *weakSelf = self;
RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *loadedImage) {
[weakSelf imageLoaderLoadedImage:loadedImage error:error forImageSource:source];
};

_reloadImageCancellationBlock =
[_bridge.imageLoader loadImageWithURLRequest:source.request
size:imageSize
scale:imageScale
clipped:NO
resizeMode:_resizeMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *loadedImage) {

RCTImageView *strongSelf = weakSelf;
void (^setImageBlock)(UIImage *) = ^(UIImage *image) {
if (![source isEqual:strongSelf.imageSource]) {
// Bail out if source has changed since we started loading
return;
}
if (image.reactKeyframeAnimation) {
[strongSelf.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else {
[strongSelf.layer removeAnimationForKey:@"contents"];
strongSelf.image = image;
}
if (error) {
if (strongSelf->_onError) {
strongSelf->_onError(@{ @"error": error.localizedDescription });
}
} else {
if (strongSelf->_onLoad) {
strongSelf->_onLoad(nil);
}
}
if (strongSelf->_onLoadEnd) {
strongSelf->_onLoadEnd(nil);
}
};

if (blurRadius > __FLT_EPSILON__) {
// Blur on a background thread to avoid blocking interaction
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = RCTBlurredImageWithRadius(loadedImage, blurRadius);
RCTExecuteOnMainQueue(^{
setImageBlock(image);
});
});
} else {
// No blur, so try to set the image on the main thread synchronously to minimize image
// flashing. (For instance, if this view gets attached to a window, then -didMoveToWindow
// calls -reloadImage, and we want to set the image synchronously if possible so that the
// image property is set in the same CATransaction that attaches this view to the window.)
RCTExecuteOnMainQueue(^{
setImageBlock(loadedImage);
});
}
}];
completionBlock:completionHandler];
} else {
[self clearImage];
}
}

- (void)imageLoaderLoadedImage:(UIImage *)loadedImage error:(NSError *)error forImageSource:(RCTImageSource *)source
{
if (![source isEqual:_imageSource]) {
// Bail out if source has changed since we started loading
return;
}

if (error) {
if (_onError) {
_onError(@{ @"error": error.localizedDescription });
}
if (_onLoadEnd) {
_onLoadEnd(nil);
}
return;
}

void (^setImageBlock)(UIImage *) = ^(UIImage *image) {
if (image.reactKeyframeAnimation) {
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
} else {
[self.layer removeAnimationForKey:@"contents"];
self.image = image;
}

if (self->_onLoad) {
self->_onLoad(nil);
}
if (self->_onLoadEnd) {
self->_onLoadEnd(nil);
}
};

if (_blurRadius > __FLT_EPSILON__) {
// Blur on a background thread to avoid blocking interaction
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *blurredImage = RCTBlurredImageWithRadius(loadedImage, self->_blurRadius);
RCTExecuteOnMainQueue(^{
setImageBlock(blurredImage);
});
});
} else {
// No blur, so try to set the image on the main thread synchronously to minimize image
// flashing. (For instance, if this view gets attached to a window, then -didMoveToWindow
// calls -reloadImage, and we want to set the image synchronously if possible so that the
// image property is set in the same CATransaction that attaches this view to the window.)
RCTExecuteOnMainQueue(^{
setImageBlock(loadedImage);
});
}
}

- (void)reactSetFrame:(CGRect)frame
{
[super reactSetFrame:frame];
Expand Down
14 changes: 7 additions & 7 deletions Libraries/Image/RCTImageViewManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -49,13 +49,13 @@ - (UIView *)view
errorBlock:(RCTResponseErrorBlock)errorBlock)
{
[self.bridge.imageLoader getImageSizeForURLRequest:request
block:^(NSError *error, CGSize size) {
if (error) {
errorBlock(error);
} else {
successBlock(@[@(size.width), @(size.height)]);
}
}];
block:^(NSError *error, CGSize size) {
if (error) {
errorBlock(error);
} else {
successBlock(@[@(size.width), @(size.height)]);
}
}];
}

RCT_EXPORT_METHOD(prefetchImage:(NSURLRequest *)request
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Network/RCTNetworkTask.m
Original file line number Diff line number Diff line change
Expand Up @@ -78,9 +78,9 @@ - (void)start
- (void)cancel
{
_status = RCTNetworkTaskFinished;
__strong id strongToken = _requestToken;
if (strongToken && [_handler respondsToSelector:@selector(cancelRequest:)]) {
[_handler cancelRequest:strongToken];
id token = _requestToken;
if (token && [_handler respondsToSelector:@selector(cancelRequest:)]) {
[_handler cancelRequest:token];
}
[self invalidate];
}
Expand Down

0 comments on commit 1418828

Please sign in to comment.