From 61c1e2a61b99ff4a4f0230b627786bb99a201572 Mon Sep 17 00:00:00 2001 From: Nick Lockwood Date: Mon, 19 Oct 2015 09:04:54 -0700 Subject: [PATCH] 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 --- Examples/UIExplorer/ImageExample.js | 19 + .../RCTImageLoaderHelpers.h | 10 +- .../RCTImageLoaderHelpers.m | 10 +- .../UIExplorerUnitTests/RCTImageLoaderTests.m | 6 +- Libraries/Image/RCTGIFImageDecoder.h | 2 +- .../Image/RCTImage.xcodeproj/project.pbxproj | 6 - Libraries/Image/RCTImageDownloader.m | 178 ---------- Libraries/Image/RCTImageLoader.h | 36 +- Libraries/Image/RCTImageLoader.m | 336 +++++++++++++----- Libraries/Image/RCTShadowVirtualImage.m | 9 +- Libraries/Image/RCTXCAssetImageLoader.m | 5 - .../RCTDataRequestHandler.h} | 15 +- Libraries/Network/RCTDataRequestHandler.m | 73 ++++ Libraries/Network/RCTFileRequestHandler.m | 8 +- .../RCTNetwork.xcodeproj/project.pbxproj | 18 +- .../{RCTDownloadTask.h => RCTNetworkTask.h} | 2 +- .../{RCTDownloadTask.m => RCTNetworkTask.m} | 6 +- Libraries/Network/RCTNetworking.h | 15 +- Libraries/Network/RCTNetworking.m | 159 +++++---- 19 files changed, 516 insertions(+), 397 deletions(-) delete mode 100644 Libraries/Image/RCTImageDownloader.m rename Libraries/{Image/RCTImageDownloader.h => Network/RCTDataRequestHandler.h} (57%) create mode 100644 Libraries/Network/RCTDataRequestHandler.m rename Libraries/Network/{RCTDownloadTask.h => RCTNetworkTask.h} (96%) rename Libraries/Network/{RCTDownloadTask.m => RCTNetworkTask.m} (97%) 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]; }