diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 753f58b35..b345591a3 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -99,8 +99,11 @@ RCT_EXPORT_MODULE() float previousPriority = 0; id previousLoader = nil; for (id 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 previousDecoder = nil; for (id 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 imageDecoder = [self imageDataDecoderForData:data]; if (imageDecoder) { diff --git a/Libraries/Image/RCTImageView.m b/Libraries/Image/RCTImageView.m index 4f4a65d33..386852d65 100644 --- a/Libraries/Image/RCTImageView.m +++ b/Libraries/Image/RCTImageView.m @@ -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 { diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index fe4f9a4dd..749c9e0b6 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -165,8 +165,11 @@ RCT_EXPORT_MODULE() float previousPriority = 0; id previousHandler = nil; for (id 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 diff --git a/React/Base/RCTAssert.h b/React/Base/RCTAssert.h index 94667502e..345a32541 100644 --- a/React/Base/RCTAssert.h +++ b/React/Base/RCTAssert.h @@ -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