Skip to content

Commit a92f107

Browse files
nicklockwoodfacebook-github-bot-4
authored andcommitted
Added RCTFileRequestHandler
Summary: @​public We previously discovered that using an NSURLSessionDataTask to load local files is noticably less efficient than using regular filesystem methods. This diff adds RCTFileRequestHandler as a replacement for RCTHTTPRequestHandler when loading local files. This reduces loading time when loading local files via XMLHttpRequest, as well as improving the performance for some image load requests. Reviewed By: @javache Differential Revision: D2531710 fb-gh-sync-id: 259714baac131784de494d24939f42ad52bff41a
1 parent 28f6eba commit a92f107

File tree

7 files changed

+135
-40
lines changed

7 files changed

+135
-40
lines changed

Libraries/Image/RCTImageDownloader.m

Lines changed: 8 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,7 @@ - (RCTImageLoaderCancellationBlock)downloadDataForURL:(NSURL *)url
8686
}
8787

8888
RCTDownloadTask *task = [_bridge.networking downloadTaskWithRequest:request completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
89-
if (response && !error) {
89+
if (response && !error && [response.URL.scheme hasPrefix:@"http"]) {
9090
RCTImageDownloader *strongSelf = weakSelf;
9191
NSCachedURLResponse *cachedResponse = [[NSCachedURLResponse alloc] initWithResponse:response data:data userInfo:nil storagePolicy:NSURLCacheStorageAllowed];
9292
[strongSelf->_cache storeCachedResponse:cachedResponse forRequest:request];
@@ -106,9 +106,15 @@ - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
106106
progressHandler:(RCTImageLoaderProgressBlock)progressHandler
107107
completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
108108
{
109-
if ([imageURL.scheme.lowercaseString hasPrefix:@"http"]) {
109+
if ([imageURL.scheme.lowercaseString hasPrefix:@"http"] ||
110+
[imageURL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame) {
110111
__block RCTImageLoaderCancellationBlock decodeCancel = nil;
111112

113+
// Add missing png extension
114+
if (imageURL.fileURL && imageURL.pathExtension.length == 0) {
115+
imageURL = [NSURL fileURLWithPath:[imageURL.path stringByAppendingPathExtension:@"png"]];
116+
}
117+
112118
__weak RCTImageDownloader *weakSelf = self;
113119
RCTImageLoaderCancellationBlock downloadCancel = [self downloadDataForURL:imageURL progressHandler:progressHandler completionHandler:^(NSError *error, NSData *imageData) {
114120
if (error) {
@@ -153,31 +159,6 @@ - (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL
153159
return ^{
154160
cancelled = YES;
155161
};
156-
} else if ([imageURL.scheme isEqualToString:@"file"]) {
157-
__block BOOL cancelled = NO;
158-
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
159-
if (cancelled) {
160-
return;
161-
}
162-
163-
UIImage *image = [UIImage imageWithContentsOfFile:imageURL.resourceSpecifier];
164-
if (image) {
165-
if (progressHandler) {
166-
progressHandler(1, 1);
167-
}
168-
if (completionHandler) {
169-
completionHandler(nil, image);
170-
}
171-
} else {
172-
if (completionHandler) {
173-
NSString *message = [NSString stringWithFormat:@"Could not find image at path: %@", imageURL.absoluteString];
174-
completionHandler(RCTErrorWithMessage(message), nil);
175-
}
176-
}
177-
});
178-
return ^{
179-
cancelled = YES;
180-
};
181162
} else {
182163
RCTLogError(@"Unexpected image schema %@", imageURL.scheme);
183164
return ^{};

Libraries/Network/RCTDownloadTask.m

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
#import "RCTDownloadTask.h"
1111

12-
#import "RCTAssert.h"
12+
#import "RCTLog.h"
1313

1414
@implementation RCTDownloadTask
1515
{
@@ -65,8 +65,7 @@ - (BOOL)validateRequestToken:(id)requestToken
6565
{
6666
if (![requestToken isEqual:_requestToken]) {
6767
if (RCT_DEBUG) {
68-
RCTAssert([requestToken isEqual:_requestToken],
69-
@"Unrecognized request token: %@", requestToken);
68+
RCTLogError(@"Unrecognized request token: %@ expected: %@", requestToken, _requestToken);
7069
}
7170
if (_completionBlock) {
7271
_completionBlock(_response, _data, [NSError errorWithDomain:RCTErrorDomain code:0
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTURLRequestHandler.h"
11+
#import "RCTInvalidating.h"
12+
13+
/**
14+
* This is the default RCTURLRequestHandler implementation for file requests.
15+
*/
16+
@interface RCTFileRequestHandler : NSObject <RCTURLRequestHandler, RCTInvalidating>
17+
18+
@end
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
/**
2+
* Copyright (c) 2015-present, Facebook, Inc.
3+
* All rights reserved.
4+
*
5+
* This source code is licensed under the BSD-style license found in the
6+
* LICENSE file in the root directory of this source tree. An additional grant
7+
* of patent rights can be found in the PATENTS file in the same directory.
8+
*/
9+
10+
#import "RCTFileRequestHandler.h"
11+
12+
#import <MobileCoreServices/MobileCoreServices.h>
13+
14+
@implementation RCTFileRequestHandler
15+
{
16+
NSOperationQueue *_fileQueue;
17+
}
18+
19+
@synthesize methodQueue = _methodQueue;
20+
21+
RCT_EXPORT_MODULE()
22+
23+
- (void)invalidate
24+
{
25+
[_fileQueue cancelAllOperations];
26+
_fileQueue = nil;
27+
}
28+
29+
- (BOOL)canHandleRequest:(NSURLRequest *)request
30+
{
31+
return [request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame;
32+
}
33+
34+
- (NSOperation *)sendRequest:(NSURLRequest *)request
35+
withDelegate:(id<RCTURLRequestDelegate>)delegate
36+
{
37+
// Lazy setup
38+
if (!_fileQueue) {
39+
_fileQueue = [NSOperationQueue new];
40+
_fileQueue.maxConcurrentOperationCount = 4;
41+
}
42+
43+
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
44+
45+
// Get content length
46+
NSError *error = nil;
47+
NSFileManager *fileManager = [NSFileManager new];
48+
NSDictionary *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
49+
if (error) {
50+
[delegate URLRequest:op didCompleteWithError:error];
51+
return;
52+
}
53+
54+
// Get mime type
55+
NSString *fileExtension = [request.URL pathExtension];
56+
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(
57+
kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
58+
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(
59+
(__bridge CFStringRef)UTI, kUTTagClassMIMEType);
60+
61+
// Send response
62+
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
63+
MIMEType:contentType
64+
expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue]
65+
textEncodingName:nil];
66+
67+
[delegate URLRequest:op didReceiveResponse:response];
68+
69+
// Load data
70+
NSData *data = [NSData dataWithContentsOfURL:request.URL
71+
options:NSDataReadingMappedIfSafe
72+
error:&error];
73+
if (data) {
74+
[delegate URLRequest:op didReceiveData:data];
75+
}
76+
[delegate URLRequest:op didCompleteWithError:error];
77+
}];
78+
79+
[_fileQueue addOperation:op];
80+
return op;
81+
}
82+
83+
- (void)cancelRequest:(NSOperation *)op
84+
{
85+
[op cancel];
86+
}
87+
88+
@end

Libraries/Network/RCTHTTPRequestHandler.m

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,9 @@ - (BOOL)canHandleRequest:(NSURLRequest *)request
5050
static NSSet *schemes = nil;
5151
static dispatch_once_t onceToken;
5252
dispatch_once(&onceToken, ^{
53-
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", @"file", nil];
53+
// technically, RCTHTTPRequestHandler can handle file:// as well,
54+
// but it's less efficient than using RCTFileRequestHandler
55+
schemes = [[NSSet alloc] initWithObjects:@"http", @"https", nil];
5456
});
5557
return [schemes containsObject:request.URL.scheme.lowercaseString];
5658
}

Libraries/Network/RCTNetwork.xcodeproj/project.pbxproj

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
/* Begin PBXBuildFile section */
1010
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */ = {isa = PBXBuildFile; fileRef = 1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */; };
1111
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */ = {isa = PBXBuildFile; fileRef = 13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */; };
12+
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */; settings = {ASSET_TAGS = (); }; };
1213
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */ = {isa = PBXBuildFile; fileRef = 352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */; };
1314
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */ = {isa = PBXBuildFile; fileRef = 58B512071A9E6CE300147676 /* RCTNetworking.m */; };
1415
/* End PBXBuildFile section */
@@ -30,6 +31,8 @@
3031
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; lineEnding = 0; path = RCTNetInfo.m; sourceTree = "<group>"; xcLanguageSpecificationIdentifier = xcode.lang.objc; };
3132
13D6D6681B5FCF8200883BE9 /* RCTDownloadTask.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTDownloadTask.h; sourceTree = "<group>"; };
3233
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTDownloadTask.m; sourceTree = "<group>"; };
34+
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTFileRequestHandler.h; sourceTree = "<group>"; };
35+
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTFileRequestHandler.m; sourceTree = "<group>"; };
3336
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RCTHTTPRequestHandler.h; sourceTree = "<group>"; };
3437
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = RCTHTTPRequestHandler.m; sourceTree = "<group>"; };
3538
58B511DB1A9E6C8500147676 /* libRCTNetwork.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = libRCTNetwork.a; sourceTree = BUILT_PRODUCTS_DIR; };
@@ -55,6 +58,8 @@
5558
13D6D6691B5FCF8200883BE9 /* RCTDownloadTask.m */,
5659
352DA0B71B17855800AA15A8 /* RCTHTTPRequestHandler.h */,
5760
352DA0B81B17855800AA15A8 /* RCTHTTPRequestHandler.m */,
61+
13EF800C1BCBE015003F47DD /* RCTFileRequestHandler.h */,
62+
13EF800D1BCBE015003F47DD /* RCTFileRequestHandler.m */,
5863
1372B7351AB03E7B00659ED6 /* RCTNetInfo.h */,
5964
1372B7361AB03E7B00659ED6 /* RCTNetInfo.m */,
6065
58B512061A9E6CE300147676 /* RCTNetworking.h */,
@@ -130,6 +135,7 @@
130135
buildActionMask = 2147483647;
131136
files = (
132137
13D6D66A1B5FCF8200883BE9 /* RCTDownloadTask.m in Sources */,
138+
13EF800E1BCBE015003F47DD /* RCTFileRequestHandler.m in Sources */,
133139
1372B7371AB03E7B00659ED6 /* RCTNetInfo.m in Sources */,
134140
58B512081A9E6CE300147676 /* RCTNetworking.m in Sources */,
135141
352DA0BA1B17855800AA15A8 /* RCTHTTPRequestHandler.m in Sources */,

Libraries/Network/RCTNetworking.m

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -314,16 +314,17 @@ - (void)sendRequest:(NSURLRequest *)request
314314

315315
void (^responseBlock)(NSURLResponse *) = ^(NSURLResponse *response) {
316316
dispatch_async(_methodQueue, ^{
317-
NSHTTPURLResponse *httpResponse = nil;
318-
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
319-
// Might be a local file request
320-
httpResponse = (NSHTTPURLResponse *)response;
317+
NSDictionary *headers;
318+
NSInteger status;
319+
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request
320+
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
321+
headers = httpResponse.allHeaderFields ?: @{};
322+
status = httpResponse.statusCode;
323+
} else {
324+
headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{};
325+
status = 200;
321326
}
322-
NSArray *responseJSON = @[task.requestID,
323-
@(httpResponse.statusCode ?: 200),
324-
httpResponse.allHeaderFields ?: @{},
325-
];
326-
327+
NSArray *responseJSON = @[task.requestID, @(status), headers];
327328
[_bridge.eventDispatcher sendDeviceEventWithName:@"didReceiveNetworkResponse"
328329
body:responseJSON];
329330
});

0 commit comments

Comments
 (0)