mirror of
https://github.com/status-im/react-native.git
synced 2025-02-12 01:16:46 +00:00
Summary: public There was a race condition issue in RCTDownLoadTask whereby the request handler would sometimes call one of the delegate methods before setup was complete, causing an error to be logged because the request token had not been set, and causing te request to fail because the class was not yet set up. This diff fixes that issue by adding an explicit `start` method to RCTDownloadTask, and changing the setup order to allow for the request to call back immediately without this being treated as an error. Reviewed By: tadeuzagallo Differential Revision: D2553628 fb-gh-sync-id: 5ca4e791574a632ccbf2e873e28ac88bffdf851d
179 lines
6.1 KiB
Objective-C
179 lines
6.1 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 "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", @"data", 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
|