diff --git a/Libraries/Network/RCTNetworkTask.h b/Libraries/Network/RCTNetworkTask.h index 189471def..05097260f 100644 --- a/Libraries/Network/RCTNetworkTask.h +++ b/Libraries/Network/RCTNetworkTask.h @@ -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)handler - completionBlock:(RCTURLRequestCompletionBlock)completionBlock NS_DESIGNATED_INITIALIZER; + callbackQueue:(dispatch_queue_t)callbackQueue NS_DESIGNATED_INITIALIZER; - (void)start; - (void)cancel; diff --git a/Libraries/Network/RCTNetworkTask.m b/Libraries/Network/RCTNetworkTask.m index c8abb7ac0..f354ab44a 100644 --- a/Libraries/Network/RCTNetworkTask.m +++ b/Libraries/Network/RCTNetworkTask.m @@ -10,21 +10,24 @@ #import "RCTNetworkTask.h" #import "RCTLog.h" +#import "RCTUtils.h" @implementation RCTNetworkTask { NSMutableData *_data; id _handler; + dispatch_queue_t _callbackQueue; + RCTNetworkTask *_selfReference; } - (instancetype)initWithRequest:(NSURLRequest *)request handler:(id)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 diff --git a/Libraries/Network/RCTNetworking.m b/Libraries/Network/RCTNetworking.m index ea912e0f8..d5823b241 100644 --- a/Libraries/Network/RCTNetworking.m +++ b/Libraries/Network/RCTNetworking.m @@ -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 *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 *responseJSON = @[task.requestID, @(status), headers, responseURL]; - [self sendEventWithName:@"didReceiveNetworkResponse" body:responseJSON]; - }); + NSDictionary *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 *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 *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 *responseJSON = @[task.requestID, responseString, @(progress), @(total)]; + [self sendEventWithName:@"didReceiveNetworkIncrementalData" body:responseJSON]; }; } else { downloadProgressBlock = ^(int64_t progress, int64_t total) { - dispatch_async(self->_methodQueue, ^{ - NSArray *responseJSON = @[task.requestID, @(progress), @(total)]; - [self sendEventWithName:@"didReceiveNetworkDataProgress" body:responseJSON]; - }); + NSArray *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 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