Reduced work done on main thread by RCTImageLoader

Summary: public

Removed redundant calls to [RCTNetwork canHandleRequest] in release mode when loading images, and improved perf for handler lookups when running in debug mode.

Reviewed By: tadeuzagallo

Differential Revision: D2663307

fb-gh-sync-id: 13285154c1c3773b32dba7894d86d14992e2fd7d
This commit is contained in:
Nick Lockwood 2015-11-17 07:18:55 -08:00 committed by facebook-github-bot-0
parent c043c68e7e
commit 5b796cec34
4 changed files with 126 additions and 102 deletions

View File

@ -99,8 +99,11 @@ RCT_EXPORT_MODULE()
float previousPriority = 0;
id<RCTImageURLLoader> previousLoader = nil;
for (id<RCTImageURLLoader> loader in _loaders) {
float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0;
if (previousLoader && priority < previousPriority) {
return previousLoader;
}
if ([loader canLoadImageURL:URL]) {
float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0;
if (previousLoader) {
if (priority == previousPriority) {
RCTLogError(@"The RCTImageURLLoaders %@ and %@ both reported that"
@ -114,6 +117,7 @@ RCT_EXPORT_MODULE()
}
}
}
return previousLoader;
}
// Normal code path
@ -132,8 +136,11 @@ RCT_EXPORT_MODULE()
float previousPriority = 0;
id<RCTImageDataDecoder> previousDecoder = nil;
for (id<RCTImageDataDecoder> decoder in _decoders) {
float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0;
if (previousDecoder && priority < previousPriority) {
return previousDecoder;
}
if ([decoder canDecodeImageData:data]) {
float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0;
if (previousDecoder) {
if (priority == previousPriority) {
RCTLogError(@"The RCTImageDataDecoders %@ and %@ both reported that"
@ -148,6 +155,7 @@ RCT_EXPORT_MODULE()
}
}
}
return previousDecoder;
}
// Normal code path
@ -211,101 +219,101 @@ RCT_EXPORT_MODULE()
}
// Check if networking module is available
if (![_bridge respondsToSelector:@selector(networking)]) {
if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) {
RCTLogError(@"No suitable image URL loader found for %@. You may need to "
" import the RCTNetworking library in order to load images.",
imageTag);
return ^{};
}
// Use networking module to load image
if ([_bridge.networking canHandleRequest:request]) {
__weak RCTImageLoader *weakSelf = self;
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
RCTURLRequestCompletionBlock processResponse =
^(NSURLResponse *response, NSData *data, NSError *error) {
// Check for system errors
if (error) {
completionHandler(error, nil);
return;
} else if (!data) {
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
return;
}
// Check for http errors
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (statusCode != 200) {
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
code:statusCode
userInfo:nil], nil);
return;
}
}
// Decode image
decodeCancel = [weakSelf decodeImageData:data
size:size
scale:scale
resizeMode:resizeMode
completionBlock:completionHandler];
};
// Check for cached response before reloading
// TODO: move URL cache out of RCTImageLoader into its own module
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
if (cachedResponse) {
processResponse(cachedResponse.response, cachedResponse.data, nil);
return ^{};
}
// Add missing png extension
if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
request = mutableRequest;
}
// Download image
RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:
^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
completionHandler(error, nil);
return;
}
// Cache the response
// TODO: move URL cache out of RCTImageLoader into its own module
RCTImageLoader *strongSelf = weakSelf;
BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"];
[strongSelf->_cache storeCachedResponse:
[[NSCachedURLResponse alloc] initWithResponse:response
data:data
userInfo:nil
storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
forRequest:request];
// Process image data
processResponse(response, data, nil);
}];
task.downloadProgressBlock = progressHandler;
[task start];
return ^{
[task cancel];
if (decodeCancel) {
decodeCancel();
}
OSAtomicOr32Barrier(1, &cancelled);
};
// Check if networking module can load image
if (RCT_DEBUG && ![_bridge.networking canHandleRequest:request]) {
RCTLogError(@"No suitable image URL loader found for %@", imageTag);
return ^{};
}
RCTLogError(@"No suitable image URL loader found for %@", imageTag);
return ^{};
// Use networking module to load image
__weak RCTImageLoader *weakSelf = self;
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
RCTURLRequestCompletionBlock processResponse =
^(NSURLResponse *response, NSData *data, NSError *error) {
// Check for system errors
if (error) {
completionHandler(error, nil);
return;
} else if (!data) {
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil);
return;
}
// Check for http errors
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (statusCode != 200) {
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
code:statusCode
userInfo:nil], nil);
return;
}
}
// Decode image
decodeCancel = [weakSelf decodeImageData:data
size:size
scale:scale
resizeMode:resizeMode
completionBlock:completionHandler];
};
// Check for cached response before reloading
// TODO: move URL cache out of RCTImageLoader into its own module
NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
if (cachedResponse) {
processResponse(cachedResponse.response, cachedResponse.data, nil);
return ^{};
}
// Add missing png extension
if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
NSMutableURLRequest *mutableRequest = [request mutableCopy];
mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
request = mutableRequest;
}
// Download image
RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest:request completionBlock:
^(NSURLResponse *response, NSData *data, NSError *error) {
if (error) {
completionHandler(error, nil);
return;
}
// Cache the response
// TODO: move URL cache out of RCTImageLoader into its own module
RCTImageLoader *strongSelf = weakSelf;
BOOL isHTTPRequest = [request.URL.scheme hasPrefix:@"http"];
[strongSelf->_cache storeCachedResponse:
[[NSCachedURLResponse alloc] initWithResponse:response
data:data
userInfo:nil
storagePolicy:isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
forRequest:request];
// Process image data
processResponse(response, data, nil);
}];
task.downloadProgressBlock = progressHandler;
[task start];
return ^{
[task cancel];
if (decodeCancel) {
decodeCancel();
}
OSAtomicOr32Barrier(1, &cancelled);
};
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
@ -314,6 +322,11 @@ RCT_EXPORT_MODULE()
resizeMode:(UIViewContentMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionHandler
{
if (data.length == 0) {
completionHandler(RCTErrorWithMessage(@"No image data"), nil);
return ^{};
}
id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
if (imageDecoder) {

View File

@ -183,6 +183,19 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
resizeMode:self.contentMode
progressBlock:progressHandler
completionBlock:^(NSError *error, UIImage *image) {
if (error) {
if (_onError) {
_onError(@{ @"error": error.localizedDescription });
}
} else {
if (_onLoad) {
_onLoad(nil);
}
}
if (_onLoadEnd) {
_onLoadEnd(nil);
}
dispatch_async(dispatch_get_main_queue(), ^{
if (image.reactKeyframeAnimation) {
[self.layer addAnimation:image.reactKeyframeAnimation forKey:@"contents"];
@ -190,18 +203,6 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
[self.layer removeAnimationForKey:@"contents"];
self.image = image;
}
if (error) {
if (_onError) {
_onError(@{ @"error": error.localizedDescription });
}
} else {
if (_onLoad) {
_onLoad(nil);
}
}
if (_onLoadEnd) {
_onLoadEnd(nil);
}
});
}];
} else {

View File

@ -165,8 +165,11 @@ RCT_EXPORT_MODULE()
float previousPriority = 0;
id<RCTURLRequestHandler> previousHandler = nil;
for (id<RCTURLRequestHandler> handler in _handlers) {
float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0;
if (previousHandler && priority < previousPriority) {
return previousHandler;
}
if ([handler canHandleRequest:request]) {
float priority = [handler respondsToSelector:@selector(handlerPriority)] ? [handler handlerPriority] : 0;
if (previousHandler) {
if (priority == previousPriority) {
RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that"
@ -180,6 +183,7 @@ RCT_EXPORT_MODULE()
}
}
}
return previousHandler;
}
// Normal code path

View File

@ -79,6 +79,12 @@ typedef void (^RCTFatalHandler)(NSError *error);
#define RCTAssertMainThread() RCTAssert([NSThread isMainThread], \
@"This function must be called on the main thread")
/**
* Convenience macro for asserting that we're running off the main thread.
*/
#define RCTAssertNotMainThread() RCTAssert(![NSThread isMainThread], \
@"This function must not be called on the main thread")
/**
* These methods get and set the current assert function called by the RCTAssert
* macros. You can use these to replace the standard behavior with custom assert