Add some defensive cleanup of cancelBlock in RCTImageLoader

Reviewed By: mmmulani

Differential Revision: D3742177

fbshipit-source-id: 1cd16c2519052ec5811ecadf2530a5846b4ae1bc
This commit is contained in:
Pieter De Baets 2016-08-19 10:31:42 -07:00 коммит произвёл Facebook Github Bot 0
Родитель 0082517a6c
Коммит 14188289fc
4 изменённых файлов: 102 добавлений и 86 удалений

Просмотреть файл

@ -294,10 +294,6 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
progressBlock:(RCTImageLoaderProgressBlock)progressHandler progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate))completionBlock 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]; NSMutableURLRequest *mutableRequest = [request mutableCopy];
[NSURLProtocol setProperty:@"RCTImageLoader" [NSURLProtocol setProperty:@"RCTImageLoader"
@ -316,9 +312,14 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ? BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ?
[loadHandler requiresScheduling] : YES; [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) { void (^completionHandler)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate) {
cancelLoad = nil;
BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ?
[loadHandler shouldCacheLoadedImages] : YES; [loadHandler shouldCacheLoadedImages] : YES;
// If we've received an image, we should try to set it synchronously, // If we've received an image, we should try to set it synchronously,
// if it's data, do decoding on a background thread. // if it's data, do decoding on a background thread.
if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) { if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) {
@ -352,6 +353,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
[self setUp]; [self setUp];
} }
__weak RCTImageLoader *weakSelf = self;
dispatch_async(_URLRequestQueue, ^{ dispatch_async(_URLRequestQueue, ^{
__typeof(self) strongSelf = weakSelf; __typeof(self) strongSelf = weakSelf;
if (cancelled || !strongSelf) { if (cancelled || !strongSelf) {
@ -364,7 +366,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
scale:scale scale:scale
resizeMode:resizeMode resizeMode:resizeMode
progressHandler:progressHandler progressHandler:progressHandler
completionHandler:^(NSError *error, UIImage *image){ completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil); completionHandler(error, image, nil);
}]; }];
} else { } else {
@ -378,6 +380,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
return ^{ return ^{
if (cancelLoad) { if (cancelLoad) {
cancelLoad(); cancelLoad();
cancelLoad = nil;
} }
OSAtomicOr32Barrier(1, &cancelled); OSAtomicOr32Barrier(1, &cancelled);
}; };
@ -464,7 +467,6 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
// Prepare for next task // Prepare for next task
[strongSelf dequeueTasks]; [strongSelf dequeueTasks];
}); });
}]; }];
task.downloadProgressBlock = progressHandler; task.downloadProgressBlock = progressHandler;
@ -488,13 +490,19 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
scale:(CGFloat)scale scale:(CGFloat)scale
clipped:(BOOL)clipped clipped:(BOOL)clipped
resizeMode:(RCTResizeMode)resizeMode resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler progressBlock:(RCTImageLoaderProgressBlock)progressBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{ {
__block volatile uint32_t cancelled = 0; __block volatile uint32_t cancelled = 0;
__block void(^cancelLoad)(void) = nil; __block dispatch_block_t cancelLoad = nil;
__weak RCTImageLoader *weakSelf = self; 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) { void (^completionHandler)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
__typeof(self) strongSelf = weakSelf; __typeof(self) strongSelf = weakSelf;
if (cancelled || !strongSelf) { if (cancelled || !strongSelf) {
@ -502,6 +510,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
} }
if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) { if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
cancelLoad = nil;
completionBlock(error, imageOrData); completionBlock(error, imageOrData);
return; return;
} }
@ -509,27 +518,29 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
// Check decoded image cache // Check decoded image cache
if (cacheResult) { if (cacheResult) {
UIImage *image = [[strongSelf imageCache] imageForUrl:imageURLRequest.URL.absoluteString UIImage *image = [[strongSelf imageCache] imageForUrl:imageURLRequest.URL.absoluteString
size:size size:size
scale:scale scale:scale
resizeMode:resizeMode resizeMode:resizeMode
responseDate:fetchDate]; responseDate:fetchDate];
if (image) { if (image) {
cancelLoad = nil;
completionBlock(nil, image); completionBlock(nil, image);
return; return;
} }
} }
// Store decoded image in cache RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {
RCTImageLoaderCompletionBlock cacheResultHandler = ^(NSError *error_, UIImage *image) { if (cacheResult && image) {
if (image) { // Store decoded image in cache
[[strongSelf imageCache] addImageToCache:image [[strongSelf imageCache] addImageToCache:image
URL:imageURLRequest.URL.absoluteString URL:imageURLRequest.URL.absoluteString
size:size size:size
scale:scale scale:scale
resizeMode:resizeMode resizeMode:resizeMode
responseDate:fetchDate]; responseDate:fetchDate];
} }
cancelLoad = nil;
completionBlock(error_, image); completionBlock(error_, image);
}; };
@ -538,21 +549,16 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
scale:scale scale:scale
clipped:clipped clipped:clipped
resizeMode:resizeMode resizeMode:resizeMode
completionBlock:cacheResult ? cacheResultHandler: completionBlock]; completionBlock:decodeCompletionHandler];
}; };
cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest
size:size size:size
scale:scale scale:scale
resizeMode:resizeMode resizeMode:resizeMode
progressBlock:progressHandler progressBlock:progressBlock
completionBlock:completionHandler]; completionBlock:completionHandler];
return ^{ return cancellationBlock;
if (cancelLoad) {
cancelLoad();
}
OSAtomicOr32Barrier(1, &cancelled);
};
} }
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data

Просмотреть файл

@ -273,8 +273,11 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
} }
RCTImageSource *source = _imageSource; RCTImageSource *source = _imageSource;
CGFloat blurRadius = _blurRadius;
__weak RCTImageView *weakSelf = self; __weak RCTImageView *weakSelf = self;
RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *loadedImage) {
[weakSelf imageLoaderLoadedImage:loadedImage error:error forImageSource:source];
};
_reloadImageCancellationBlock = _reloadImageCancellationBlock =
[_bridge.imageLoader loadImageWithURLRequest:source.request [_bridge.imageLoader loadImageWithURLRequest:source.request
size:imageSize size:imageSize
@ -282,57 +285,64 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
clipped:NO clipped:NO
resizeMode:_resizeMode resizeMode:_resizeMode
progressBlock:progressHandler progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *loadedImage) { completionBlock:completionHandler];
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);
});
}
}];
} else { } else {
[self clearImage]; [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 - (void)reactSetFrame:(CGRect)frame
{ {
[super reactSetFrame:frame]; [super reactSetFrame:frame];

Просмотреть файл

@ -49,13 +49,13 @@ RCT_EXPORT_METHOD(getSize:(NSURLRequest *)request
errorBlock:(RCTResponseErrorBlock)errorBlock) errorBlock:(RCTResponseErrorBlock)errorBlock)
{ {
[self.bridge.imageLoader getImageSizeForURLRequest:request [self.bridge.imageLoader getImageSizeForURLRequest:request
block:^(NSError *error, CGSize size) { block:^(NSError *error, CGSize size) {
if (error) { if (error) {
errorBlock(error); errorBlock(error);
} else { } else {
successBlock(@[@(size.width), @(size.height)]); successBlock(@[@(size.width), @(size.height)]);
} }
}]; }];
} }
RCT_EXPORT_METHOD(prefetchImage:(NSURLRequest *)request RCT_EXPORT_METHOD(prefetchImage:(NSURLRequest *)request

Просмотреть файл

@ -78,9 +78,9 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)cancel - (void)cancel
{ {
_status = RCTNetworkTaskFinished; _status = RCTNetworkTaskFinished;
__strong id strongToken = _requestToken; id token = _requestToken;
if (strongToken && [_handler respondsToSelector:@selector(cancelRequest:)]) { if (token && [_handler respondsToSelector:@selector(cancelRequest:)]) {
[_handler cancelRequest:strongToken]; [_handler cancelRequest:token];
} }
[self invalidate]; [self invalidate];
} }