diff --git a/Examples/UIExplorer/ImageExample.js b/Examples/UIExplorer/ImageExample.js
index 993cde3081e..4cab0c1a1cd 100644
--- a/Examples/UIExplorer/ImageExample.js
+++ b/Examples/UIExplorer/ImageExample.js
@@ -24,6 +24,8 @@ var {
ActivityIndicatorIOS
} = React;
+var base64Icon = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAABLCAQAAACSR7JhAAADtUlEQVR4Ac3YA2Bj6QLH0XPT1Fzbtm29tW3btm3bfLZtv7e2ObZnms7d8Uw098tuetPzrxv8wiISrtVudrG2JXQZ4VOv+qUfmqCGGl1mqLhoA52oZlb0mrjsnhKpgeUNEs91Z0pd1kvihA3ULGVHiQO2narKSHKkEMulm9VgUyE60s1aWoMQUbpZOWE+kaqs4eLEjdIlZTcFZB0ndc1+lhB1lZrIuk5P2aib1NBpZaL+JaOGIt0ls47SKzLC7CqrlGF6RZ09HGoNy1lYl2aRSWL5GuzqWU1KafRdoRp0iOQEiDzgZPnG6DbldcomadViflnl/cL93tOoVbsOLVM2jylvdWjXolWX1hmfZbGR/wjypDjFLSZIRov09BgYmtUqPQPlQrPapecLgTIy0jMgPKtTeob2zWtrGH3xvjUkPCtNg/tm1rjwrMa+mdUkPd3hWbH0jArPGiU9ufCsNNWFZ40wpwn+62/66R2RUtoso1OB34tnLOcy7YB1fUdc9e0q3yru8PGM773vXsuZ5YIZX+5xmHwHGVvlrGPN6ZSiP1smOsMMde40wKv2VmwPPVXNut4sVpUreZiLBHi0qln/VQeI/LTMYXpsJtFiclUN+5HVZazim+Ky+7sAvxWnvjXrJFneVtLWLyPJu9K3cXLWeOlbMTlrIelbMDlrLenrjEQOtIF+fuI9xRp9ZBFp6+b6WT8RrxEpdK64BuvHgDk+vUy+b5hYk6zfyfs051gRoNO1usU12WWRWL73/MMEy9pMi9qIrR4ZpV16Rrvduxazmy1FSvuFXRkqTnE7m2kdb5U8xGjLw/spRr1uTov4uOgQE+0N/DvFrG/Jt7i/FzwxbA9kDanhf2w+t4V97G8lrT7wc08aA2QNUkuTfW/KimT01wdlfK4yEw030VfT0RtZbzjeMprNq8m8tnSTASrTLti64oBNdpmMQm0eEwvfPwRbUBywG5TzjPCsdwk3IeAXjQblLCoXnDVeoAz6SfJNk5TTzytCNZk/POtTSV40NwOFWzw86wNJRpubpXsn60NJFlHeqlYRbslqZm2jnEZ3qcSKgm0kTli3zZVS7y/iivZTweYXJ26Y+RTbV1zh3hYkgyFGSTKPfRVbRqWWVReaxYeSLarYv1Qqsmh1s95S7G+eEWK0f3jYKTbV6bOwepjfhtafsvUsqrQvrGC8YhmnO9cSCk3yuY984F1vesdHYhWJ5FvASlacshUsajFt2mUM9pqzvKGcyNJW0arTKN1GGGzQlH0tXwLDgQTurS8eIQAAAABJRU5ErkJggg==';
+
var ImageCapInsetsExample = require('./ImageCapInsetsExample');
var NetworkImageExample = React.createClass({
@@ -335,6 +337,18 @@ exports.examples = [
},
platform: 'ios',
},
+ {
+ title: 'Base64 image',
+ render: function() {
+ return (
+
+ );
+ },
+ platform: 'ios',
+ },
{
title: 'Cap Insets',
description:
@@ -399,4 +413,9 @@ var styles = StyleSheet.create({
flex: 1,
height: 200,
},
+ base64: {
+ flex: 1,
+ height: 50,
+ resizeMode: 'contain',
+ },
});
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h
index 364cddc8869..6ee68b5c380 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.h
@@ -25,14 +25,14 @@ typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(
@end
-typedef BOOL (^RCTImageDecoderCanDecodeImageDataHandler)(NSData *imageData);
-typedef RCTImageLoaderCancellationBlock (^RCTImageDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
+typedef BOOL (^RCTImageDataDecoderCanDecodeImageDataHandler)(NSData *imageData);
+typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
-@interface RCTConcreteImageDecoder : NSObject
+@interface RCTConcreteImageDecoder : NSObject
- (instancetype)initWithPriority:(float)priority
- canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
- decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler;
+ canDecodeImageDataHandler:(RCTImageDataDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
+ decodeImageDataHandler:(RCTImageDataDecoderDecodeImageDataHandler)decodeImageDataHandler;
@end
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
index 2038fad50ab..e41b09f3d7f 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderHelpers.m
@@ -52,7 +52,7 @@
return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
}
-- (float)imageLoaderPriority
+- (float)loaderPriority
{
return _priority;
}
@@ -61,8 +61,8 @@
@implementation RCTConcreteImageDecoder
{
- RCTImageDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
- RCTImageDecoderDecodeImageDataHandler _decodeImageDataHandler;
+ RCTImageDataDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
+ RCTImageDataDecoderDecodeImageDataHandler _decodeImageDataHandler;
float _priority;
}
@@ -76,7 +76,7 @@
return nil;
}
-- (instancetype)initWithPriority:(float)priority canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler
+- (instancetype)initWithPriority:(float)priority canDecodeImageDataHandler:(RCTImageDataDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler decodeImageDataHandler:(RCTImageDataDecoderDecodeImageDataHandler)decodeImageDataHandler
{
if ((self = [super init])) {
_canDecodeImageDataHandler = [canDecodeImageDataHandler copy];
@@ -97,7 +97,7 @@
return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
}
-- (float)imageDecoderPriority
+- (float)decoderPriority
{
return _priority;
}
diff --git a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
index d0323df7a14..04f367312a1 100644
--- a/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
+++ b/Examples/UIExplorer/UIExplorerUnitTests/RCTImageLoaderTests.m
@@ -95,7 +95,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
UIImage *image = [[UIImage alloc] initWithData:data];
- id decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
+ id decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data);
@@ -118,7 +118,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
UIImage *image = [[UIImage alloc] initWithData:data];
- id decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
+ id decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data);
@@ -126,7 +126,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
return nil;
}];
- id decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
+ id decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
XCTFail(@"Should not have used decoder2");
diff --git a/Libraries/Image/RCTGIFImageDecoder.h b/Libraries/Image/RCTGIFImageDecoder.h
index 51c25fa2e47..465219e8c2f 100644
--- a/Libraries/Image/RCTGIFImageDecoder.h
+++ b/Libraries/Image/RCTGIFImageDecoder.h
@@ -9,6 +9,6 @@
#import "RCTImageLoader.h"
-@interface RCTGIFImageDecoder : NSObject
+@interface RCTGIFImageDecoder : NSObject
@end
diff --git a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
index 0a1360c1a42..0c404f31c6a 100644
--- a/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
+++ b/Libraries/Image/RCTImage.xcodeproj/project.pbxproj
@@ -17,7 +17,6 @@
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.m */; };
- 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */; };
/* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -54,8 +53,6 @@
354631661B69857700AA0B86 /* RCTImageEditingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageEditingManager.h; sourceTree = ""; };
354631671B69857700AA0B86 /* RCTImageEditingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageEditingManager.m; sourceTree = ""; };
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; };
- 58B511891A9E6BD600147676 /* RCTImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageDownloader.h; sourceTree = ""; };
- 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = ""; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -76,8 +73,6 @@
13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
- 58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
- 58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
354631661B69857700AA0B86 /* RCTImageEditingManager.h */,
354631671B69857700AA0B86 /* RCTImageEditingManager.m */,
143879361AAD32A300F088A5 /* RCTImageLoader.h */,
@@ -166,7 +161,6 @@
files = (
13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */,
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
- 58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
diff --git a/Libraries/Image/RCTImageDownloader.m b/Libraries/Image/RCTImageDownloader.m
deleted file mode 100644
index dab71ee6436..00000000000
--- a/Libraries/Image/RCTImageDownloader.m
+++ /dev/null
@@ -1,178 +0,0 @@
-/**
- * Copyright (c) 2015-present, Facebook, Inc.
- * All rights reserved.
- *
- * This source code is licensed under the BSD-style license found in the
- * LICENSE file in the root directory of this source tree. An additional grant
- * of patent rights can be found in the PATENTS file in the same directory.
- */
-
-#import "RCTImageDownloader.h"
-
-#import "RCTImageLoader.h"
-#import "RCTImageUtils.h"
-#import "RCTLog.h"
-#import "RCTNetworking.h"
-#import "RCTUtils.h"
-
-@implementation RCTImageDownloader
-{
- NSURLCache *_cache;
- dispatch_queue_t _processingQueue;
-}
-
-@synthesize bridge = _bridge;
-
-RCT_EXPORT_MODULE()
-
-- (instancetype)init
-{
- if ((self = [super init])) {
- _cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 diskCapacity:200 * 1024 * 1024 diskPath:@"React/RCTImageDownloader"];
- _processingQueue = dispatch_queue_create("com.facebook.React.DownloadProcessingQueue", DISPATCH_QUEUE_SERIAL);
- }
- return self;
-}
-
-- (BOOL)canLoadImageURL:(NSURL *)requestURL
-{
- static NSSet *schemes = nil;
- static dispatch_once_t onceToken;
- dispatch_once(&onceToken, ^{
- schemes = [[NSSet alloc] initWithObjects:@"http", @"https", @"file", nil];
- });
- return [schemes containsObject:requestURL.scheme.lowercaseString];
-}
-
-/**
- * Downloads a block of raw data and returns it. Note that the callback block
- * will not be executed on the same thread you called the method from, nor on
- * the main thread. Returns a token that can be used to cancel the download.
- */
-- (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
- progressHandler:(RCTImageLoaderProgressBlock)progressBlock
- completionHandler:(void (^)(NSError *error, NSData *data))completionBlock
-{
- if (![_bridge respondsToSelector:NSSelectorFromString(@"networking")]) {
- RCTLogError(@"You need to import the RCTNetworking library in order to download remote images.");
- return ^{};
- }
-
- __weak RCTImageDownloader *weakSelf = self;
- RCTURLRequestCompletionBlock runBlocks = ^(NSURLResponse *response, NSData *data, NSError *error) {
-
- if (!error && [response isKindOfClass:[NSHTTPURLResponse class]]) {
- NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
- if (httpResponse.statusCode != 200) {
- data = nil;
- error = [[NSError alloc] initWithDomain:NSURLErrorDomain
- code:httpResponse.statusCode
- userInfo:nil];
- }
- }
-
- dispatch_async(_processingQueue, ^{
- completionBlock(error, data);
- });
- };
-
- NSURLRequest *request = [NSURLRequest requestWithURL:url];
- {
- NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest:request];
- if (cachedResponse) {
- runBlocks(cachedResponse.response, cachedResponse.data, nil);
- return ^{};
- }
- }
-
- RCTDownloadTask *task = [_bridge.networking downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
- if (response && !error && [response.URL.scheme hasPrefix:@"http"]) {
- RCTImageDownloader *strongSelf = weakSelf;
- NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
- [strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request];
- }
- runBlocks(response, data, error);
- }];
- if (progressBlock) {
- task.downloadProgressBlock = progressBlock;
- }
- [task start];
- return ^{ [task cancel]; };
-}
-
-- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
- size:(CGSize)size
- scale:(CGFloat)scale
- resizeMode:(UIViewContentMode)resizeMode
- progressHandler:(RCTImageLoaderProgressBlock)progressHandler
- completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
-{
- if ([imageURL.scheme.lowercaseString hasPrefix:@"http"] ||
- [imageURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame) {
- __block RCTImageLoaderCancellationBlock decodeCancel = nil;
-
- // Add missing png extension
- if (imageURL.fileURL && imageURL.pathExtension.length == 0) {
- imageURL = [NSURL fileURLWithPath:[imageURL.path stringByAppendingPathExtension:@"png"]];
- }
-
- __weak RCTImageDownloader *weakSelf = self;
- RCTImageLoaderCancellationBlock downloadCancel = [self downloadDataForURL:imageURL progressHandler:progressHandler completionHandler:^(NSError *error, NSData *imageData) {
- if (error) {
- completionHandler(error, nil);
- } else {
- decodeCancel = [weakSelf.bridge.imageLoader decodeImageData:imageData size:size scale:scale resizeMode:resizeMode completionBlock:completionHandler];
- }
- }];
-
- return ^{
- downloadCancel();
-
- if (decodeCancel) {
- decodeCancel();
- }
- };
- } else if ([imageURL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame) {
- __block BOOL cancelled = NO;
- dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
- if (cancelled) {
- return;
- }
-
- // Normally -dataWithContentsOfURL: would be bad but this is a data URL.
- NSData *data = [NSData dataWithContentsOfURL:imageURL];
-
- UIImage *image = [UIImage imageWithData:data scale:scale];
- if (image) {
- if (progressHandler) {
- progressHandler(1, 1);
- }
- if (completionHandler) {
- completionHandler(nil, image);
- }
- } else {
- if (completionHandler) {
- NSString *message = [NSString stringWithFormat:@"Invalid image data for URL: %@", imageURL];
- completionHandler(RCTErrorWithMessage(message), nil);
- }
- }
- });
- return ^{
- cancelled = YES;
- };
- } else {
- RCTLogError(@"Unexpected image schema %@", imageURL.scheme);
- return ^{};
- }
-}
-
-@end
-
-@implementation RCTBridge (RCTImageDownloader)
-
-- (RCTImageDownloader *)imageDownloader
-{
- return self.modules[RCTBridgeModuleNameForClass([RCTImageDownloader class])];
-}
-
-@end
diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h
index a55df421b12..f6bf9b3e8ce 100644
--- a/Libraries/Image/RCTImageLoader.h
+++ b/Libraries/Image/RCTImageLoader.h
@@ -46,7 +46,8 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
/**
* Finds an appropriate image decoder and passes the target size, scale and
- * resizeMode for optimal image decoding.
+ * resizeMode for optimal image decoding. Can be called from any thread,
+ * will call callback on an unspecified thread.
*/
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size
@@ -66,7 +67,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
@end
/**
- * Provides the interface needed to register an image data loader. Image data
+ * Provides the interface needed to register an image loader. Image data
* loaders are also bridge modules, so should be registered using
* RCT_EXPORT_MODULE().
*/
@@ -85,18 +86,23 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
* has finished. The method should also return a cancellation block, if
* applicable.
*/
-- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
+- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(UIViewContentMode)resizeMode
+ progressHandler:(RCTImageLoaderProgressBlock)progressHandler
+ completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@optional
/**
* If more than one RCTImageURLLoader responds YES to `-canLoadImageURL:`
- * then `imageLoaderPriority` is used to determine which one to use. The handler
+ * then `loaderPriority` is used to determine which one to use. The loader
* with the highest priority will be selected. Default priority is zero. If
- * two or more valid handlers have the same priority, the selection order is
+ * two or more valid loaders have the same priority, the selection order is
* undefined.
*/
-- (float)imageLoaderPriority;
+- (float)loaderPriority;
@end
@@ -104,7 +110,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
* Provides the interface needed to register an image decoder. Image decoders
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
*/
-@protocol RCTImageDecoder
+@protocol RCTImageDataDecoder
/**
* Indicates whether this handler is capable of decoding the specified data.
@@ -118,17 +124,21 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
* completionHandler when the decoding operation has finished. The method
* should also return a cancellation block, if applicable.
*/
-- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
+- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
+ size:(CGSize)size
+ scale:(CGFloat)scale
+ resizeMode:(UIViewContentMode)resizeMode
+ completionHandler:(RCTImageLoaderCompletionBlock)completionHandler;
@optional
/**
- * If more than one RCTImageDecoder responds YES to `-canDecodeImageData:`
- * then `imageDecoderPriority` is used to determine which one to use. The
- * handler with the highest priority will be selected. Default priority is zero.
- * If two or more valid handlers have the same priority, the selection order is
+ * If more than one RCTImageDataDecoder responds YES to `-canDecodeImageData:`
+ * then `decoderPriority` is used to determine which one to use. The decoder
+ * with the highest priority will be selected. Default priority is zero.
+ * If two or more valid decoders have the same priority, the selection order is
* undefined.
*/
-- (float)imageDecoderPriority;
+- (float)decoderPriority;
@end
diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m
index e378bc4050f..ca1092dcedf 100644
--- a/Libraries/Image/RCTImageLoader.m
+++ b/Libraries/Image/RCTImageLoader.m
@@ -13,9 +13,9 @@
#import "RCTConvert.h"
#import "RCTDefines.h"
-#import "RCTImageDownloader.h"
#import "RCTImageUtils.h"
#import "RCTLog.h"
+#import "RCTNetworking.h"
#import "RCTUtils.h"
static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image)
@@ -44,11 +44,131 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
@end
@implementation RCTImageLoader
+{
+ NSArray *_loaders;
+ NSArray *_decoders;
+ NSURLCache *_cache;
+}
@synthesize bridge = _bridge;
RCT_EXPORT_MODULE()
+- (void)setBridge:(RCTBridge *)bridge
+{
+ // Get image loaders and decoders
+ NSMutableArray *loaders = [NSMutableArray array];
+ NSMutableArray *decoders = [NSMutableArray array];
+ for (id module in bridge.modules.allValues) {
+ if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
+ [loaders addObject:module];
+ }
+ if ([module conformsToProtocol:@protocol(RCTImageDataDecoder)]) {
+ [decoders addObject:module];
+ }
+ }
+
+ // Sort loaders in reverse priority order (highest priority first)
+ [loaders sortUsingComparator:^NSComparisonResult(id a, id b) {
+ float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
+ float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
+ if (priorityA > priorityB) {
+ return NSOrderedAscending;
+ } else if (priorityA < priorityB) {
+ return NSOrderedDescending;
+ } else {
+ return NSOrderedSame;
+ }
+ }];
+
+ // Sort decoders in reverse priority order (highest priority first)
+ [decoders sortUsingComparator:^NSComparisonResult(id a, id b) {
+ float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
+ float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
+ if (priorityA > priorityB) {
+ return NSOrderedAscending;
+ } else if (priorityA < priorityB) {
+ return NSOrderedDescending;
+ } else {
+ return NSOrderedSame;
+ }
+ }];
+
+ _bridge = bridge;
+ _loaders = loaders;
+ _decoders = decoders;
+ _cache = [[NSURLCache alloc] initWithMemoryCapacity:5 * 1024 * 1024 // 5MB
+ diskCapacity:200 * 1024 * 1024 // 200MB
+ diskPath:@"React/RCTImageDownloader"];
+}
+
+- (id)imageURLLoaderForURL:(NSURL *)URL
+{
+ if (RCT_DEBUG) {
+ // Check for handler conflicts
+ float previousPriority = 0;
+ id previousLoader = nil;
+ for (id loader in _loaders) {
+ 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"
+ " they can load the URL %@, and have equal priority"
+ " (%g). This could result in non-deterministic behavior.",
+ loader, previousLoader, URL, priority);
+ }
+ } else {
+ previousLoader = loader;
+ previousPriority = priority;
+ }
+ }
+ }
+ }
+
+ // Normal code path
+ for (id loader in _loaders) {
+ if ([loader canLoadImageURL:URL]) {
+ return loader;
+ }
+ }
+ return nil;
+}
+
+- (id)imageDataDecoderForData:(NSData *)data
+{
+ if (RCT_DEBUG) {
+ // Check for handler conflicts
+ float previousPriority = 0;
+ id previousDecoder = nil;
+ for (id decoder in _decoders) {
+ 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"
+ " they can decode the data , and"
+ " have equal priority (%g). This could result in"
+ " non-deterministic behavior.",
+ decoder, previousDecoder, data, data.length, priority);
+ }
+ } else {
+ previousDecoder = decoder;
+ previousPriority = priority;
+ }
+ }
+ }
+ }
+
+ // Normal code path
+ for (id decoder in _decoders) {
+ if ([decoder canDecodeImageData:data]) {
+ return decoder;
+ }
+ }
+ return nil;
+}
+
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
callback:(RCTImageLoaderCompletionBlock)callback
{
@@ -60,35 +180,6 @@ RCT_EXPORT_MODULE()
completionBlock:callback];
}
-- (id)imageURLLoaderForRequest:(NSURL *)requestURL
-{
- NSMutableArray *handlers = [NSMutableArray array];
- for (id module in _bridge.modules.allValues) {
- if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
- if ([(id)module canLoadImageURL:requestURL]) {
- [handlers addObject:module];
- }
- }
- }
- [handlers sortUsingComparator:^NSComparisonResult(id a, id b) {
- float priorityA = [a respondsToSelector:@selector(imageLoaderPriority)] ? [a imageLoaderPriority] : 0;
- float priorityB = [b respondsToSelector:@selector(imageLoaderPriority)] ? [b imageLoaderPriority] : 0;
- if (priorityA < priorityB) {
- return NSOrderedAscending;
- } else if (priorityA > priorityB) {
- return NSOrderedDescending;
- } else {
- RCTLogError(@"The RCTImageLoader %@ and %@ both reported that they can"
- " handle the load request %@, and have equal priority (%g)."
- " This could result in non-deterministic behavior.",
- a, b, requestURL, priorityA);
-
- return NSOrderedSame;
- }
- }];
- return [handlers lastObject];
-}
-
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
size:(CGSize)size
scale:(CGFloat)scale
@@ -101,58 +192,125 @@ RCT_EXPORT_MODULE()
return ^{};
}
- NSURL *requestURL = [RCTConvert NSURL:imageTag];
- id loadHandler = [self imageURLLoaderForRequest:requestURL];
- if (!loadHandler) {
- RCTLogError(@"No suitable image URL loader found for %@", imageTag);
+ // Ensure progress is dispatched on main thread
+ RCTImageLoaderProgressBlock progressHandler = nil;
+ if (progressBlock) {
+ progressHandler = ^(int64_t progress, int64_t total) {
+ if ([NSThread isMainThread]) {
+ progressBlock(progress, total);
+ } else {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ progressBlock(progress, total);
+ });
+ }
+ };
+ }
+
+ // Ensure completion is dispatched on main thread
+ RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) {
+ RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
+ };
+
+ // Find suitable image URL loader
+ NSURLRequest *request = [RCTConvert NSURLRequest:imageTag];
+ id loadHandler = [self imageURLLoaderForURL:request.URL];
+ if (loadHandler) {
+ return [loadHandler loadImageForURL:request.URL
+ size:size
+ scale:scale
+ resizeMode:resizeMode
+ progressHandler:progressHandler
+ completionHandler:completionHandler];
+ }
+
+ // Check if networking module is available
+ if (![_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 ^{};
}
- return [loadHandler loadImageForURL:requestURL size:size scale:scale resizeMode:resizeMode progressHandler:^(int64_t progress, int64_t total) {
- if (!progressBlock) {
- return;
- }
+ // Use networking module to load image
+ if ([_bridge.networking canHandleRequest:request]) {
- if ([NSThread isMainThread]) {
- progressBlock(progress, total);
- } else {
- dispatch_async(dispatch_get_main_queue(), ^{
- progressBlock(progress, total);
- });
- }
- } completionHandler:^(NSError *error, UIImage *image) {
- RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
- }];
-}
+ __weak RCTImageLoader *weakSelf = self;
+ __block RCTImageLoaderCancellationBlock decodeCancel = nil;
+ RCTURLRequestCompletionBlock processResponse =
+ ^(NSURLResponse *response, NSData *data, __unused NSError *error) {
-- (id)imageDecoderForRequest:(NSData *)imageData
-{
- NSMutableArray *handlers = [NSMutableArray array];
- for (id module in _bridge.modules.allValues) {
- if ([module conformsToProtocol:@protocol(RCTImageDecoder)]) {
- if ([(id)module canDecodeImageData:imageData]) {
- [handlers addObject:module];
+ // 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;
+ }
}
- }
- }
- [handlers sortUsingComparator:^NSComparisonResult(id a, id b) {
- float priorityA = [a respondsToSelector:@selector(imageDecoderPriority)] ? [a imageDecoderPriority] : 0;
- float priorityB = [b respondsToSelector:@selector(imageDecoderPriority)] ? [b imageDecoderPriority] : 0;
- if (priorityA < priorityB) {
- return NSOrderedAscending;
- } else if (priorityA > priorityB) {
- return NSOrderedDescending;
- } else {
- RCTLogError(@"The RCTImageDecoder %@ and %@ both reported that they can"
- " handle the decode request , and have"
- " equal priority (%g). This could result in"
- " non-deterministic behavior.",
- a, b, imageData, imageData.length, priorityA);
- return NSOrderedSame;
+ // 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 ^{};
}
- }];
- return [handlers lastObject];
+
+ // 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);
+
+ }];
+ if (progressBlock) {
+ task.downloadProgressBlock = progressBlock;
+ }
+ [task start];
+
+ return ^{
+ [task cancel];
+ if (decodeCancel) {
+ decodeCancel();
+ }
+ };
+ }
+
+ RCTLogError(@"No suitable image URL loader found for %@", imageTag);
+ return ^{};
}
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
@@ -161,20 +319,22 @@ RCT_EXPORT_MODULE()
resizeMode:(UIViewContentMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
- id imageDecoder = [self imageDecoderForRequest:data];
+ id imageDecoder = [self imageDataDecoderForData:data];
if (imageDecoder) {
- return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, UIImage *image) {
- RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
- }];
+ return [imageDecoder decodeImageData:data
+ size:size
+ scale:scale
+ resizeMode:resizeMode
+ completionHandler:completionBlock];
} else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) {
- RCTDispatchCallbackOnMainQueue(completionBlock, nil, image);
+ completionBlock(nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data ", data, data.length];
NSError *finalError = RCTErrorWithMessage(errorMessage);
- RCTDispatchCallbackOnMainQueue(completionBlock, finalError, nil);
+ completionBlock(finalError, nil);
}
});
return ^{};
@@ -185,18 +345,20 @@ RCT_EXPORT_MODULE()
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
- id handler = [self imageURLLoaderForRequest:request.URL];
-
- // RCTImageDownloader is an image plugin that uses the networking stack.
- // We don't want to route any network calls through the image downloader
- // as that would cause cyclical dependencies.
- return handler && ![handler isKindOfClass:[RCTImageDownloader class]];
+ NSURL *requestURL = request.URL;
+ for (id module in _bridge.modules.allValues) {
+ if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] &&
+ [(id)module canLoadImageURL:requestURL]) {
+ return YES;
+ }
+ }
+ return NO;
}
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id)delegate
{
__block RCTImageLoaderCancellationBlock requestToken;
- requestToken = [self.bridge.imageLoader loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) {
+ requestToken = [self loadImageWithTag:request.URL.absoluteString callback:^(NSError *error, UIImage *image) {
if (error) {
[delegate URLRequest:requestToken didCompleteWithError:error];
return;
diff --git a/Libraries/Image/RCTShadowVirtualImage.m b/Libraries/Image/RCTShadowVirtualImage.m
index 57dbef04b3a..c908b62df97 100644
--- a/Libraries/Image/RCTShadowVirtualImage.m
+++ b/Libraries/Image/RCTShadowVirtualImage.m
@@ -11,6 +11,7 @@
#import "RCTImageLoader.h"
#import "RCTBridge.h"
#import "RCTConvert.h"
+#import "RCTUIManager.h"
@implementation RCTShadowVirtualImage
{
@@ -38,9 +39,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
__weak RCTShadowVirtualImage *weakSelf = self;
[_bridge.imageLoader loadImageWithTag:imageTag size:CGSizeZero scale:scale resizeMode:UIViewContentModeScaleToFill progressBlock:nil completionBlock:^(NSError *error, UIImage *image) {
- RCTShadowVirtualImage *strongSelf = weakSelf;
- strongSelf->_image = image;
- [strongSelf dirtyText];
+ dispatch_async(_bridge.uiManager.methodQueue, ^{
+ RCTShadowVirtualImage *strongSelf = weakSelf;
+ strongSelf->_image = image;
+ [strongSelf dirtyText];
+ });
}];
}
}
diff --git a/Libraries/Image/RCTXCAssetImageLoader.m b/Libraries/Image/RCTXCAssetImageLoader.m
index dd12b7c6a9b..93a02bce36e 100644
--- a/Libraries/Image/RCTXCAssetImageLoader.m
+++ b/Libraries/Image/RCTXCAssetImageLoader.m
@@ -20,11 +20,6 @@ RCT_EXPORT_MODULE()
return RCTIsXCAssetURL(requestURL);
}
-- (float)imageLoaderPriority
-{
- return 100; // higher priority than any ordinary file loader
-}
-
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{
__block BOOL cancelled = NO;
diff --git a/Libraries/Image/RCTImageDownloader.h b/Libraries/Network/RCTDataRequestHandler.h
similarity index 57%
rename from Libraries/Image/RCTImageDownloader.h
rename to Libraries/Network/RCTDataRequestHandler.h
index 0e5c8de0b5c..81829a5c57a 100644
--- a/Libraries/Image/RCTImageDownloader.h
+++ b/Libraries/Network/RCTDataRequestHandler.h
@@ -7,15 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
-#import "RCTBridge.h"
-#import "RCTImageLoader.h"
+#import "RCTURLRequestHandler.h"
+#import "RCTInvalidating.h"
-@interface RCTImageDownloader : NSObject
-
-@end
-
-@interface RCTBridge (RCTImageDownloader)
-
-@property (nonatomic, readonly) RCTImageDownloader *imageDownloader;
+/**
+ * This is the default RCTURLRequestHandler implementation for data URL requests.
+ */
+@interface RCTDataRequestHandler : NSObject
@end
diff --git a/Libraries/Network/RCTDataRequestHandler.m b/Libraries/Network/RCTDataRequestHandler.m
new file mode 100644
index 00000000000..e80bcfc6392
--- /dev/null
+++ b/Libraries/Network/RCTDataRequestHandler.m
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2015-present, Facebook, Inc.
+ * All rights reserved.
+ *
+ * This source code is licensed under the BSD-style license found in the
+ * LICENSE file in the root directory of this source tree. An additional grant
+ * of patent rights can be found in the PATENTS file in the same directory.
+ */
+
+#import "RCTDataRequestHandler.h"
+
+@implementation RCTDataRequestHandler
+{
+ NSOperationQueue *_queue;
+}
+
+RCT_EXPORT_MODULE()
+
+- (void)invalidate
+{
+ [_queue cancelAllOperations];
+ _queue = nil;
+}
+
+- (BOOL)canHandleRequest:(NSURLRequest *)request
+{
+ return [request.URL.scheme caseInsensitiveCompare:@"data"] == NSOrderedSame;
+}
+
+- (NSOperation *)sendRequest:(NSURLRequest *)request
+ withDelegate:(id)delegate
+{
+ // Lazy setup
+ if (!_queue) {
+ _queue = [NSOperationQueue new];
+ _queue.maxConcurrentOperationCount = 2;
+ }
+
+ __block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
+
+ // Get mime type
+ NSRange firstSemicolon = [request.URL.resourceSpecifier rangeOfString:@";"];
+ NSString *mimeType = firstSemicolon.length ? [request.URL.resourceSpecifier substringToIndex:firstSemicolon.location] : nil;
+
+ // Send response
+ NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
+ MIMEType:mimeType
+ expectedContentLength:-1
+ textEncodingName:nil];
+
+ [delegate URLRequest:op didReceiveResponse:response];
+
+ // Load data
+ NSError *error;
+ NSData *data = [NSData dataWithContentsOfURL:request.URL
+ options:NSDataReadingMappedIfSafe
+ error:&error];
+ if (data) {
+ [delegate URLRequest:op didReceiveData:data];
+ }
+ [delegate URLRequest:op didCompleteWithError:error];
+ }];
+
+ [_queue addOperation:op];
+ return op;
+}
+
+- (void)cancelRequest:(NSOperation *)op
+{
+ [op cancel];
+}
+
+@end
diff --git a/Libraries/Network/RCTFileRequestHandler.m b/Libraries/Network/RCTFileRequestHandler.m
index c4f3f045d62..797fab9e2d6 100644
--- a/Libraries/Network/RCTFileRequestHandler.m
+++ b/Libraries/Network/RCTFileRequestHandler.m
@@ -11,13 +11,13 @@
#import
+#import "RCTUtils.h"
+
@implementation RCTFileRequestHandler
{
NSOperationQueue *_fileQueue;
}
-@synthesize methodQueue = _methodQueue;
-
RCT_EXPORT_MODULE()
- (void)invalidate
@@ -28,7 +28,9 @@ RCT_EXPORT_MODULE()
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
- return [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame;
+ return
+ [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame
+ && !RCTIsXCAssetURL(request.URL);
}
- (NSOperation *)sendRequest:(NSURLRequest *)request
diff --git a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj
index 37b87a261fb..f0f23f9b74e 100644
--- a/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj
+++ b/Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj
@@ -7,8 +7,9 @@
objects = {
/* Begin PBXBuildFile section */
+ 134E969A1BCEB7F800AFFDA1 /* RCTDataRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */; settings = {ASSET_TAGS = (); }; };
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */; };
- 13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; };
+ 13D6D66A1B5FCF8200883BE9 /* RCTNetworkTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */; };
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */; settings = {ASSET_TAGS = (); }; };
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
@@ -27,10 +28,12 @@
/* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */
+ 134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataRequestHandler.h; sourceTree = ""; };
+ 134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataRequestHandler.m; sourceTree = ""; };
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTNetInfo.h; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = ""; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
- 13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = ""; };
- 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = ""; };
+ 13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkTask.h; sourceTree = ""; };
+ 13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkTask.m; sourceTree = ""; };
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFileRequestHandler.h; sourceTree = ""; };
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileRequestHandler.m; sourceTree = ""; };
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = ""; };
@@ -54,12 +57,14 @@
58B511D21A9E6C8500147676 = {
isa = PBXGroup;
children = (
- 13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */,
- 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */,
+ 13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */,
+ 13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */,
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */,
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */,
+ 134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */,
+ 134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */,
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */,
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */,
58B512061A9E6CE300147676 /* RCTNetworking.h */,
@@ -134,8 +139,9 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */,
+ 13D6D66A1B5FCF8200883BE9 /* RCTNetworkTask.m in Sources */,
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */,
+ 134E969A1BCEB7F800AFFDA1 /* RCTDataRequestHandler.m in Sources */,
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */,
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,
diff --git a/Libraries/Network/RCTDownloadTask.h b/Libraries/Network/RCTNetworkTask.h
similarity index 96%
rename from Libraries/Network/RCTDownloadTask.h
rename to Libraries/Network/RCTNetworkTask.h
index 2cb2a8f0bfa..2684836706c 100644
--- a/Libraries/Network/RCTDownloadTask.h
+++ b/Libraries/Network/RCTNetworkTask.h
@@ -18,7 +18,7 @@ typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data);
typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total);
typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
-@interface RCTDownloadTask : NSObject
+@interface RCTNetworkTask : NSObject
@property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSNumber *requestID;
diff --git a/Libraries/Network/RCTDownloadTask.m b/Libraries/Network/RCTNetworkTask.m
similarity index 97%
rename from Libraries/Network/RCTDownloadTask.m
rename to Libraries/Network/RCTNetworkTask.m
index a0492d542c7..1c6cfca551b 100644
--- a/Libraries/Network/RCTDownloadTask.m
+++ b/Libraries/Network/RCTNetworkTask.m
@@ -7,15 +7,15 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
-#import "RCTDownloadTask.h"
+#import "RCTNetworkTask.h"
#import "RCTLog.h"
-@implementation RCTDownloadTask
+@implementation RCTNetworkTask
{
NSMutableData *_data;
id _handler;
- RCTDownloadTask *_selfReference;
+ RCTNetworkTask *_selfReference;
}
- (instancetype)initWithRequest:(NSURLRequest *)request
diff --git a/Libraries/Network/RCTNetworking.h b/Libraries/Network/RCTNetworking.h
index 8df9002ca7d..cd8b99dde4d 100644
--- a/Libraries/Network/RCTNetworking.h
+++ b/Libraries/Network/RCTNetworking.h
@@ -10,12 +10,21 @@
#import
#import "RCTBridge.h"
-#import "RCTDownloadTask.h"
+#import "RCTNetworkTask.h"
@interface RCTNetworking : NSObject
-- (RCTDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
- completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
+/**
+ * Does a handler exist for the specified request?
+ */
+- (BOOL)canHandleRequest:(NSURLRequest *)request;
+
+/**
+ * Return an RCTNetworkTask for the specified request. This is useful for
+ * invoking the React Native networking stack from within native code.
+ */
+- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
+ completionBlock:(RCTURLRequestCompletionBlock)completionBlock;
@end
diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m
index 73f9dc57c62..7d5e1fd2c08 100644
--- a/Libraries/Network/RCTNetworking.m
+++ b/Libraries/Network/RCTNetworking.m
@@ -11,7 +11,7 @@
#import "RCTAssert.h"
#import "RCTConvert.h"
-#import "RCTDownloadTask.h"
+#import "RCTNetworkTask.h"
#import "RCTURLRequestHandler.h"
#import "RCTEventDispatcher.h"
#import "RCTHTTPRequestHandler.h"
@@ -37,10 +37,10 @@ typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSD
@implementation RCTHTTPFormDataHelper
{
- NSMutableArray *parts;
- NSMutableData *multipartBody;
+ NSMutableArray *_parts;
+ NSMutableData *_multipartBody;
RCTHTTPQueryResult _callback;
- NSString *boundary;
+ NSString *_boundary;
}
static NSString *RCTGenerateFormBoundary()
@@ -56,19 +56,19 @@ static NSString *RCTGenerateFormBoundary()
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
}
-- (RCTURLRequestCancellationBlock)process:(NSArray *)formData
+- (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData
callback:(RCTHTTPQueryResult)callback
{
if (formData.count == 0) {
return callback(nil, nil);
}
- parts = [formData mutableCopy];
+ _parts = [formData mutableCopy];
_callback = callback;
- multipartBody = [NSMutableData new];
- boundary = RCTGenerateFormBoundary();
+ _multipartBody = [NSMutableData new];
+ _boundary = RCTGenerateFormBoundary();
- return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *error, NSDictionary *result) {
+ return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *error, NSDictionary *result) {
return [self handleResult:result error:error];
}];
}
@@ -81,37 +81,37 @@ static NSString *RCTGenerateFormBoundary()
}
// Start with boundary.
- [multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary]
- dataUsingEncoding:NSUTF8StringEncoding]];
+ [_multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary]
+ dataUsingEncoding:NSUTF8StringEncoding]];
// Print headers.
- NSMutableDictionary *headers = [parts[0][@"headers"] mutableCopy];
+ NSMutableDictionary *headers = [_parts[0][@"headers"] mutableCopy];
NSString *partContentType = result[@"contentType"];
if (partContentType != nil) {
headers[@"content-type"] = partContentType;
}
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
- [multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
- dataUsingEncoding:NSUTF8StringEncoding]];
+ [_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
+ dataUsingEncoding:NSUTF8StringEncoding]];
}];
// Add the body.
- [multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
- [multipartBody appendData:result[@"body"]];
- [multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
+ [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
+ [_multipartBody appendData:result[@"body"]];
+ [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
- [parts removeObjectAtIndex:0];
- if (parts.count) {
- return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *err, NSDictionary *res) {
+ [_parts removeObjectAtIndex:0];
+ if (_parts.count) {
+ return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *err, NSDictionary *res) {
return [self handleResult:res error:err];
}];
}
// We've processed the last item. Finish and return.
- [multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary]
- dataUsingEncoding:NSUTF8StringEncoding]];
- NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundary];
- return _callback(nil, @{@"body": multipartBody, @"contentType": contentType});
+ [_multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary]
+ dataUsingEncoding:NSUTF8StringEncoding]];
+ NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", _boundary];
+ return _callback(nil, @{@"body": _multipartBody, @"contentType": contentType});
}
@end
@@ -122,6 +122,7 @@ static NSString *RCTGenerateFormBoundary()
@implementation RCTNetworking
{
NSMutableDictionary *_tasksByRequestID;
+ NSArray *_handlers;
}
@synthesize bridge = _bridge;
@@ -129,12 +130,65 @@ static NSString *RCTGenerateFormBoundary()
RCT_EXPORT_MODULE()
-- (instancetype)init
+- (void)setBridge:(RCTBridge *)bridge
{
- if ((self = [super init])) {
- _tasksByRequestID = [NSMutableDictionary new];
+ // get handlers
+ NSMutableArray *handlers = [NSMutableArray array];
+ for (id module in bridge.modules.allValues) {
+ if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
+ [handlers addObject:module];
+ }
}
- return self;
+
+ // Sort handlers in reverse priority order (highest priority first)
+ [handlers sortUsingComparator:^NSComparisonResult(id a, id b) {
+ float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
+ float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
+ if (priorityA > priorityB) {
+ return NSOrderedAscending;
+ } else if (priorityA < priorityB) {
+ return NSOrderedDescending;
+ } else {
+ return NSOrderedSame;
+ }
+ }];
+
+ _bridge = bridge;
+ _handlers = handlers;
+ _tasksByRequestID = [NSMutableDictionary new];
+}
+
+- (id)handlerForRequest:(NSURLRequest *)request
+{
+ if (RCT_DEBUG) {
+ // Check for handler conflicts
+ float previousPriority = 0;
+ id previousHandler = nil;
+ for (id handler in _handlers) {
+ 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"
+ " they can handle the request %@, and have equal priority"
+ " (%g). This could result in non-deterministic behavior.",
+ handler, previousHandler, request, priority);
+ }
+ } else {
+ previousHandler = handler;
+ previousPriority = priority;
+ }
+ }
+ }
+ }
+
+ // Normal code path
+ for (id handler in _handlers) {
+ if ([handler canHandleRequest:request]) {
+ return handler;
+ }
+ }
+ return nil;
}
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query
@@ -169,37 +223,9 @@ RCT_EXPORT_MODULE()
}];
}
-- (id)handlerForRequest:(NSURLRequest *)request
+- (BOOL)canHandleRequest:(NSURLRequest *)request
{
- NSMutableArray *handlers = [NSMutableArray array];
- for (id module in _bridge.modules.allValues) {
- if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
- if ([(id)module canHandleRequest:request]) {
- [handlers addObject:module];
- }
- }
- }
- [handlers sortUsingComparator:^NSComparisonResult(id a, id b) {
- float priorityA = [a respondsToSelector:@selector(handlerPriority)] ? [a handlerPriority] : 0;
- float priorityB = [b respondsToSelector:@selector(handlerPriority)] ? [b handlerPriority] : 0;
- if (priorityA < priorityB) {
- return NSOrderedAscending;
- } else if (priorityA > priorityB) {
- return NSOrderedDescending;
- } else {
- RCTLogError(@"The RCTURLRequestHandlers %@ and %@ both reported that"
- " they can handle the request %@, and have equal priority"
- " (%g). This could result in non-deterministic behavior.",
- a, b, request, priorityA);
-
- return NSOrderedSame;
- }
- }];
- id handler = handlers.lastObject;
- if (!handler) {
- RCTLogError(@"No suitable request handler found for %@", request.URL);
- }
- return handler;
+ return [self handlerForRequest:request] != nil;
}
/**
@@ -234,13 +260,13 @@ RCT_EXPORT_MODULE()
if (request) {
__block RCTURLRequestCancellationBlock cancellationBlock = nil;
- RCTDownloadTask *task = [self downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
+ RCTNetworkTask *task = [self networkTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
}];
[task start];
- __weak RCTDownloadTask *weakTask = task;
+ __weak RCTNetworkTask *weakTask = task;
return ^{
[weakTask cancel];
if (cancellationBlock) {
@@ -259,7 +285,7 @@ RCT_EXPORT_MODULE()
return callback(nil, nil);
}
-- (void)sendData:(NSData *)data forTask:(RCTDownloadTask *)task
+- (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task
{
if (data.length == 0) {
return;
@@ -278,7 +304,7 @@ RCT_EXPORT_MODULE()
if (!responseText && data.length) {
// We don't have an encoding, or the encoding is incorrect, so now we
- // try to guess (unfortunately, this feature is available of iOS 8+ only)
+ // try to guess (unfortunately, this feature is available in iOS 8+ only)
if ([NSString respondsToSelector:@selector(stringEncodingForData:
encodingOptions:
convertedString:
@@ -305,7 +331,7 @@ RCT_EXPORT_MODULE()
incrementalUpdates:(BOOL)incrementalUpdates
responseSender:(RCTResponseSenderBlock)responseSender
{
- __block RCTDownloadTask *task;
+ __block RCTNetworkTask *task;
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
dispatch_async(_methodQueue, ^{
@@ -355,7 +381,7 @@ RCT_EXPORT_MODULE()
});
};
- task = [self downloadTaskWithRequest:request completionBlock:completionBlock];
+ task = [self networkTaskWithRequest:request completionBlock:completionBlock];
task.incrementalDataBlock = incrementalDataBlock;
task.responseBlock = responseBlock;
task.uploadProgressBlock = uploadProgressBlock;
@@ -370,15 +396,16 @@ RCT_EXPORT_MODULE()
#pragma mark - Public API
-- (RCTDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request
+- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{
id handler = [self handlerForRequest:request];
if (!handler) {
+ RCTLogError(@"No suitable URL request handler found for %@", request.URL);
return nil;
}
- return [[RCTDownloadTask alloc] initWithRequest:request
+ return [[RCTNetworkTask alloc] initWithRequest:request
handler:handler
completionBlock:completionBlock];
}