Added RCTDataRequestHandler

Summary: public

Added RCTDataRequestHandler, which is responsible for loading data URLs. This moves the logic for data URL handling out of RCTImageDownloader (no longer needed) and into the RCTNetwork library, where it makes more sense.

This also means that it is now possible to load data URLs via XHR, and use them for purposes other than just images.

Reviewed By: javache

Differential Revision: D2540964

fb-gh-sync-id: 4f0418bd6b9186f047cc8297276bb970795af104
This commit is contained in:
Nick Lockwood
2015-10-19 09:07:06 -07:00
committed by facebook-github-bot-4
parent 31f9a690f3
commit 1076f4a172
19 changed files with 516 additions and 397 deletions
+19
View File
@@ -24,6 +24,8 @@ var {
ActivityIndicatorIOS ActivityIndicatorIOS
} = React; } = 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 ImageCapInsetsExample = require('./ImageCapInsetsExample');
var NetworkImageExample = React.createClass({ var NetworkImageExample = React.createClass({
@@ -335,6 +337,18 @@ exports.examples = [
}, },
platform: 'ios', platform: 'ios',
}, },
{
title: 'Base64 image',
render: function() {
return (
<Image
style={styles.base64}
source={{uri: base64Icon, scale: 3}}
/>
);
},
platform: 'ios',
},
{ {
title: 'Cap Insets', title: 'Cap Insets',
description: description:
@@ -399,4 +413,9 @@ var styles = StyleSheet.create({
flex: 1, flex: 1,
height: 200, height: 200,
}, },
base64: {
flex: 1,
height: 50,
resizeMode: 'contain',
},
}); });
@@ -25,14 +25,14 @@ typedef RCTImageLoaderCancellationBlock (^RCTImageURLLoaderLoadImageURLHandler)(
@end @end
typedef BOOL (^RCTImageDecoderCanDecodeImageDataHandler)(NSData *imageData); typedef BOOL (^RCTImageDataDecoderCanDecodeImageDataHandler)(NSData *imageData);
typedef RCTImageLoaderCancellationBlock (^RCTImageDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler); typedef RCTImageLoaderCancellationBlock (^RCTImageDataDecoderDecodeImageDataHandler)(NSData *imageData, CGSize size, CGFloat scale, UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler);
@interface RCTConcreteImageDecoder : NSObject <RCTImageDecoder> @interface RCTConcreteImageDecoder : NSObject <RCTImageDataDecoder>
- (instancetype)initWithPriority:(float)priority - (instancetype)initWithPriority:(float)priority
canDecodeImageDataHandler:(RCTImageDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler canDecodeImageDataHandler:(RCTImageDataDecoderCanDecodeImageDataHandler)canDecodeImageDataHandler
decodeImageDataHandler:(RCTImageDecoderDecodeImageDataHandler)decodeImageDataHandler; decodeImageDataHandler:(RCTImageDataDecoderDecodeImageDataHandler)decodeImageDataHandler;
@end @end
@@ -52,7 +52,7 @@
return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler); return _loadImageURLHandler(imageURL, size, scale, resizeMode, progressHandler, completionHandler);
} }
- (float)imageLoaderPriority - (float)loaderPriority
{ {
return _priority; return _priority;
} }
@@ -61,8 +61,8 @@
@implementation RCTConcreteImageDecoder @implementation RCTConcreteImageDecoder
{ {
RCTImageDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler; RCTImageDataDecoderCanDecodeImageDataHandler _canDecodeImageDataHandler;
RCTImageDecoderDecodeImageDataHandler _decodeImageDataHandler; RCTImageDataDecoderDecodeImageDataHandler _decodeImageDataHandler;
float _priority; float _priority;
} }
@@ -76,7 +76,7 @@
return nil; 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])) { if ((self = [super init])) {
_canDecodeImageDataHandler = [canDecodeImageDataHandler copy]; _canDecodeImageDataHandler = [canDecodeImageDataHandler copy];
@@ -97,7 +97,7 @@
return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler); return _decodeImageDataHandler(imageData, size, scale, resizeMode, completionHandler);
} }
- (float)imageDecoderPriority - (float)decoderPriority
{ {
return _priority; return _priority;
} }
@@ -95,7 +95,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO]; NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
UIImage *image = [[UIImage alloc] initWithData:data]; UIImage *image = [[UIImage alloc] initWithData:data];
id<RCTImageDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) { id<RCTImageDataDecoder> decoder = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES; return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) { } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data); XCTAssertEqualObjects(imageData, data);
@@ -118,7 +118,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO]; NSData *data = [NSData dataWithBytesNoCopy:blackGIF length:sizeof(blackGIF) freeWhenDone:NO];
UIImage *image = [[UIImage alloc] initWithData:data]; UIImage *image = [[UIImage alloc] initWithData:data];
id<RCTImageDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) { id<RCTImageDataDecoder> decoder1 = [[RCTImageLoaderTestsDecoder1 alloc] initWithPriority:1.0 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES; return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) { } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, RCTImageLoaderCompletionBlock completionHandler) {
XCTAssertEqualObjects(imageData, data); XCTAssertEqualObjects(imageData, data);
@@ -126,7 +126,7 @@ RCTDefineImageDecoder(RCTImageLoaderTestsDecoder2)
return nil; return nil;
}]; }];
id<RCTImageDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) { id<RCTImageDataDecoder> decoder2 = [[RCTImageLoaderTestsDecoder2 alloc] initWithPriority:0.5 canDecodeImageDataHandler:^BOOL(__unused NSData *imageData) {
return YES; return YES;
} decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) { } decodeImageDataHandler:^RCTImageLoaderCancellationBlock(__unused NSData *imageData, __unused CGSize size, __unused CGFloat scale, __unused UIViewContentMode resizeMode, __unused RCTImageLoaderCompletionBlock completionHandler) {
XCTFail(@"Should not have used decoder2"); XCTFail(@"Should not have used decoder2");
+1 -1
View File
@@ -9,6 +9,6 @@
#import "RCTImageLoader.h" #import "RCTImageLoader.h"
@interface RCTGIFImageDecoder : NSObject <RCTImageDecoder> @interface RCTGIFImageDecoder : NSObject <RCTImageDataDecoder>
@end @end
@@ -17,7 +17,6 @@
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; }; 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 143879371AAD32A300F088A5 /* RCTImageLoader.m */; };
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; }; 35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 35123E6A1B59C99D00EBAD80 /* RCTImageStoreManager.m */; };
354631681B69857700AA0B86 /* RCTImageEditingManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 354631671B69857700AA0B86 /* RCTImageEditingManager.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 */ /* End PBXBuildFile section */
/* Begin PBXCopyFilesBuildPhase section */ /* Begin PBXCopyFilesBuildPhase section */
@@ -54,8 +53,6 @@
354631661B69857700AA0B86 /* RCTImageEditingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageEditingManager.h; sourceTree = "<group>"; }; 354631661B69857700AA0B86 /* RCTImageEditingManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTImageEditingManager.h; sourceTree = "<group>"; };
354631671B69857700AA0B86 /* RCTImageEditingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageEditingManager.m; sourceTree = "<group>"; }; 354631671B69857700AA0B86 /* RCTImageEditingManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageEditingManager.m; sourceTree = "<group>"; };
58B5115D1A9E6B3D00147676 /* libRCTImage.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTImage.a; sourceTree = BUILT_PRODUCTS_DIR; }; 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 = "<group>"; };
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTImageDownloader.m; sourceTree = "<group>"; };
/* End PBXFileReference section */ /* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */ /* Begin PBXFrameworksBuildPhase section */
@@ -76,8 +73,6 @@
13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */, 13EF7F7E1BC825B1003F47DD /* RCTXCAssetImageLoader.m */,
1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */, 1304D5B01AA8C50D0002E2BE /* RCTGIFImageDecoder.h */,
1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */, 1304D5B11AA8C50D0002E2BE /* RCTGIFImageDecoder.m */,
58B511891A9E6BD600147676 /* RCTImageDownloader.h */,
58B5118A1A9E6BD600147676 /* RCTImageDownloader.m */,
354631661B69857700AA0B86 /* RCTImageEditingManager.h */, 354631661B69857700AA0B86 /* RCTImageEditingManager.h */,
354631671B69857700AA0B86 /* RCTImageEditingManager.m */, 354631671B69857700AA0B86 /* RCTImageEditingManager.m */,
143879361AAD32A300F088A5 /* RCTImageLoader.h */, 143879361AAD32A300F088A5 /* RCTImageLoader.h */,
@@ -166,7 +161,6 @@
files = ( files = (
13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */, 13EF7F0C1BC42D4E003F47DD /* RCTVirtualImageManager.m in Sources */,
35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */, 35123E6B1B59C99D00EBAD80 /* RCTImageStoreManager.m in Sources */,
58B5118F1A9E6BD600147676 /* RCTImageDownloader.m in Sources */,
1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */, 1304D5AC1AA8C4A30002E2BE /* RCTImageViewManager.m in Sources */,
1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */, 1304D5B21AA8C50D0002E2BE /* RCTGIFImageDecoder.m in Sources */,
143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */, 143879381AAD32A300F088A5 /* RCTImageLoader.m in Sources */,
-178
View File
@@ -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", @"data", 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
+23 -13
View File
@@ -46,7 +46,8 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
/** /**
* Finds an appropriate image decoder and passes the target size, scale and * 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 - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)imageData
size:(CGSize)size size:(CGSize)size
@@ -66,7 +67,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
@end @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 * loaders are also bridge modules, so should be registered using
* RCT_EXPORT_MODULE(). * RCT_EXPORT_MODULE().
*/ */
@@ -85,18 +86,23 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
* has finished. The method should also return a cancellation block, if * has finished. The method should also return a cancellation block, if
* applicable. * 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 @optional
/** /**
* If more than one RCTImageURLLoader responds YES to `-canLoadImageURL:` * 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 * 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. * undefined.
*/ */
- (float)imageLoaderPriority; - (float)loaderPriority;
@end @end
@@ -104,7 +110,7 @@ typedef void (^RCTImageLoaderCancellationBlock)(void);
* Provides the interface needed to register an image decoder. Image decoders * Provides the interface needed to register an image decoder. Image decoders
* are also bridge modules, so should be registered using RCT_EXPORT_MODULE(). * are also bridge modules, so should be registered using RCT_EXPORT_MODULE().
*/ */
@protocol RCTImageDecoder <RCTBridgeModule> @protocol RCTImageDataDecoder <RCTBridgeModule>
/** /**
* Indicates whether this handler is capable of decoding the specified data. * 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 * completionHandler when the decoding operation has finished. The method
* should also return a cancellation block, if applicable. * 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 @optional
/** /**
* If more than one RCTImageDecoder responds YES to `-canDecodeImageData:` * If more than one RCTImageDataDecoder responds YES to `-canDecodeImageData:`
* then `imageDecoderPriority` is used to determine which one to use. The * then `decoderPriority` is used to determine which one to use. The decoder
* handler with the highest priority will be selected. Default priority is zero. * 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 two or more valid decoders have the same priority, the selection order is
* undefined. * undefined.
*/ */
- (float)imageDecoderPriority; - (float)decoderPriority;
@end @end
+249 -87
View File
@@ -13,9 +13,9 @@
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTDefines.h" #import "RCTDefines.h"
#import "RCTImageDownloader.h"
#import "RCTImageUtils.h" #import "RCTImageUtils.h"
#import "RCTLog.h" #import "RCTLog.h"
#import "RCTNetworking.h"
#import "RCTUtils.h" #import "RCTUtils.h"
static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image) static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSError *error, UIImage *image)
@@ -44,11 +44,131 @@ static void RCTDispatchCallbackOnMainQueue(void (^callback)(NSError *, id), NSEr
@end @end
@implementation RCTImageLoader @implementation RCTImageLoader
{
NSArray *_loaders;
NSArray *_decoders;
NSURLCache *_cache;
}
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
- (void)setBridge:(RCTBridge *)bridge
{
// Get image loaders and decoders
NSMutableArray *loaders = [NSMutableArray array];
NSMutableArray *decoders = [NSMutableArray array];
for (id<RCTBridgeModule> 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<RCTImageURLLoader> a, id<RCTImageURLLoader> 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<RCTImageDataDecoder> a, id<RCTImageDataDecoder> 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<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
{
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
id<RCTImageURLLoader> previousLoader = nil;
for (id<RCTImageURLLoader> 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<RCTImageURLLoader> loader in _loaders) {
if ([loader canLoadImageURL:URL]) {
return loader;
}
}
return nil;
}
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
{
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
id<RCTImageDataDecoder> previousDecoder = nil;
for (id<RCTImageDataDecoder> 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 <NSData %p; %tu bytes>, 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<RCTImageDataDecoder> decoder in _decoders) {
if ([decoder canDecodeImageData:data]) {
return decoder;
}
}
return nil;
}
- (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
callback:(RCTImageLoaderCompletionBlock)callback callback:(RCTImageLoaderCompletionBlock)callback
{ {
@@ -60,35 +180,6 @@ RCT_EXPORT_MODULE()
completionBlock:callback]; completionBlock:callback];
} }
- (id<RCTImageURLLoader>)imageURLLoaderForRequest:(NSURL *)requestURL
{
NSMutableArray *handlers = [NSMutableArray array];
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTImageURLLoader)]) {
if ([(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
[handlers addObject:module];
}
}
}
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> 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 - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
size:(CGSize)size size:(CGSize)size
scale:(CGFloat)scale scale:(CGFloat)scale
@@ -101,58 +192,125 @@ RCT_EXPORT_MODULE()
return ^{}; return ^{};
} }
NSURL *requestURL = [RCTConvert NSURL:imageTag]; // Ensure progress is dispatched on main thread
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForRequest:requestURL]; RCTImageLoaderProgressBlock progressHandler = nil;
if (!loadHandler) { if (progressBlock) {
RCTLogError(@"No suitable image URL loader found for %@", imageTag); 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<RCTImageURLLoader> 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 ^{};
} }
return [loadHandler loadImageForURL:requestURL size:size scale:scale resizeMode:resizeMode progressHandler:^(int64_t progress, int64_t total) { // Use networking module to load image
if (!progressBlock) { if ([_bridge.networking canHandleRequest:request]) {
return;
}
if ([NSThread isMainThread]) { __weak RCTImageLoader *weakSelf = self;
progressBlock(progress, total); __block RCTImageLoaderCancellationBlock decodeCancel = nil;
} else { RCTURLRequestCompletionBlock processResponse =
dispatch_async(dispatch_get_main_queue(), ^{ ^(NSURLResponse *response, NSData *data, __unused NSError *error) {
progressBlock(progress, total);
});
}
} completionHandler:^(NSError *error, UIImage *image) {
RCTDispatchCallbackOnMainQueue(completionBlock, error, image);
}];
}
- (id<RCTImageDecoder>)imageDecoderForRequest:(NSData *)imageData // Check for http errors
{ if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSMutableArray *handlers = [NSMutableArray array]; NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
for (id<RCTBridgeModule> module in _bridge.modules.allValues) { if (statusCode != 200) {
if ([module conformsToProtocol:@protocol(RCTImageDecoder)]) { completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
if ([(id<RCTImageDecoder>)module canDecodeImageData:imageData]) { code:statusCode
[handlers addObject:module]; userInfo:nil], nil);
return;
}
} }
}
}
[handlers sortUsingComparator:^NSComparisonResult(id<RCTImageDecoder> a, id<RCTImageDecoder> 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 <NSData %p; %tu bytes>, 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 - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
@@ -161,20 +319,22 @@ RCT_EXPORT_MODULE()
resizeMode:(UIViewContentMode)resizeMode resizeMode:(UIViewContentMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{ {
id<RCTImageDecoder> imageDecoder = [self imageDecoderForRequest:data]; id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
if (imageDecoder) { if (imageDecoder) {
return [imageDecoder decodeImageData:data size:size scale:scale resizeMode:resizeMode completionHandler:^(NSError *error, UIImage *image) { return [imageDecoder decodeImageData:data
RCTDispatchCallbackOnMainQueue(completionBlock, error, image); size:size
}]; scale:scale
resizeMode:resizeMode
completionHandler:completionBlock];
} else { } else {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
UIImage *image = [UIImage imageWithData:data scale:scale]; UIImage *image = [UIImage imageWithData:data scale:scale];
if (image) { if (image) {
RCTDispatchCallbackOnMainQueue(completionBlock, nil, image); completionBlock(nil, image);
} else { } else {
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length]; NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
NSError *finalError = RCTErrorWithMessage(errorMessage); NSError *finalError = RCTErrorWithMessage(errorMessage);
RCTDispatchCallbackOnMainQueue(completionBlock, finalError, nil); completionBlock(finalError, nil);
} }
}); });
return ^{}; return ^{};
@@ -185,18 +345,20 @@ RCT_EXPORT_MODULE()
- (BOOL)canHandleRequest:(NSURLRequest *)request - (BOOL)canHandleRequest:(NSURLRequest *)request
{ {
id<RCTImageURLLoader> handler = [self imageURLLoaderForRequest:request.URL]; NSURL *requestURL = request.URL;
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
// RCTImageDownloader is an image plugin that uses the networking stack. if ([module conformsToProtocol:@protocol(RCTImageURLLoader)] &&
// We don't want to route any network calls through the image downloader [(id<RCTImageURLLoader>)module canLoadImageURL:requestURL]) {
// as that would cause cyclical dependencies. return YES;
return handler && ![handler isKindOfClass:[RCTImageDownloader class]]; }
}
return NO;
} }
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate - (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
{ {
__block RCTImageLoaderCancellationBlock requestToken; __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) { if (error) {
[delegate URLRequest:requestToken didCompleteWithError:error]; [delegate URLRequest:requestToken didCompleteWithError:error];
return; return;
+6 -3
View File
@@ -11,6 +11,7 @@
#import "RCTImageLoader.h" #import "RCTImageLoader.h"
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTUIManager.h"
@implementation RCTShadowVirtualImage @implementation RCTShadowVirtualImage
{ {
@@ -38,9 +39,11 @@ RCT_NOT_IMPLEMENTED(-(instancetype)init)
__weak RCTShadowVirtualImage *weakSelf = self; __weak RCTShadowVirtualImage *weakSelf = self;
[_bridge.imageLoader loadImageWithTag:imageTag size:CGSizeZero scale:scale resizeMode:UIViewContentModeScaleToFill progressBlock:nil completionBlock:^(NSError *error, UIImage *image) { [_bridge.imageLoader loadImageWithTag:imageTag size:CGSizeZero scale:scale resizeMode:UIViewContentModeScaleToFill progressBlock:nil completionBlock:^(NSError *error, UIImage *image) {
RCTShadowVirtualImage *strongSelf = weakSelf; dispatch_async(_bridge.uiManager.methodQueue, ^{
strongSelf->_image = image; RCTShadowVirtualImage *strongSelf = weakSelf;
[strongSelf dirtyText]; strongSelf->_image = image;
[strongSelf dirtyText];
});
}]; }];
} }
} }
-5
View File
@@ -20,11 +20,6 @@ RCT_EXPORT_MODULE()
return RCTIsXCAssetURL(requestURL); 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 - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
{ {
__block BOOL cancelled = NO; __block BOOL cancelled = NO;
@@ -7,15 +7,12 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import "RCTBridge.h" #import "RCTURLRequestHandler.h"
#import "RCTImageLoader.h" #import "RCTInvalidating.h"
@interface RCTImageDownloader : NSObject <RCTImageURLLoader> /**
* This is the default RCTURLRequestHandler implementation for data URL requests.
@end */
@interface RCTDataRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
@interface RCTBridge (RCTImageDownloader)
@property (nonatomic, readonly) RCTImageDownloader *imageDownloader;
@end @end
+73
View File
@@ -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<RCTURLRequestDelegate>)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
+5 -3
View File
@@ -11,13 +11,13 @@
#import <MobileCoreServices/MobileCoreServices.h> #import <MobileCoreServices/MobileCoreServices.h>
#import "RCTUtils.h"
@implementation RCTFileRequestHandler @implementation RCTFileRequestHandler
{ {
NSOperationQueue *_fileQueue; NSOperationQueue *_fileQueue;
} }
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
- (void)invalidate - (void)invalidate
@@ -28,7 +28,9 @@ RCT_EXPORT_MODULE()
- (BOOL)canHandleRequest:(NSURLRequest *)request - (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 - (NSOperation *)sendRequest:(NSURLRequest *)request
@@ -7,8 +7,9 @@
objects = { objects = {
/* Begin PBXBuildFile section */ /* 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 */; }; 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 = (); }; }; 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 */; }; 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; }; 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
@@ -27,10 +28,12 @@
/* End PBXCopyFilesBuildPhase section */ /* End PBXCopyFilesBuildPhase section */
/* Begin PBXFileReference section */ /* Begin PBXFileReference section */
134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDataRequestHandler.h; sourceTree = "<group>"; };
134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDataRequestHandler.m; sourceTree = "<group>"; };
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTNetInfo.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; }; 1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; lineEnding = 0; path = RCTNetInfo.h; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objcpp; };
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; }; 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = "<group>"; }; 13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTNetworkTask.h; sourceTree = "<group>"; };
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = "<group>"; }; 13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTNetworkTask.m; sourceTree = "<group>"; };
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFileRequestHandler.h; sourceTree = "<group>"; }; 13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFileRequestHandler.h; sourceTree = "<group>"; };
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileRequestHandler.m; sourceTree = "<group>"; }; 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileRequestHandler.m; sourceTree = "<group>"; };
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; }; 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
@@ -54,12 +57,14 @@
58B511D21A9E6C8500147676 = { 58B511D21A9E6C8500147676 = {
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */, 13D6D6681B5FCF8200883BE9 /* RCTNetworkTask.h */,
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */, 13D6D6691B5FCF8200883BE9 /* RCTNetworkTask.m */,
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */, 352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */, 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */, 13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */,
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */, 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */,
134E96981BCEB7F800AFFDA1 /* RCTDataRequestHandler.h */,
134E96991BCEB7F800AFFDA1 /* RCTDataRequestHandler.m */,
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */, 1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */,
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */, 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */,
58B512061A9E6CE300147676 /* RCTNetworking.h */, 58B512061A9E6CE300147676 /* RCTNetworking.h */,
@@ -134,8 +139,9 @@
isa = PBXSourcesBuildPhase; isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647; buildActionMask = 2147483647;
files = ( files = (
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */, 13D6D66A1B5FCF8200883BE9 /* RCTNetworkTask.m in Sources */,
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */, 13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */,
134E969A1BCEB7F800AFFDA1 /* RCTDataRequestHandler.m in Sources */,
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */, 1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */,
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */, 58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */, 352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,
@@ -18,7 +18,7 @@ typedef void (^RCTURLRequestIncrementalDataBlock)(NSData *data);
typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total); typedef void (^RCTURLRequestProgressBlock)(int64_t progress, int64_t total);
typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response); typedef void (^RCTURLRequestResponseBlock)(NSURLResponse *response);
@interface RCTDownloadTask : NSObject <RCTURLRequestDelegate> @interface RCTNetworkTask : NSObject <RCTURLRequestDelegate>
@property (nonatomic, readonly) NSURLRequest *request; @property (nonatomic, readonly) NSURLRequest *request;
@property (nonatomic, readonly) NSNumber *requestID; @property (nonatomic, readonly) NSNumber *requestID;
@@ -7,15 +7,15 @@
* of patent rights can be found in the PATENTS file in the same directory. * of patent rights can be found in the PATENTS file in the same directory.
*/ */
#import "RCTDownloadTask.h" #import "RCTNetworkTask.h"
#import "RCTLog.h" #import "RCTLog.h"
@implementation RCTDownloadTask @implementation RCTNetworkTask
{ {
NSMutableData *_data; NSMutableData *_data;
id<RCTURLRequestHandler> _handler; id<RCTURLRequestHandler> _handler;
RCTDownloadTask *_selfReference; RCTNetworkTask *_selfReference;
} }
- (instancetype)initWithRequest:(NSURLRequest *)request - (instancetype)initWithRequest:(NSURLRequest *)request
+12 -3
View File
@@ -10,12 +10,21 @@
#import <Foundation/Foundation.h> #import <Foundation/Foundation.h>
#import "RCTBridge.h" #import "RCTBridge.h"
#import "RCTDownloadTask.h" #import "RCTNetworkTask.h"
@interface RCTNetworking : NSObject <RCTBridgeModule> @interface RCTNetworking : NSObject <RCTBridgeModule>
- (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 @end
+93 -66
View File
@@ -11,7 +11,7 @@
#import "RCTAssert.h" #import "RCTAssert.h"
#import "RCTConvert.h" #import "RCTConvert.h"
#import "RCTDownloadTask.h" #import "RCTNetworkTask.h"
#import "RCTURLRequestHandler.h" #import "RCTURLRequestHandler.h"
#import "RCTEventDispatcher.h" #import "RCTEventDispatcher.h"
#import "RCTHTTPRequestHandler.h" #import "RCTHTTPRequestHandler.h"
@@ -37,10 +37,10 @@ typedef RCTURLRequestCancellationBlock (^RCTHTTPQueryResult)(NSError *error, NSD
@implementation RCTHTTPFormDataHelper @implementation RCTHTTPFormDataHelper
{ {
NSMutableArray *parts; NSMutableArray *_parts;
NSMutableData *multipartBody; NSMutableData *_multipartBody;
RCTHTTPQueryResult _callback; RCTHTTPQueryResult _callback;
NSString *boundary; NSString *_boundary;
} }
static NSString *RCTGenerateFormBoundary() static NSString *RCTGenerateFormBoundary()
@@ -56,19 +56,19 @@ static NSString *RCTGenerateFormBoundary()
return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES]; return [[NSString alloc] initWithBytesNoCopy:bytes length:boundaryLength encoding:NSUTF8StringEncoding freeWhenDone:YES];
} }
- (RCTURLRequestCancellationBlock)process:(NSArray *)formData - (RCTURLRequestCancellationBlock)process:(NSDictionaryArray *)formData
callback:(RCTHTTPQueryResult)callback callback:(RCTHTTPQueryResult)callback
{ {
if (formData.count == 0) { if (formData.count == 0) {
return callback(nil, nil); return callback(nil, nil);
} }
parts = [formData mutableCopy]; _parts = [formData mutableCopy];
_callback = callback; _callback = callback;
multipartBody = [NSMutableData new]; _multipartBody = [NSMutableData new];
boundary = RCTGenerateFormBoundary(); _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]; return [self handleResult:result error:error];
}]; }];
} }
@@ -81,37 +81,37 @@ static NSString *RCTGenerateFormBoundary()
} }
// Start with boundary. // Start with boundary.
[multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", boundary] [_multipartBody appendData:[[NSString stringWithFormat:@"--%@\r\n", _boundary]
dataUsingEncoding:NSUTF8StringEncoding]]; dataUsingEncoding:NSUTF8StringEncoding]];
// Print headers. // Print headers.
NSMutableDictionary *headers = [parts[0][@"headers"] mutableCopy]; NSMutableDictionary *headers = [_parts[0][@"headers"] mutableCopy];
NSString *partContentType = result[@"contentType"]; NSString *partContentType = result[@"contentType"];
if (partContentType != nil) { if (partContentType != nil) {
headers[@"content-type"] = partContentType; headers[@"content-type"] = partContentType;
} }
[headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) { [headers enumerateKeysAndObjectsUsingBlock:^(NSString *parameterKey, NSString *parameterValue, BOOL *stop) {
[multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue] [_multipartBody appendData:[[NSString stringWithFormat:@"%@: %@\r\n", parameterKey, parameterValue]
dataUsingEncoding:NSUTF8StringEncoding]]; dataUsingEncoding:NSUTF8StringEncoding]];
}]; }];
// Add the body. // Add the body.
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[multipartBody appendData:result[@"body"]]; [_multipartBody appendData:result[@"body"]];
[multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]]; [_multipartBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
[parts removeObjectAtIndex:0]; [_parts removeObjectAtIndex:0];
if (parts.count) { if (_parts.count) {
return [_networker processDataForHTTPQuery:parts[0] callback:^(NSError *err, NSDictionary *res) { return [_networker processDataForHTTPQuery:_parts[0] callback:^(NSError *err, NSDictionary *res) {
return [self handleResult:res error:err]; return [self handleResult:res error:err];
}]; }];
} }
// We've processed the last item. Finish and return. // We've processed the last item. Finish and return.
[multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", boundary] [_multipartBody appendData:[[NSString stringWithFormat:@"--%@--\r\n", _boundary]
dataUsingEncoding:NSUTF8StringEncoding]]; dataUsingEncoding:NSUTF8StringEncoding]];
NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", boundary]; NSString *contentType = [NSString stringWithFormat:@"multipart/form-data; boundary=\"%@\"", _boundary];
return _callback(nil, @{@"body": multipartBody, @"contentType": contentType}); return _callback(nil, @{@"body": _multipartBody, @"contentType": contentType});
} }
@end @end
@@ -122,6 +122,7 @@ static NSString *RCTGenerateFormBoundary()
@implementation RCTNetworking @implementation RCTNetworking
{ {
NSMutableDictionary *_tasksByRequestID; NSMutableDictionary *_tasksByRequestID;
NSArray *_handlers;
} }
@synthesize bridge = _bridge; @synthesize bridge = _bridge;
@@ -129,12 +130,65 @@ static NSString *RCTGenerateFormBoundary()
RCT_EXPORT_MODULE() RCT_EXPORT_MODULE()
- (instancetype)init - (void)setBridge:(RCTBridge *)bridge
{ {
if ((self = [super init])) { // get handlers
_tasksByRequestID = [NSMutableDictionary new]; NSMutableArray *handlers = [NSMutableArray array];
for (id<RCTBridgeModule> 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<RCTURLRequestHandler> a, id<RCTURLRequestHandler> 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<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request
{
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
id<RCTURLRequestHandler> previousHandler = nil;
for (id<RCTURLRequestHandler> 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<RCTURLRequestHandler> handler in _handlers) {
if ([handler canHandleRequest:request]) {
return handler;
}
}
return nil;
} }
- (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query - (RCTURLRequestCancellationBlock)buildRequest:(NSDictionary *)query
@@ -169,37 +223,9 @@ RCT_EXPORT_MODULE()
}]; }];
} }
- (id<RCTURLRequestHandler>)handlerForRequest:(NSURLRequest *)request - (BOOL)canHandleRequest:(NSURLRequest *)request
{ {
NSMutableArray *handlers = [NSMutableArray array]; return [self handlerForRequest:request] != nil;
for (id<RCTBridgeModule> module in _bridge.modules.allValues) {
if ([module conformsToProtocol:@protocol(RCTURLRequestHandler)]) {
if ([(id<RCTURLRequestHandler>)module canHandleRequest:request]) {
[handlers addObject:module];
}
}
}
[handlers sortUsingComparator:^NSComparisonResult(id<RCTURLRequestHandler> a, id<RCTURLRequestHandler> 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<RCTURLRequestHandler> handler = handlers.lastObject;
if (!handler) {
RCTLogError(@"No suitable request handler found for %@", request.URL);
}
return handler;
} }
/** /**
@@ -234,13 +260,13 @@ RCT_EXPORT_MODULE()
if (request) { if (request) {
__block RCTURLRequestCancellationBlock cancellationBlock = nil; __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); cancellationBlock = callback(error, data ? @{@"body": data, @"contentType": RCTNullIfNil(response.MIMEType)} : nil);
}]; }];
[task start]; [task start];
__weak RCTDownloadTask *weakTask = task; __weak RCTNetworkTask *weakTask = task;
return ^{ return ^{
[weakTask cancel]; [weakTask cancel];
if (cancellationBlock) { if (cancellationBlock) {
@@ -259,7 +285,7 @@ RCT_EXPORT_MODULE()
return callback(nil, nil); return callback(nil, nil);
} }
- (void)sendData:(NSData *)data forTask:(RCTDownloadTask *)task - (void)sendData:(NSData *)data forTask:(RCTNetworkTask *)task
{ {
if (data.length == 0) { if (data.length == 0) {
return; return;
@@ -278,7 +304,7 @@ RCT_EXPORT_MODULE()
if (!responseText && data.length) { if (!responseText && data.length) {
// We don't have an encoding, or the encoding is incorrect, so now we // 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: if ([NSString respondsToSelector:@selector(stringEncodingForData:
encodingOptions: encodingOptions:
convertedString: convertedString:
@@ -305,7 +331,7 @@ RCT_EXPORT_MODULE()
incrementalUpdates:(BOOL)incrementalUpdates incrementalUpdates:(BOOL)incrementalUpdates
responseSender:(RCTResponseSenderBlock)responseSender responseSender:(RCTResponseSenderBlock)responseSender
{ {
__block RCTDownloadTask *task; __block RCTNetworkTask *task;
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) { RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
dispatch_async(_methodQueue, ^{ 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.incrementalDataBlock = incrementalDataBlock;
task.responseBlock = responseBlock; task.responseBlock = responseBlock;
task.uploadProgressBlock = uploadProgressBlock; task.uploadProgressBlock = uploadProgressBlock;
@@ -370,15 +396,16 @@ RCT_EXPORT_MODULE()
#pragma mark - Public API #pragma mark - Public API
- (RCTDownloadTask *)downloadTaskWithRequest:(NSURLRequest *)request - (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{ {
id<RCTURLRequestHandler> handler = [self handlerForRequest:request]; id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
if (!handler) { if (!handler) {
RCTLogError(@"No suitable URL request handler found for %@", request.URL);
return nil; return nil;
} }
return [[RCTDownloadTask alloc] initWithRequest:request return [[RCTNetworkTask alloc] initWithRequest:request
handler:handler handler:handler
completionBlock:completionBlock]; completionBlock:completionBlock];
} }