react-native/Libraries/Network/RCTFileRequestHandler.m
Nick Lockwood 235749ba19 Fix missing images
Summary:
Under rare and as-yet-to-be determined circumstances, images can sometimes fail to load/download and get "stuck", without producing an error.

Because the `RCTNetworkTask` for these images is stuck in the "in progress" state, they clog up the RCTImageLoader task queue, which has a limit of 4 concurrent in-progress tasks.

This was previously masked by the fact that we automatically cancelled image requests when the RCTImageView moved offscreen, but we no longer do that.

This diff adds logic to detect some types of stuck task and remove them, thereby unblocking the queue. I've also restored the functionality of cancelling downloads for offscreen images (but not unloading the image itself) so that stuck images will be cancelled when you move to another screen, instead of using up space in the queue forever.

Reviewed By: fkgozali

Differential Revision: D3398105

fbshipit-source-id: 75ee40d06a872ae8e1cb57f02f9cad57c459143c
2016-06-09 09:58:31 -07:00

93 lines
2.7 KiB
Objective-C

/**
* 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 "RCTFileRequestHandler.h"
#import <MobileCoreServices/MobileCoreServices.h>
#import "RCTUtils.h"
@implementation RCTFileRequestHandler
{
NSOperationQueue *_fileQueue;
}
RCT_EXPORT_MODULE()
- (void)invalidate
{
[_fileQueue cancelAllOperations];
_fileQueue = nil;
}
- (BOOL)canHandleRequest:(NSURLRequest *)request
{
return
[request.URL.scheme caseInsensitiveCompare:@"file"] == NSOrderedSame
&& !RCTIsXCAssetURL(request.URL);
}
- (NSOperation *)sendRequest:(NSURLRequest *)request
withDelegate:(id<RCTURLRequestDelegate>)delegate
{
// Lazy setup
if (!_fileQueue) {
_fileQueue = [NSOperationQueue new];
_fileQueue.maxConcurrentOperationCount = 4;
}
__weak __block NSBlockOperation *weakOp;
__block NSBlockOperation *op = [NSBlockOperation blockOperationWithBlock:^{
// Get content length
NSError *error = nil;
NSFileManager *fileManager = [NSFileManager new];
NSDictionary<NSString *, id> *fileAttributes = [fileManager attributesOfItemAtPath:request.URL.path error:&error];
if (!fileAttributes) {
[delegate URLRequest:weakOp didCompleteWithError:error];
return;
}
// Get mime type
NSString *fileExtension = [request.URL pathExtension];
NSString *UTI = (__bridge_transfer NSString *)UTTypeCreatePreferredIdentifierForTag(
kUTTagClassFilenameExtension, (__bridge CFStringRef)fileExtension, NULL);
NSString *contentType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(
(__bridge CFStringRef)UTI, kUTTagClassMIMEType);
// Send response
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:contentType
expectedContentLength:[fileAttributes[NSFileSize] ?: @-1 integerValue]
textEncodingName:nil];
[delegate URLRequest:weakOp didReceiveResponse:response];
// Load data
NSData *data = [NSData dataWithContentsOfURL:request.URL
options:NSDataReadingMappedIfSafe
error:&error];
if (data) {
[delegate URLRequest:weakOp didReceiveData:data];
}
[delegate URLRequest:weakOp didCompleteWithError:error];
}];
weakOp = op;
[_fileQueue addOperation:op];
return op;
}
- (void)cancelRequest:(NSOperation *)op
{
[op cancel];
}
@end