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]; }