Perform all callbacks from RCTNetworkTasks on a given queue

Reviewed By: mmmulani

Differential Revision: D3690022

fbshipit-source-id: 55c0121f7a99cc2186e68d0bb3ebfe5355af98fc
This commit is contained in:
Pieter De Baets 2016-08-17 10:34:15 -07:00 committed by Facebook Github Bot 7
parent e4ac66b0ee
commit 66bea7d1e5
3 changed files with 112 additions and 89 deletions

View File

@ -30,8 +30,8 @@ typedef NS_ENUM(NSInteger, RCTNetworkTaskStatus) {
@property (nonatomic, readonly) NSNumber *requestID;
@property (nonatomic, readonly, weak) id requestToken;
@property (nonatomic, readonly) NSURLResponse *response;
@property (nonatomic, readonly) RCTURLRequestCompletionBlock completionBlock;
@property (nonatomic, copy) RCTURLRequestCompletionBlock completionBlock;
@property (nonatomic, copy) RCTURLRequestProgressBlock downloadProgressBlock;
@property (nonatomic, copy) RCTURLRequestIncrementalDataBlock incrementalDataBlock;
@property (nonatomic, copy) RCTURLRequestResponseBlock responseBlock;
@ -41,7 +41,7 @@ typedef NS_ENUM(NSInteger, RCTNetworkTaskStatus) {
- (instancetype)initWithRequest:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
completionBlock:(RCTURLRequestCompletionBlock)completionBlock NS_DESIGNATED_INITIALIZER;
callbackQueue:(dispatch_queue_t)callbackQueue NS_DESIGNATED_INITIALIZER;
- (void)start;
- (void)cancel;

View File

@ -10,21 +10,24 @@
#import "RCTNetworkTask.h"
#import "RCTLog.h"
#import "RCTUtils.h"
@implementation RCTNetworkTask
{
NSMutableData *_data;
id<RCTURLRequestHandler> _handler;
dispatch_queue_t _callbackQueue;
RCTNetworkTask *_selfReference;
}
- (instancetype)initWithRequest:(NSURLRequest *)request
handler:(id<RCTURLRequestHandler>)handler
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
callbackQueue:(dispatch_queue_t)callbackQueue
{
RCTAssertParam(request);
RCTAssertParam(handler);
RCTAssertParam(completionBlock);
RCTAssertParam(callbackQueue);
static NSUInteger requestID = 0;
@ -32,7 +35,7 @@
_requestID = @(requestID++);
_request = request;
_handler = handler;
_completionBlock = completionBlock;
_callbackQueue = callbackQueue;
_status = RCTNetworkTaskPending;
}
return self;
@ -53,8 +56,8 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)start
{
if (_requestToken == nil) {
if ([self validateRequestToken:[_handler sendRequest:_request
withDelegate:self]]) {
id token = [_handler sendRequest:_request withDelegate:self];
if ([self validateRequestToken:token]) {
_selfReference = self;
_status = RCTNetworkTaskInProgress;
}
@ -88,11 +91,14 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
}
valid = NO;
}
if (!valid) {
_status = RCTNetworkTaskFinished;
if (_completionBlock) {
_completionBlock(_response, nil, [NSError errorWithDomain:RCTErrorDomain code:0
userInfo:@{NSLocalizedDescriptionKey: @"Invalid request token."}]);
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
dispatch_async(_callbackQueue, ^{
completionBlock(self->_response, nil, RCTErrorWithMessage(@"Invalid request token."));
});
}
[self invalidate];
}
@ -101,48 +107,76 @@ RCT_NOT_IMPLEMENTED(- (instancetype)init)
- (void)URLRequest:(id)requestToken didSendDataWithProgress:(int64_t)bytesSent
{
if ([self validateRequestToken:requestToken]) {
if (_uploadProgressBlock) {
_uploadProgressBlock(bytesSent, _request.HTTPBody.length);
}
if (![self validateRequestToken:requestToken]) {
return;
}
if (_uploadProgressBlock) {
RCTURLRequestProgressBlock uploadProgressBlock = _uploadProgressBlock;
int64_t length = _request.HTTPBody.length;
dispatch_async(_callbackQueue, ^{
uploadProgressBlock(bytesSent, length);
});
}
}
- (void)URLRequest:(id)requestToken didReceiveResponse:(NSURLResponse *)response
{
if ([self validateRequestToken:requestToken]) {
_response = response;
if (_responseBlock) {
_responseBlock(response);
}
if (![self validateRequestToken:requestToken]) {
return;
}
_response = response;
if (_responseBlock) {
RCTURLRequestResponseBlock responseBlock = _responseBlock;
dispatch_async(_callbackQueue, ^{
responseBlock(response);
});
}
}
- (void)URLRequest:(id)requestToken didReceiveData:(NSData *)data
{
if ([self validateRequestToken:requestToken]) {
if (!_data) {
_data = [NSMutableData new];
}
[_data appendData:data];
if (_incrementalDataBlock) {
_incrementalDataBlock(data, _data.length, _response.expectedContentLength);
}
if (_downloadProgressBlock && _response.expectedContentLength > 0) {
_downloadProgressBlock(_data.length, _response.expectedContentLength);
}
if (![self validateRequestToken:requestToken]) {
return;
}
if (!_data) {
_data = [NSMutableData new];
}
[_data appendData:data];
int64_t length = _data.length;
int64_t total = _response.expectedContentLength;
if (_incrementalDataBlock) {
RCTURLRequestIncrementalDataBlock incrementalDataBlock = _incrementalDataBlock;
dispatch_async(_callbackQueue, ^{
incrementalDataBlock(data, length, total);
});
}
if (_downloadProgressBlock && total > 0) {
RCTURLRequestProgressBlock downloadProgressBlock = _downloadProgressBlock;
dispatch_async(_callbackQueue, ^{
downloadProgressBlock(length, total);
});
}
}
- (void)URLRequest:(id)requestToken didCompleteWithError:(NSError *)error
{
if ([self validateRequestToken:requestToken]) {
_status = RCTNetworkTaskFinished;
if (_completionBlock) {
_completionBlock(_response, _data, error);
}
[self invalidate];
if (![self validateRequestToken:requestToken]) {
return;
}
_status = RCTNetworkTaskFinished;
if (_completionBlock) {
RCTURLRequestCompletionBlock completionBlock = _completionBlock;
dispatch_async(_callbackQueue, ^{
completionBlock(self->_response, self->_data, error);
});
}
[self invalidate];
}
@end

View File

@ -382,30 +382,25 @@ RCT_EXPORT_MODULE()
RCTAssertThread(_methodQueue, @"sendRequest: must be called on method queue");
__block RCTNetworkTask *task;
RCTURLRequestProgressBlock uploadProgressBlock = ^(int64_t progress, int64_t total) {
dispatch_async(self->_methodQueue, ^{
NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)];
[self sendEventWithName:@"didSendNetworkData" body:responseJSON];
});
NSArray *responseJSON = @[task.requestID, @((double)progress), @((double)total)];
[self sendEventWithName:@"didSendNetworkData" body:responseJSON];
};
RCTURLRequestResponseBlock responseBlock = ^(NSURLResponse *response) {
dispatch_async(self->_methodQueue, ^{
NSDictionary<NSString *, NSString *> *headers;
NSInteger status;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
headers = httpResponse.allHeaderFields ?: @{};
status = httpResponse.statusCode;
} else {
headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{};
status = 200;
}
id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL];
[self sendEventWithName:@"didReceiveNetworkResponse" body:responseJSON];
});
NSDictionary<NSString *, NSString *> *headers;
NSInteger status;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { // Might be a local file request
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)response;
headers = httpResponse.allHeaderFields ?: @{};
status = httpResponse.statusCode;
} else {
headers = response.MIMEType ? @{@"Content-Type": response.MIMEType} : @{};
status = 200;
}
id responseURL = response.URL ? response.URL.absoluteString : [NSNull null];
NSArray<id> *responseJSON = @[task.requestID, @(status), headers, responseURL];
[self sendEventWithName:@"didReceiveNetworkResponse" body:responseJSON];
};
// XHR does not allow you to peek at xhr.response before the response is
@ -417,44 +412,38 @@ RCT_EXPORT_MODULE()
if (incrementalUpdates) {
if ([responseType isEqualToString:@"text"]) {
incrementalDataBlock = ^(NSData *data, int64_t progress, int64_t total) {
dispatch_async(self->_methodQueue, ^{
NSString *responseString = [RCTNetworking decodeTextData:data fromResponse:task.response];
if (!responseString) {
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
return;
}
NSArray<id> *responseJSON = @[task.requestID, responseString, @(progress), @(total)];
[self sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON];
});
NSString *responseString = [RCTNetworking decodeTextData:data fromResponse:task.response];
if (!responseString) {
RCTLogWarn(@"Received data was not a string, or was not a recognised encoding.");
return;
}
NSArray<id> *responseJSON = @[task.requestID, responseString, @(progress), @(total)];
[self sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON];
};
} else {
downloadProgressBlock = ^(int64_t progress, int64_t total) {
dispatch_async(self->_methodQueue, ^{
NSArray<id> *responseJSON = @[task.requestID, @(progress), @(total)];
[self sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON];
});
NSArray<id> *responseJSON = @[task.requestID, @(progress), @(total)];
[self sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON];
};
}
}
RCTURLRequestCompletionBlock completionBlock =
^(NSURLResponse *response, NSData *data, NSError *error) {
dispatch_async(self->_methodQueue, ^{
// Unless we were sending incremental (text) chunks to JS, all along, now
// is the time to send the request body to JS.
if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) {
[self sendData:data
responseType:responseType
forTask:task];
}
NSArray *responseJSON = @[task.requestID,
RCTNullIfNil(error.localizedDescription),
error.code == kCFURLErrorTimedOut ? @YES : @NO
];
// Unless we were sending incremental (text) chunks to JS, all along, now
// is the time to send the request body to JS.
if (!(incrementalUpdates && [responseType isEqualToString:@"text"])) {
[self sendData:data
responseType:responseType
forTask:task];
}
NSArray *responseJSON = @[task.requestID,
RCTNullIfNil(error.localizedDescription),
error.code == kCFURLErrorTimedOut ? @YES : @NO
];
[self sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON];
[self->_tasksByRequestID removeObjectForKey:task.requestID];
});
[self sendEventWithName:@"didCompleteNetworkResponse" body:responseJSON];
[self->_tasksByRequestID removeObjectForKey:task.requestID];
};
task = [self networkTaskWithRequest:request completionBlock:completionBlock];
@ -476,8 +465,7 @@ RCT_EXPORT_MODULE()
#pragma mark - Public API
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request
completionBlock:(RCTURLRequestCompletionBlock)completionBlock
- (RCTNetworkTask *)networkTaskWithRequest:(NSURLRequest *)request completionBlock:(RCTURLRequestCompletionBlock)completionBlock
{
id<RCTURLRequestHandler> handler = [self handlerForRequest:request];
if (!handler) {
@ -485,9 +473,11 @@ RCT_EXPORT_MODULE()
return nil;
}
return [[RCTNetworkTask alloc] initWithRequest:request
handler:handler
completionBlock:completionBlock];
RCTNetworkTask *task = [[RCTNetworkTask alloc] initWithRequest:request
handler:handler
callbackQueue:_methodQueue];
task.completionBlock = completionBlock;
return task;
}
#pragma mark - JS API
@ -499,7 +489,6 @@ RCT_EXPORT_METHOD(sendRequest:(NSDictionary *)query
// no way to invoke it, if, for example the request is cancelled while
// loading a large file to build the request body
[self buildRequest:query completionBlock:^(NSURLRequest *request) {
NSString *responseType = [RCTConvert NSString:query[@"responseType"]];
BOOL incrementalUpdates = [RCTConvert BOOL:query[@"incrementalUpdates"]];
[self sendRequest:request