2019-10-16 20:03:47 +03:00
/*
2018-09-12 01:27:47 +03:00
* Copyright (c) Facebook, Inc. and its affiliates.
2015-03-23 23:28:42 +03:00
*
2018-02-17 05:24:55 +03:00
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
2015-03-23 23:28:42 +03:00
*/
2015-03-10 03:08:01 +03:00
2016-11-23 18:47:52 +03:00
#import <objc/runtime.h>
2019-08-15 21:27:29 +03:00
#import <atomic>
2019-12-17 09:42:41 +03:00
#import <mach/mach_time.h>
2015-03-10 03:08:01 +03:00
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
In order to retrieve the image dimensions, the image may first need to be loaded or downloaded, after which it will be cached. This means that in principle you could use this method to preload images, however it is not optimized for that purpose, and may in future be implemented in a way that does not fully load/download the image data.
A fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 05:50:26 +03:00
#import <ImageIO/ImageIO.h>
2015-03-10 03:08:01 +03:00
2019-08-15 21:27:29 +03:00
#import <FBReactNativeSpec/FBReactNativeSpec.h>
2016-11-23 18:47:52 +03:00
#import <React/RCTConvert.h>
#import <React/RCTDefines.h>
2019-08-15 21:27:29 +03:00
#import <React/RCTImageCache.h>
2016-11-23 18:47:52 +03:00
#import <React/RCTImageLoader.h>
2019-11-16 11:10:25 +03:00
#import <React/RCTImageLoaderWithAttributionProtocol.h>
2019-08-15 21:27:29 +03:00
#import <React/RCTImageUtils.h>
2016-11-23 18:47:52 +03:00
#import <React/RCTLog.h>
#import <React/RCTNetworking.h>
#import <React/RCTUtils.h>
2016-07-18 17:12:19 +03:00
2019-10-22 02:57:38 +03:00
#import "RCTImagePlugins.h"
2015-03-10 03:08:01 +03:00
2019-11-16 11:10:25 +03:00
using namespace facebook::react;
2019-12-09 21:56:57 +03:00
static BOOL imagePerfInstrumentationEnabled = NO;
BOOL RCTImageLoadingPerfInstrumentationEnabled(void)
{
return imagePerfInstrumentationEnabled;
}
void RCTEnableImageLoadingPerfInstrumentation(BOOL enabled)
{
imagePerfInstrumentationEnabled = enabled;
}
2019-01-25 13:42:15 +03:00
static NSInteger RCTImageBytesForImage(UIImage *image)
{
NSInteger singleImageBytes = image.size.width * image.size.height * image.scale * image.scale * 4;
2019-07-23 17:04:34 +03:00
return image.images ? image.images.count * singleImageBytes : singleImageBytes;
2019-01-25 13:42:15 +03:00
}
2019-12-17 09:42:41 +03:00
static uint64_t monotonicTimeGetCurrentNanoseconds(void)
{
static struct mach_timebase_info tb_info = {0};
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
__unused int ret = mach_timebase_info(&tb_info);
assert(0 == ret);
});
return (mach_absolute_time() * tb_info.numer) / tb_info.denom;
}
2019-11-16 11:10:25 +03:00
@interface RCTImageLoader() <NativeImageLoaderIOSSpec, RCTImageLoaderWithAttributionProtocol>
2019-08-15 21:27:29 +03:00
@end
2015-09-04 14:35:44 +03:00
@implementation UIImage (React)
2019-01-25 13:42:15 +03:00
- (NSInteger)reactDecodedImageBytes
{
NSNumber *imageBytes = objc_getAssociatedObject(self, _cmd);
if (!imageBytes) {
imageBytes = @(RCTImageBytesForImage(self));
}
return [imageBytes integerValue];
}
- (void)setReactDecodedImageBytes:(NSInteger)bytes
{
objc_setAssociatedObject(self, @selector(reactDecodedImageBytes), @(bytes), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
2015-09-04 14:35:44 +03:00
@end
2015-03-10 03:08:01 +03:00
@implementation RCTImageLoader
2015-10-19 19:04:54 +03:00
{
2019-03-13 07:03:46 +03:00
NSArray<id<RCTImageURLLoader>> * (^_loadersProvider)(void);
NSArray<id<RCTImageDataDecoder>> * (^_decodersProvider)(void);
2019-03-01 09:45:07 +03:00
NSArray<id<RCTImageURLLoader>> *_loaders;
NSArray<id<RCTImageDataDecoder>> *_decoders;
NSOperationQueue *_imageDecodeQueue;
dispatch_queue_t _URLRequestQueue;
id<RCTImageCache> _imageCache;
NSMutableArray *_pendingTasks;
NSInteger _activeTasks;
NSMutableArray *_pendingDecodes;
NSInteger _scheduledDecodes;
NSUInteger _activeBytes;
2017-12-09 01:49:24 +03:00
__weak id<RCTImageRedirectProtocol> _redirectDelegate;
2015-10-19 19:04:54 +03:00
}
2015-03-10 03:08:01 +03:00
2015-07-27 18:48:31 +03:00
@synthesize bridge = _bridge;
2019-08-14 23:35:12 +03:00
@synthesize maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks;
@synthesize maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks;
@synthesize maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes;
2020-07-08 02:22:59 +03:00
@synthesize turboModuleRegistry = _turboModuleRegistry;
2015-07-27 18:48:31 +03:00
RCT_EXPORT_MODULE()
2017-12-09 01:49:24 +03:00
- (instancetype)init
{
return [self initWithRedirectDelegate:nil];
}
2018-04-04 02:13:58 +03:00
+ (BOOL)requiresMainQueueSetup
{
2019-03-01 09:45:07 +03:00
return NO;
2018-04-04 02:13:58 +03:00
}
2017-12-09 01:49:24 +03:00
- (instancetype)initWithRedirectDelegate:(id<RCTImageRedirectProtocol>)redirectDelegate
{
2019-03-01 09:45:07 +03:00
if (self = [super init]) {
_redirectDelegate = redirectDelegate;
}
return self;
2017-12-09 01:49:24 +03:00
}
2019-03-13 07:03:46 +03:00
- (instancetype)initWithRedirectDelegate:(id<RCTImageRedirectProtocol>)redirectDelegate
loadersProvider:(NSArray<id<RCTImageURLLoader>> * (^)(void))getLoaders
decodersProvider:(NSArray<id<RCTImageDataDecoder>> * (^)(void))getHandlers
{
if (self = [self initWithRedirectDelegate:redirectDelegate]) {
_loadersProvider = getLoaders;
_decodersProvider = getHandlers;
}
return self;
}
2015-11-25 14:09:00 +03:00
- (void)setUp
2015-07-14 14:06:17 +03:00
{
2019-03-01 09:45:07 +03:00
// Set defaults
_maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
_maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
_maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
_URLRequestQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLRequestQueue", DISPATCH_QUEUE_SERIAL);
2016-05-25 16:50:16 +03:00
}
2016-08-02 08:00:56 +03:00
- (float)handlerPriority
2016-05-25 16:50:16 +03:00
{
2019-03-01 09:45:07 +03:00
return 2;
2015-10-19 19:04:54 +03:00
}
2020-06-06 06:52:02 +03:00
#pragma mark - RCTImageLoaderProtocol 1/3
2015-10-19 19:04:54 +03:00
2016-08-02 08:00:56 +03:00
- (id<RCTImageCache>)imageCache
2016-07-27 15:58:51 +03:00
{
2019-03-01 09:45:07 +03:00
if (!_imageCache) {
//set up with default cache
_imageCache = [RCTImageCache new];
}
return _imageCache;
2016-08-02 08:00:56 +03:00
}
- (void)setImageCache:(id<RCTImageCache>)cache
{
2019-03-01 09:45:07 +03:00
if (_imageCache) {
2019-07-23 13:19:41 +03:00
RCTLogWarn(@"RCTImageCache was already set and has now been overridden.");
2019-03-01 09:45:07 +03:00
}
_imageCache = cache;
2016-07-27 15:58:51 +03:00
}
2015-10-19 19:04:54 +03:00
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
{
2019-03-01 09:45:07 +03:00
if (!_maxConcurrentLoadingTasks) {
[self setUp];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (!_loaders) {
// Get loaders, sorted in reverse priority order (highest priority first)
2019-03-13 07:03:46 +03:00
if (_loadersProvider) {
_loaders = _loadersProvider();
} else {
2019-12-17 03:37:53 +03:00
RCTAssert(_bridge, @"Trying to find RCTImageURLLoaders and bridge not set.");
2019-03-13 07:03:46 +03:00
_loaders = [_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)];
}
_loaders = [_loaders sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
2019-03-01 09:45:07 +03:00
float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
if (priorityA > priorityB) {
return NSOrderedAscending;
} else if (priorityA < priorityB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
id<RCTImageURLLoader> previousLoader = nil;
2017-06-13 18:02:13 +03:00
for (id<RCTImageURLLoader> loader in _loaders) {
2019-03-01 09:45:07 +03:00
float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0;
if (previousLoader && priority < previousPriority) {
return previousLoader;
}
if ([loader canLoadImageURL:URL]) {
if (previousLoader) {
if (priority == previousPriority) {
RCTLogError(@"The RCTImageURLLoaders %@ and %@ both reported that"
" they can load the URL %@, and have equal priority"
" (%g). This could result in non-deterministic behavior.",
loader, previousLoader, URL, priority);
}
} else {
previousLoader = loader;
previousPriority = priority;
2017-06-13 18:02:13 +03:00
}
2019-03-01 09:45:07 +03:00
}
}
return previousLoader;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Normal code path
for (id<RCTImageURLLoader> loader in _loaders) {
if ([loader canLoadImageURL:URL]) {
return loader;
2015-10-19 19:04:54 +03:00
}
2019-03-01 09:45:07 +03:00
}
return nil;
2015-10-19 19:04:54 +03:00
}
2020-06-06 06:52:02 +03:00
# pragma mark - Private Image Decoding & Resizing
2015-10-19 19:04:54 +03:00
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
{
2019-03-01 09:45:07 +03:00
if (!_maxConcurrentLoadingTasks) {
[self setUp];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (!_decoders) {
// Get decoders, sorted in reverse priority order (highest priority first)
2019-03-13 07:03:46 +03:00
if (_decodersProvider) {
_decoders = _decodersProvider();
} else {
2019-12-17 03:37:53 +03:00
RCTAssert(_bridge, @"Trying to find RCTImageDataDecoders and bridge not set.");
2019-03-13 07:03:46 +03:00
_decoders = [_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)];
}
2019-03-01 09:45:07 +03:00
_decoders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
if (priorityA > priorityB) {
return NSOrderedAscending;
} else if (priorityA < priorityB) {
return NSOrderedDescending;
} else {
return NSOrderedSame;
}
}];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (RCT_DEBUG) {
// Check for handler conflicts
float previousPriority = 0;
id<RCTImageDataDecoder> previousDecoder = nil;
2017-06-13 18:02:13 +03:00
for (id<RCTImageDataDecoder> decoder in _decoders) {
2019-03-01 09:45:07 +03:00
float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0;
if (previousDecoder && priority < previousPriority) {
return previousDecoder;
}
if ([decoder canDecodeImageData:data]) {
if (previousDecoder) {
if (priority == previousPriority) {
RCTLogError(@"The RCTImageDataDecoders %@ and %@ both reported that"
" they can decode the data <NSData %p; %tu bytes>, and"
" have equal priority (%g). This could result in"
" non-deterministic behavior.",
decoder, previousDecoder, data, data.length, priority);
}
} else {
previousDecoder = decoder;
previousPriority = priority;
2017-06-13 18:02:13 +03:00
}
2019-03-01 09:45:07 +03:00
}
2015-10-19 19:04:54 +03:00
}
2019-03-01 09:45:07 +03:00
return previousDecoder;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Normal code path
for (id<RCTImageDataDecoder> decoder in _decoders) {
if ([decoder canDecodeImageData:data]) {
return decoder;
}
}
return nil;
2015-10-19 19:04:54 +03:00
}
2016-01-20 22:03:22 +03:00
static UIImage *RCTResizeImageIfNeeded(UIImage *image,
CGSize size,
CGFloat scale,
RCTResizeMode resizeMode)
{
2019-03-01 09:45:07 +03:00
if (CGSizeEqualToSize(size, CGSizeZero) ||
CGSizeEqualToSize(image.size, CGSizeZero) ||
CGSizeEqualToSize(image.size, size)) {
2016-01-20 22:03:22 +03:00
return image;
2019-03-01 09:45:07 +03:00
}
CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode);
CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize);
image = RCTTransformImage(image, size, scale, transform);
return image;
2016-01-20 22:03:22 +03:00
}
2020-06-06 06:52:02 +03:00
#pragma mark - RCTImageLoaderProtocol 2/3
- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
callback:(RCTImageLoaderCompletionBlock)callback
{
return [self loadImageWithURLRequest:imageURLRequest
priority:RCTImageLoaderPriorityImmediate
callback:callback];
}
- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
priority:(RCTImageLoaderPriority)priority
callback:(RCTImageLoaderCompletionBlock)callback {
return [self loadImageWithURLRequest:imageURLRequest
size:CGSizeZero
scale:1
clipped:YES
resizeMode:RCTResizeModeStretch
priority:priority
progressBlock:nil
partialLoadBlock:nil
completionBlock:callback];
}
- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
size:(CGSize)size
scale:(CGFloat)scale
clipped:(BOOL)clipped
resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
2015-10-19 19:04:54 +03:00
{
2020-06-06 06:52:02 +03:00
return [self loadImageWithURLRequest:imageURLRequest
size:size
scale:scale
clipped:clipped
resizeMode:resizeMode
priority:RCTImageLoaderPriorityImmediate
progressBlock:progressBlock
partialLoadBlock:partialLoadBlock
completionBlock:completionBlock];
2015-07-27 18:48:31 +03:00
}
2019-11-16 11:10:25 +03:00
- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
size:(CGSize)size
scale:(CGFloat)scale
clipped:(BOOL)clipped
resizeMode:(RCTResizeMode)resizeMode
2020-06-06 06:52:02 +03:00
priority:(RCTImageLoaderPriority)priority
2019-11-16 11:10:25 +03:00
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
{
2019-12-17 09:42:41 +03:00
RCTImageURLLoaderRequest *request = [self loadImageWithURLRequest:imageURLRequest
size:size
scale:scale
clipped:clipped
resizeMode:resizeMode
2020-06-06 06:52:02 +03:00
priority:priority
2019-12-17 09:42:41 +03:00
attribution:{}
progressBlock:progressBlock
partialLoadBlock:partialLoadBlock
completionBlock:completionBlock];
return ^{
[request cancel];
};
2019-11-16 11:10:25 +03:00
}
2020-06-06 06:52:02 +03:00
#pragma mark - Private Downloader Methods
2016-02-16 23:41:20 +03:00
- (void)dequeueTasks
{
2019-03-01 09:45:07 +03:00
dispatch_async(_URLRequestQueue, ^{
// Remove completed tasks
NSMutableArray *tasksToRemove = nil;
for (RCTNetworkTask *task in self->_pendingTasks.reverseObjectEnumerator) {
switch (task.status) {
case RCTNetworkTaskFinished:
if (!tasksToRemove) {
tasksToRemove = [NSMutableArray new];
}
[tasksToRemove addObject:task];
self->_activeTasks--;
break;
case RCTNetworkTaskPending:
break;
case RCTNetworkTaskInProgress:
// Check task isn't "stuck"
if (task.requestToken == nil) {
RCTLogWarn(@"Task orphaned for request %@", task.request);
if (!tasksToRemove) {
tasksToRemove = [NSMutableArray new];
2016-12-05 21:01:28 +03:00
}
2019-03-01 09:45:07 +03:00
[tasksToRemove addObject:task];
self->_activeTasks--;
[task cancel];
}
break;
}
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (tasksToRemove) {
[self->_pendingTasks removeObjectsInArray:tasksToRemove];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Start queued decode
NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count;
while (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes &&
activeDecodes <= self->_maxConcurrentDecodingTasks)) {
dispatch_block_t decodeBlock = self->_pendingDecodes.firstObject;
if (decodeBlock) {
[self->_pendingDecodes removeObjectAtIndex:0];
decodeBlock();
} else {
break;
}
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Start queued tasks
for (RCTNetworkTask *task in self->_pendingTasks) {
if (MAX(self->_activeTasks, self->_scheduledDecodes) >= self->_maxConcurrentLoadingTasks) {
break;
}
if (task.status == RCTNetworkTaskPending) {
[task start];
self->_activeTasks++;
}
}
});
2016-02-16 23:41:20 +03:00
}
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
In order to retrieve the image dimensions, the image may first need to be loaded or downloaded, after which it will be cached. This means that in principle you could use this method to preload images, however it is not optimized for that purpose, and may in future be implemented in a way that does not fully load/download the image data.
A fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 05:50:26 +03:00
/**
* This returns either an image, or raw image data, depending on the loading
* path taken. This is useful if you want to skip decoding, e.g. when preloading
* the image, or retrieving metadata.
*/
2019-12-17 09:42:41 +03:00
- (RCTImageURLLoaderRequest *)_loadImageOrDataWithURLRequest:(NSURLRequest *)request
size:(CGSize)size
scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode
2020-06-06 06:52:02 +03:00
priority:(RCTImageLoaderPriority)priority
2019-12-17 09:42:41 +03:00
attribution:(const ImageURLLoaderAttribution &)attribution
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response))completionBlock
2015-03-10 03:08:01 +03:00
{
2019-03-01 09:45:07 +03:00
{
NSMutableURLRequest *mutableRequest = [request mutableCopy];
[NSURLProtocol setProperty:@"RCTImageLoader"
forKey:@"trackingName"
inRequest:mutableRequest];
2019-03-21 00:37:46 +03:00
// Add missing png extension
if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
mutableRequest.URL = [request.URL URLByAppendingPathExtension:@"png"];
}
2019-03-01 09:45:07 +03:00
if (_redirectDelegate != nil) {
mutableRequest.URL = [_redirectDelegate redirectAssetsURL:mutableRequest.URL];
2015-11-17 18:18:55 +03:00
}
2019-03-01 09:45:07 +03:00
request = mutableRequest;
}
2019-03-13 07:03:46 +03:00
2019-11-16 11:10:25 +03:00
// Create a copy here so the value is retained when accessed in the blocks below.
ImageURLLoaderAttribution attributionCopy(attribution);
2019-03-01 09:45:07 +03:00
// Find suitable image URL loader
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:request.URL];
BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ?
[loadHandler requiresScheduling] : YES;
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ?
[loadHandler shouldCacheLoadedImages] : YES;
2019-03-13 07:03:46 +03:00
2019-08-15 21:27:29 +03:00
auto cancelled = std::make_shared<std::atomic<int>>(0);
2019-03-01 09:45:07 +03:00
__block dispatch_block_t cancelLoad = nil;
__block NSLock *cancelLoadLock = [NSLock new];
2019-12-17 09:42:41 +03:00
NSString *requestId = [NSString stringWithFormat:@"%@-%llu",[[NSUUID UUID] UUIDString], monotonicTimeGetCurrentNanoseconds()];
2019-03-01 09:45:07 +03:00
void (^completionHandler)(NSError *, id, NSURLResponse *) = ^(NSError *error, id imageOrData, NSURLResponse *response) {
[cancelLoadLock lock];
cancelLoad = nil;
[cancelLoadLock unlock];
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// If we've received an image, we should try to set it synchronously,
// if it's data, do decoding on a background thread.
if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) {
// Most loaders do not return on the main thread, so caller is probably not
// expecting it, and may do expensive post-processing in the callback
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2019-08-15 21:27:29 +03:00
if (!std::atomic_load(cancelled.get())) {
2019-03-01 09:45:07 +03:00
completionBlock(error, imageOrData, cacheResult, response);
2017-08-01 12:48:28 +03:00
}
2019-03-01 09:45:07 +03:00
});
2019-08-15 21:27:29 +03:00
} else if (!std::atomic_load(cancelled.get())) {
2019-03-01 09:45:07 +03:00
completionBlock(error, imageOrData, cacheResult, response);
}
};
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// If the loader doesn't require scheduling we call it directly on
// the main queue.
if (loadHandler && !requiresScheduling) {
2019-11-16 11:10:25 +03:00
if ([loadHandler conformsToProtocol:@protocol(RCTImageURLLoaderWithAttribution)]) {
return [(id<RCTImageURLLoaderWithAttribution>)loadHandler loadImageForURL:request.URL
size:size
scale:scale
resizeMode:resizeMode
2019-12-17 09:42:41 +03:00
requestId:requestId
2020-06-06 06:52:02 +03:00
priority:priority
2019-11-16 11:10:25 +03:00
attribution:attributionCopy
progressHandler:progressHandler
partialLoadHandler:partialLoadHandler
completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil);
}];
}
2019-12-17 09:42:41 +03:00
RCTImageLoaderCancellationBlock cb = [loadHandler loadImageForURL:request.URL
size:size
scale:scale
resizeMode:resizeMode
progressHandler:progressHandler
partialLoadHandler:partialLoadHandler
completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil);
}];
2019-12-17 09:42:41 +03:00
return [[RCTImageURLLoaderRequest alloc] initWithRequestId:nil imageURL:request.URL cancellationBlock:cb];
2019-03-01 09:45:07 +03:00
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// All access to URL cache must be serialized
if (!_URLRequestQueue) {
[self setUp];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
__weak RCTImageLoader *weakSelf = self;
dispatch_async(_URLRequestQueue, ^{
__typeof(self) strongSelf = weakSelf;
2019-08-15 21:27:29 +03:00
if (atomic_load(cancelled.get()) || !strongSelf) {
return;
2019-03-01 09:45:07 +03:00
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (loadHandler) {
2019-11-16 11:10:25 +03:00
dispatch_block_t cancelLoadLocal;
if ([loadHandler conformsToProtocol:@protocol(RCTImageURLLoaderWithAttribution)]) {
2019-12-17 09:42:41 +03:00
RCTImageURLLoaderRequest *loaderRequest = [(id<RCTImageURLLoaderWithAttribution>)loadHandler loadImageForURL:request.URL
size:size
scale:scale
resizeMode:resizeMode
requestId:requestId
2020-06-06 06:52:02 +03:00
priority:priority
2019-12-17 09:42:41 +03:00
attribution:attributionCopy
progressHandler:progressHandler
partialLoadHandler:partialLoadHandler
completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil);
}];
cancelLoadLocal = loaderRequest.cancellationBlock;
2019-11-16 11:10:25 +03:00
} else {
cancelLoadLocal = [loadHandler loadImageForURL:request.URL
size:size
scale:scale
resizeMode:resizeMode
progressHandler:progressHandler
partialLoadHandler:partialLoadHandler
completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil);
}];
}
2019-03-01 09:45:07 +03:00
[cancelLoadLock lock];
cancelLoad = cancelLoadLocal;
[cancelLoadLock unlock];
} else {
UIImage *image;
if (cacheResult) {
image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString
size:size
scale:scale
resizeMode:resizeMode];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (image) {
completionHandler(nil, image, nil);
} else {
// Use networking module to load image
dispatch_block_t cancelLoadLocal = [strongSelf _loadURLRequest:request
progressBlock:progressHandler
completionBlock:completionHandler];
2019-03-01 05:24:26 +03:00
[cancelLoadLock lock];
2019-03-01 09:45:07 +03:00
cancelLoad = cancelLoadLocal;
2019-03-01 05:24:26 +03:00
[cancelLoadLock unlock];
2019-03-01 09:45:07 +03:00
}
}
});
2020-05-15 23:40:31 +03:00
2019-12-17 09:42:41 +03:00
return [[RCTImageURLLoaderRequest alloc] initWithRequestId:requestId imageURL:request.URL cancellationBlock:^{
2019-08-15 21:27:29 +03:00
BOOL alreadyCancelled = atomic_fetch_or(cancelled.get(), 1);
2019-03-01 09:45:07 +03:00
if (alreadyCancelled) {
return;
}
[cancelLoadLock lock];
dispatch_block_t cancelLoadLocal = cancelLoad;
cancelLoad = nil;
[cancelLoadLock unlock];
if (cancelLoadLocal) {
cancelLoadLocal();
}
2019-12-17 09:42:41 +03:00
}];
2016-07-11 23:23:40 +03:00
}
2015-11-05 20:06:53 +03:00
2016-07-11 23:23:40 +03:00
- (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
2019-02-06 13:02:19 +03:00
completionBlock:(void (^)(NSError *error, id imageOrData, NSURLResponse *response))completionHandler
2016-07-11 23:23:40 +03:00
{
2019-03-01 09:45:07 +03:00
// Check if networking module is available
2020-01-14 09:52:07 +03:00
if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]
2020-07-08 02:22:59 +03:00
&& ![_turboModuleRegistry moduleForName:"RCTNetworking"]) {
2019-03-01 09:45:07 +03:00
RCTLogError(@"No suitable image URL loader found for %@. You may need to "
" import the RCTNetwork library in order to load images.",
request.URL.absoluteString);
return NULL;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
RCTNetworking *networking = [_bridge networking];
2020-01-14 09:52:07 +03:00
if (!networking) {
2020-07-08 02:22:59 +03:00
networking = [_turboModuleRegistry moduleForName:"RCTNetworking"];
2020-01-14 09:52:07 +03:00
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Check if networking module can load image
if (RCT_DEBUG && ![networking canHandleRequest:request]) {
RCTLogError(@"No suitable image URL loader found for %@", request.URL.absoluteString);
return NULL;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Use networking module to load image
RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) {
// Check for system errors
if (error) {
completionHandler(error, nil, response);
return;
} else if (!response) {
completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, response);
return;
} else if (!data) {
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, response);
return;
2016-10-11 22:34:58 +03:00
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Check for http errors
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (statusCode != 200) {
NSString *errorMessage = [NSString stringWithFormat:@"Failed to load %@", response.URL];
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage};
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
code:statusCode
userInfo:userInfo], nil, response);
return;
}
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Call handler
completionHandler(nil, data, response);
};
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Download image
__weak __typeof(self) weakSelf = self;
__block RCTNetworkTask *task =
[networking networkTaskWithRequest:request
completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
__typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
return;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (error || !response || !data) {
NSError *someError = nil;
if (error) {
someError = error;
} else if (!response) {
someError = RCTErrorWithMessage(@"Response metadata error");
} else {
someError = RCTErrorWithMessage(@"Unknown image download error");
}
completionHandler(someError, nil, response);
[strongSelf dequeueTasks];
return;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
dispatch_async(strongSelf->_URLRequestQueue, ^{
// Process image data
processResponse(response, data, nil);
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Prepare for next task
[strongSelf dequeueTasks];
});
}];
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
task.downloadProgressBlock = ^(int64_t progress, int64_t total) {
if (progressHandler) {
progressHandler(progress, total);
}
};
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (task) {
if (!_pendingTasks) {
_pendingTasks = [NSMutableArray new];
}
[_pendingTasks addObject:task];
[self dequeueTasks];
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
return ^{
__typeof(self) strongSelf = weakSelf;
if (!strongSelf || !task) {
return;
}
dispatch_async(strongSelf->_URLRequestQueue, ^{
[task cancel];
task = nil;
});
[strongSelf dequeueTasks];
};
2015-09-02 18:25:10 +03:00
}
2019-12-17 09:42:41 +03:00
#pragma mark - RCTImageLoaderWithAttributionProtocol
2019-12-17 09:42:41 +03:00
- (RCTImageURLLoaderRequest *)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
size:(CGSize)size
scale:(CGFloat)scale
clipped:(BOOL)clipped
resizeMode:(RCTResizeMode)resizeMode
2020-06-06 06:52:02 +03:00
priority:(RCTImageLoaderPriority)priority
2019-12-17 09:42:41 +03:00
attribution:(const ImageURLLoaderAttribution &)attribution
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
In order to retrieve the image dimensions, the image may first need to be loaded or downloaded, after which it will be cached. This means that in principle you could use this method to preload images, however it is not optimized for that purpose, and may in future be implemented in a way that does not fully load/download the image data.
A fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 05:50:26 +03:00
{
2019-08-15 21:27:29 +03:00
auto cancelled = std::make_shared<std::atomic<int>>(0);
2019-03-01 09:45:07 +03:00
__block dispatch_block_t cancelLoad = nil;
__block NSLock *cancelLoadLock = [NSLock new];
dispatch_block_t cancellationBlock = ^{
2019-08-15 21:27:29 +03:00
BOOL alreadyCancelled = atomic_fetch_or(cancelled.get(), 1);
2019-03-01 09:45:07 +03:00
if (alreadyCancelled) {
return;
}
[cancelLoadLock lock];
dispatch_block_t cancelLoadLocal = cancelLoad;
cancelLoad = nil;
[cancelLoadLock unlock];
if (cancelLoadLocal) {
cancelLoadLocal();
}
};
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
__weak RCTImageLoader *weakSelf = self;
void (^completionHandler)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) {
__typeof(self) strongSelf = weakSelf;
2019-08-15 21:27:29 +03:00
if (std::atomic_load(cancelled.get()) || !strongSelf) {
2019-03-01 09:45:07 +03:00
return;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
[cancelLoadLock lock];
cancelLoad = nil;
[cancelLoadLock unlock];
completionBlock(error, imageOrData);
return;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {
if (cacheResult && image) {
// Store decoded image in cache
[[strongSelf imageCache] addImageToCache:image
URL:imageURLRequest.URL.absoluteString
size:size
scale:scale
resizeMode:resizeMode
response:response];
}
[cancelLoadLock lock];
cancelLoad = nil;
[cancelLoadLock unlock];
completionBlock(error_, image);
Change RCTImageLoader's Cache System to default NSURLRequest's cache system
Summary:
Before this PR, ```RCTImageLodaer```'s Cache was too big(200MB on disk) and It doesn't work with HTTP Cache-Control header. So to provide dynamic image, the users must have to add random value on url( ex. adding current date) to avoid cache.
So I change that cache system to default ```NSURLRequest```'s cache system, which is well-working with HTTP specs. As the discussion on this issue #7571 , making custom cache policy processor is not ready yet and useless, over-tech things, I think.
Even we have no plan about image cache system(or would change plan later), before having a nice plan, I think we should let user use image module with common HTTP Specs.
So I remove custom ```NSURLCache```, and make logic like below,
1. try fetch image,
2. on response, get ```Date``` on response's header and make ```cacheKey``` with ```Date```.
> (why? because if ```NSURLRequest```'s response was cached, the response's ```Date``` header dosen't change.)
3. find decoded imag
Closes https://github.com/facebook/react-native/pull/8235
Reviewed By: bnham
Differential Revision: D3469086
Pulled By: javache
fbshipit-source-id: 35a5552cda6e6c367481020bbf3c28eb4a9d0207
2016-07-21 17:45:55 +03:00
};
2019-03-01 09:45:07 +03:00
dispatch_block_t cancelLoadLocal = [strongSelf decodeImageData:imageOrData
size:size
scale:scale
clipped:clipped
resizeMode:resizeMode
completionBlock:decodeCompletionHandler];
[cancelLoadLock lock];
cancelLoad = cancelLoadLocal;
[cancelLoadLock unlock];
};
2019-03-13 07:03:46 +03:00
2019-12-17 09:42:41 +03:00
RCTImageURLLoaderRequest *loaderRequest = [self _loadImageOrDataWithURLRequest:imageURLRequest
size:size
scale:scale
resizeMode:resizeMode
2020-06-06 06:52:02 +03:00
priority:priority
2019-12-17 09:42:41 +03:00
attribution:attribution
progressBlock:progressBlock
partialLoadBlock:partialLoadBlock
completionBlock:completionHandler];
cancelLoad = loaderRequest.cancellationBlock;
2019-12-17 09:42:41 +03:00
return [[RCTImageURLLoaderRequest alloc] initWithRequestId:loaderRequest.requestId imageURL:imageURLRequest.URL cancellationBlock:cancellationBlock];
}
- (void)trackURLImageContentDidSetForRequest:(RCTImageURLLoaderRequest *)loaderRequest
{
if (!loaderRequest) {
return;
}
2020-04-27 11:37:05 +03:00
// This delegate method is Fabric-only
2019-12-17 09:42:41 +03:00
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
if ([loadHandler respondsToSelector:@selector(trackURLImageContentDidSetForRequest:)]) {
[(id<RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageContentDidSetForRequest:loaderRequest];
}
}
- (void)trackURLImageVisibilityForRequest:(RCTImageURLLoaderRequest *)loaderRequest imageView:(UIView *)imageView
{
if (!loaderRequest || !imageView) {
return;
}
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
if ([loadHandler respondsToSelector:@selector(trackURLImageVisibilityForRequest:imageView:)]) {
[(id<RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageVisibilityForRequest:loaderRequest imageView:imageView];
}
}
2020-05-19 02:51:33 +03:00
- (void)trackURLImageRequestDidDestroy:(RCTImageURLLoaderRequest *)loaderRequest
2020-05-15 23:40:31 +03:00
{
if (!loaderRequest) {
return;
}
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
2020-05-19 02:51:33 +03:00
if ([loadHandler respondsToSelector:@selector(trackURLImageRequestDidDestroy:)]) {
[(id<RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageRequestDidDestroy:loaderRequest];
2020-05-15 23:40:31 +03:00
}
}
2019-12-17 09:42:41 +03:00
- (void)trackURLImageDidDestroy:(RCTImageURLLoaderRequest *)loaderRequest
{
if (!loaderRequest) {
return;
}
2020-05-15 23:40:31 +03:00
2019-12-17 09:42:41 +03:00
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
if ([loadHandler respondsToSelector:@selector(trackURLImageDidDestroy:)]) {
[(id<RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageDidDestroy:loaderRequest];
}
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
In order to retrieve the image dimensions, the image may first need to be loaded or downloaded, after which it will be cached. This means that in principle you could use this method to preload images, however it is not optimized for that purpose, and may in future be implemented in a way that does not fully load/download the image data.
A fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 05:50:26 +03:00
}
2020-06-06 06:52:02 +03:00
#pragma mark - RCTImageLoaderProtocol 3/3
2015-09-02 18:25:10 +03:00
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
size:(CGSize)size
scale:(CGFloat)scale
2016-06-01 20:32:20 +03:00
clipped:(BOOL)clipped
2016-01-20 22:03:22 +03:00
resizeMode:(RCTResizeMode)resizeMode
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
2015-09-02 18:25:10 +03:00
{
2019-03-01 09:45:07 +03:00
if (data.length == 0) {
completionBlock(RCTErrorWithMessage(@"No image data"), nil);
return ^{};
}
2019-03-13 07:03:46 +03:00
2019-08-15 21:27:29 +03:00
auto cancelled = std::make_shared<std::atomic<int>>(0);
2019-03-01 09:45:07 +03:00
void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) {
if (RCTIsMainQueue()) {
// Most loaders do not return on the main thread, so caller is probably not
// expecting it, and may do expensive post-processing in the callback
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2019-08-15 21:27:29 +03:00
if (!std::atomic_load(cancelled.get())) {
2019-03-01 09:45:07 +03:00
completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
2017-06-13 18:02:13 +03:00
}
2019-03-01 09:45:07 +03:00
});
2019-08-15 21:27:29 +03:00
} else if (!std::atomic_load(cancelled.get())) {
2019-03-01 09:45:07 +03:00
completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
}
};
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
if (imageDecoder) {
return [imageDecoder decodeImageData:data
size:size
scale:scale
resizeMode:resizeMode
completionHandler:completionHandler] ?: ^{};
} else {
dispatch_block_t decodeBlock = ^{
// Calculate the size, in bytes, that the decompressed image will require
NSInteger decodedImageBytes = (size.width * scale) * (size.height * scale) * 4;
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Mark these bytes as in-use
self->_activeBytes += decodedImageBytes;
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Do actual decompression on a concurrent background queue
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
2019-08-15 21:27:29 +03:00
if (!std::atomic_load(cancelled.get())) {
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// Decompress the image data (this may be CPU and memory intensive)
UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
2019-03-13 07:03:46 +03:00
2016-01-20 22:03:22 +03:00
#if RCT_DEV
2019-03-01 09:45:07 +03:00
CGSize imagePixelSize = RCTSizeInPixels(image.size, image.scale);
CGSize screenPixelSize = RCTSizeInPixels(RCTScreenSize(), RCTScreenScale());
if (imagePixelSize.width * imagePixelSize.height >
screenPixelSize.width * screenPixelSize.height) {
RCTLogInfo(@"[PERF ASSETS] Loading image at size %@, which is larger "
"than the screen size %@", NSStringFromCGSize(imagePixelSize),
NSStringFromCGSize(screenPixelSize));
}
2016-01-20 22:03:22 +03:00
#endif
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (image) {
completionHandler(nil, image);
} else {
NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
NSError *finalError = RCTErrorWithMessage(errorMessage);
completionHandler(finalError, nil);
}
Change RCTImageLoader's Cache System to default NSURLRequest's cache system
Summary:
Before this PR, ```RCTImageLodaer```'s Cache was too big(200MB on disk) and It doesn't work with HTTP Cache-Control header. So to provide dynamic image, the users must have to add random value on url( ex. adding current date) to avoid cache.
So I change that cache system to default ```NSURLRequest```'s cache system, which is well-working with HTTP specs. As the discussion on this issue #7571 , making custom cache policy processor is not ready yet and useless, over-tech things, I think.
Even we have no plan about image cache system(or would change plan later), before having a nice plan, I think we should let user use image module with common HTTP Specs.
So I remove custom ```NSURLCache```, and make logic like below,
1. try fetch image,
2. on response, get ```Date``` on response's header and make ```cacheKey``` with ```Date```.
> (why? because if ```NSURLRequest```'s response was cached, the response's ```Date``` header dosen't change.)
3. find decoded imag
Closes https://github.com/facebook/react-native/pull/8235
Reviewed By: bnham
Differential Revision: D3469086
Pulled By: javache
fbshipit-source-id: 35a5552cda6e6c367481020bbf3c28eb4a9d0207
2016-07-21 17:45:55 +03:00
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
// We're no longer retaining the uncompressed data, so now we'll mark
// the decoding as complete so that the loading task queue can resume.
dispatch_async(self->_URLRequestQueue, ^{
self->_scheduledDecodes--;
self->_activeBytes -= decodedImageBytes;
[self dequeueTasks];
2016-02-16 23:41:20 +03:00
});
2019-03-01 09:45:07 +03:00
});
};
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (!_URLRequestQueue) {
[self setUp];
Change RCTImageLoader's Cache System to default NSURLRequest's cache system
Summary:
Before this PR, ```RCTImageLodaer```'s Cache was too big(200MB on disk) and It doesn't work with HTTP Cache-Control header. So to provide dynamic image, the users must have to add random value on url( ex. adding current date) to avoid cache.
So I change that cache system to default ```NSURLRequest```'s cache system, which is well-working with HTTP specs. As the discussion on this issue #7571 , making custom cache policy processor is not ready yet and useless, over-tech things, I think.
Even we have no plan about image cache system(or would change plan later), before having a nice plan, I think we should let user use image module with common HTTP Specs.
So I remove custom ```NSURLCache```, and make logic like below,
1. try fetch image,
2. on response, get ```Date``` on response's header and make ```cacheKey``` with ```Date```.
> (why? because if ```NSURLRequest```'s response was cached, the response's ```Date``` header dosen't change.)
3. find decoded imag
Closes https://github.com/facebook/react-native/pull/8235
Reviewed By: bnham
Differential Revision: D3469086
Pulled By: javache
fbshipit-source-id: 35a5552cda6e6c367481020bbf3c28eb4a9d0207
2016-07-21 17:45:55 +03:00
}
2019-03-01 09:45:07 +03:00
dispatch_async(_URLRequestQueue, ^{
// The decode operation retains the compressed image data until it's
// complete, so we'll mark it as having started, in order to block
// further image loads from happening until we're done with the data.
self->_scheduledDecodes++;
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
if (!self->_pendingDecodes) {
self->_pendingDecodes = [NSMutableArray new];
}
NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count - 1;
if (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes &&
activeDecodes <= self->_maxConcurrentDecodingTasks)) {
decodeBlock();
} else {
[self->_pendingDecodes addObject:decodeBlock];
}
});
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
return ^{
2019-08-15 21:27:29 +03:00
std::atomic_store(cancelled.get(), 1);
2019-03-01 09:45:07 +03:00
};
}
2015-03-10 03:08:01 +03:00
}
2016-06-01 20:32:20 +03:00
- (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest
Change RCTImageLoader's Cache System to default NSURLRequest's cache system
Summary:
Before this PR, ```RCTImageLodaer```'s Cache was too big(200MB on disk) and It doesn't work with HTTP Cache-Control header. So to provide dynamic image, the users must have to add random value on url( ex. adding current date) to avoid cache.
So I change that cache system to default ```NSURLRequest```'s cache system, which is well-working with HTTP specs. As the discussion on this issue #7571 , making custom cache policy processor is not ready yet and useless, over-tech things, I think.
Even we have no plan about image cache system(or would change plan later), before having a nice plan, I think we should let user use image module with common HTTP Specs.
So I remove custom ```NSURLCache```, and make logic like below,
1. try fetch image,
2. on response, get ```Date``` on response's header and make ```cacheKey``` with ```Date```.
> (why? because if ```NSURLRequest```'s response was cached, the response's ```Date``` header dosen't change.)
3. find decoded imag
Closes https://github.com/facebook/react-native/pull/8235
Reviewed By: bnham
Differential Revision: D3469086
Pulled By: javache
fbshipit-source-id: 35a5552cda6e6c367481020bbf3c28eb4a9d0207
2016-07-21 17:45:55 +03:00
block:(void(^)(NSError *error, CGSize size))callback
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
In order to retrieve the image dimensions, the image may first need to be loaded or downloaded, after which it will be cached. This means that in principle you could use this method to preload images, however it is not optimized for that purpose, and may in future be implemented in a way that does not fully load/download the image data.
A fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 05:50:26 +03:00
{
2019-03-01 09:45:07 +03:00
void (^completion)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) {
CGSize size;
if ([imageOrData isKindOfClass:[NSData class]]) {
NSDictionary *meta = RCTGetImageMetadata(imageOrData);
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
NSInteger imageOrientation = [meta[(id)kCGImagePropertyOrientation] integerValue];
switch (imageOrientation) {
case kCGImagePropertyOrientationLeft:
case kCGImagePropertyOrientationRight:
case kCGImagePropertyOrientationLeftMirrored:
case kCGImagePropertyOrientationRightMirrored:
// swap width and height
size = (CGSize){
2019-08-15 21:27:29 +03:00
[meta[(id)kCGImagePropertyPixelHeight] floatValue],
[meta[(id)kCGImagePropertyPixelWidth] floatValue],
2019-03-01 09:45:07 +03:00
};
break;
case kCGImagePropertyOrientationUp:
case kCGImagePropertyOrientationDown:
case kCGImagePropertyOrientationUpMirrored:
case kCGImagePropertyOrientationDownMirrored:
default:
size = (CGSize){
2019-08-15 21:27:29 +03:00
[meta[(id)kCGImagePropertyPixelWidth] floatValue],
[meta[(id)kCGImagePropertyPixelHeight] floatValue],
2019-03-01 09:45:07 +03:00
};
break;
}
} else {
UIImage *image = imageOrData;
size = (CGSize){
image.size.width * image.scale,
image.size.height * image.scale,
};
}
callback(error, size);
};
2019-03-13 07:03:46 +03:00
2019-12-17 09:42:41 +03:00
RCTImageURLLoaderRequest *loaderRequest = [self _loadImageOrDataWithURLRequest:imageURLRequest
size:CGSizeZero
scale:1
resizeMode:RCTResizeModeStretch
2020-06-06 06:52:02 +03:00
priority: RCTImageLoaderPriorityImmediate
2019-12-17 09:42:41 +03:00
attribution:{}
progressBlock:NULL
partialLoadBlock:NULL
completionBlock:completion];
return loaderRequest.cancellationBlock;
Added getImageSize method
Summary:
public
This diff adds a `getSize()` method to `Image` to retrieve the width and height of an image prior to displaying it. This is useful when working with images from uncontrolled sources, and has been a much-requested feature.
In order to retrieve the image dimensions, the image may first need to be loaded or downloaded, after which it will be cached. This means that in principle you could use this method to preload images, however it is not optimized for that purpose, and may in future be implemented in a way that does not fully load/download the image data.
A fully supported way to preload images will be provided in a future diff.
The API (separate success and failure callbacks) is far from ideal, but until we agree on a unified standard, this was the most conventional way I could think of to implement it. If it returned a promise or something similar, it would be unique among all such APIS in the framework.
Please note that this has been a long time coming, in part due to much bikeshedding about what the API should look like, so while it's not unlikely that the API may change in future, I think having *some* way to do this is better than waiting until we can define the "perfect" way.
Reviewed By: vjeux
Differential Revision: D2797365
fb-gh-sync-id: 11eb1b8547773b1f8be0bc55ddf6dfedebf7fc0a
2016-01-01 05:50:26 +03:00
}
2018-08-21 02:06:18 +03:00
- (NSDictionary *)getImageCacheStatus:(NSArray *)requests
{
NSMutableDictionary *results = [NSMutableDictionary dictionary];
for (id request in requests) {
NSURLRequest *urlRequest = [RCTConvert NSURLRequest:request];
if (urlRequest) {
NSCachedURLResponse *cachedResponse = [NSURLCache.sharedURLCache cachedResponseForRequest:urlRequest];
if (cachedResponse) {
if (cachedResponse.storagePolicy == NSURLCacheStorageAllowedInMemoryOnly) {
2019-02-25 08:55:59 +03:00
results[urlRequest.URL.absoluteString] = @"memory";
} else if (NSURLCache.sharedURLCache.currentMemoryUsage == 0) {
// We can't check whether the file is cached on disk or memory.
// However, if currentMemoryUsage is disabled, it must be read from disk.
results[urlRequest.URL.absoluteString] = @"disk";
2018-08-21 02:06:18 +03:00
} else {
2019-02-25 08:55:59 +03:00
results[urlRequest.URL.absoluteString] = @"disk/memory";
2018-08-21 02:06:18 +03:00
}
}
}
}
return results;
}
2015-09-02 18:25:10 +03:00
#pragma mark - RCTURLRequestHandler
- (BOOL)canHandleRequest:(NSURLRequest *)request
2015-07-14 14:06:17 +03:00
{
2018-09-21 18:48:33 +03:00
NSURL *requestURL = request.URL;
2019-03-13 07:03:46 +03:00
2018-09-21 18:48:33 +03:00
// If the data being loaded is a video, return NO
// Even better may be to implement this on the RCTImageURLLoader that would try to load it,
// but we'd have to run the logic both in RCTPhotoLibraryImageLoader and
// RCTAssetsLibraryRequestHandler. Once we drop iOS7 though, we'd drop
// RCTAssetsLibraryRequestHandler and can move it there.
static NSRegularExpression *videoRegex;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
NSError *error = nil;
videoRegex = [NSRegularExpression regularExpressionWithPattern:@"(?:&|^)ext=MOV(?:&|$)"
options:NSRegularExpressionCaseInsensitive
error:&error];
if (error) {
RCTLogError(@"%@", error);
2017-06-13 18:02:13 +03:00
}
2018-09-21 18:48:33 +03:00
});
2019-03-13 07:03:46 +03:00
2018-09-21 18:48:33 +03:00
NSString *query = requestURL.query;
if (
2019-03-01 09:45:07 +03:00
query != nil &&
[videoRegex firstMatchInString:query
options:0
range:NSMakeRange(0, query.length)]
) {
2018-09-21 18:48:33 +03:00
return NO;
}
2019-03-13 07:03:46 +03:00
2018-09-21 18:48:33 +03:00
for (id<RCTImageURLLoader> loader in _loaders) {
// Don't use RCTImageURLLoader protocol for modules that already conform to
// RCTURLRequestHandler as it's inefficient to decode an image and then
// convert it back into data
if (![loader conformsToProtocol:@protocol(RCTURLRequestHandler)] &&
2019-03-01 09:45:07 +03:00
[loader canLoadImageURL:requestURL]) {
2018-09-21 18:48:33 +03:00
return YES;
2017-06-13 18:02:13 +03:00
}
2018-09-21 18:48:33 +03:00
}
2019-03-13 07:03:46 +03:00
2018-09-21 18:48:33 +03:00
return NO;
2015-07-15 23:17:13 +03:00
}
2015-09-02 18:25:10 +03:00
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
2015-07-15 23:17:13 +03:00
{
2019-03-01 09:45:07 +03:00
__block RCTImageLoaderCancellationBlock requestToken;
requestToken = [self loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) {
if (error) {
[delegate URLRequest:requestToken didCompleteWithError:error];
return;
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
NSString *mimeType = nil;
NSData *imageData = nil;
if (RCTImageHasAlpha(image.CGImage)) {
mimeType = @"image/png";
imageData = UIImagePNGRepresentation(image);
} else {
mimeType = @"image/jpeg";
imageData = UIImageJPEGRepresentation(image, 1.0);
}
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:mimeType
expectedContentLength:imageData.length
textEncodingName:nil];
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
[delegate URLRequest:requestToken didReceiveResponse:response];
[delegate URLRequest:requestToken didReceiveData:imageData];
[delegate URLRequest:requestToken didCompleteWithError:nil];
}];
2019-03-13 07:03:46 +03:00
2019-03-01 09:45:07 +03:00
return requestToken;
2015-09-02 18:25:10 +03:00
}
- (void)cancelRequest:(id)requestToken
{
2019-03-01 09:45:07 +03:00
if (requestToken) {
((RCTImageLoaderCancellationBlock)requestToken)();
}
2015-07-14 14:06:17 +03:00
}
Part 2: Update ObjC++ codegen classes to use ObjCTurboModule::InitParams
Summary:
## Summary
Please check out D21035209.
## Changes
- Codemod all ObjC NativeModule `getTurboModuleWithJsInvoker:nativeInvoker:perfLogger` methods to `getTurboModule:(const ObjCTurboModule::Args)`
## Script
```
var withSpaces = (...args) => args.join('\s*')
var regexString = withSpaces(
'-',
'\(',
'std::shared_ptr',
'<',
'(?<turboModuleClass>(facebook::react::|react::|::|)TurboModule)',
'>',
'\)',
'getTurboModuleWithJsInvoker',
':',
'\(',
'std::shared_ptr',
'<',
'(?<fbNamespace>(facebook::react::|react::|::|))CallInvoker',
'>',
'\)',
'(?<jsInvokerInstance>[A-Za-z0-9]+)',
'nativeInvoker',
':',
'\(',
'std::shared_ptr',
'<',
'(facebook::react::|react::|::|)CallInvoker',
'>',
'\)',
'(?<nativeInvokerInstance>[A-Za-z0-9]+)',
'perfLogger',
':',
'\(',
'id',
'<',
'RCTTurboModulePerformanceLogger',
'>',
'\)',
'(?<perfLoggerInstance>[A-Za-z0-9]+)',
'{',
'return',
'std::make_shared',
'<',
'(?<specName>(facebook::react::|react::|::|)Native[%A-Za-z0-9]+SpecJSI)',
'>',
'\(',
'self',
',',
'\k<jsInvokerInstance>',
',',
'\k<nativeInvokerInstance>',
',',
'\k<perfLoggerInstance>',
'\)',
';',
'}',
)
var replaceString = `- (std::shared_ptr<$<turboModuleClass>>) getTurboModule:(const $<fbNamespace>ObjCTurboModule::InitParams &)params
{
return std::make_shared<$<specName>>(params);
}`
const exec = require('../lib/exec');
const abspath = require('../lib/abspath');
const relpath = require('../lib/relpath');
const readFile = (filename) => require('fs').readFileSync(filename, 'utf8');
const writeFile = (filename, content) => require('fs').writeFileSync(filename, content);
function main() {
const tmFiles = exec('cd ~/fbsource && xbgs -n 10000 -l getTurboModuleWithJsInvoker:').split('\n').filter(Boolean);
tmFiles
.filter((filename) => !filename.includes('microsoft-fork-of-react-native'))
.map(abspath)
.forEach((filename) => {
const source = readFile(filename);
const newSource = source.replace(new RegExp(regexString, 'g'), replaceString);
if (source == newSource) {
console.log(relpath(filename));
}
writeFile(filename, newSource);
});
}
if (!module.parent) {
main();
}
```
## Re-generating diff
```
> hg revert -r .^ --all
> node index.js # run script
```
Changelog: [iOS][Changed] - Make all ObjC NativeModules create TurboModules using ObjCTurboModule::Args
Reviewed By: PeteTheHeat
Differential Revision: D21036265
fbshipit-source-id: 404bcc548d1775ef23d793527606d02fe384a0a2
2020-04-17 03:23:39 +03:00
- (std::shared_ptr<facebook::react::TurboModule>)getTurboModule:(const facebook::react::ObjCTurboModule::InitParams &)params
2019-08-15 21:27:29 +03:00
{
Part 2: Update ObjC++ codegen classes to use ObjCTurboModule::InitParams
Summary:
## Summary
Please check out D21035209.
## Changes
- Codemod all ObjC NativeModule `getTurboModuleWithJsInvoker:nativeInvoker:perfLogger` methods to `getTurboModule:(const ObjCTurboModule::Args)`
## Script
```
var withSpaces = (...args) => args.join('\s*')
var regexString = withSpaces(
'-',
'\(',
'std::shared_ptr',
'<',
'(?<turboModuleClass>(facebook::react::|react::|::|)TurboModule)',
'>',
'\)',
'getTurboModuleWithJsInvoker',
':',
'\(',
'std::shared_ptr',
'<',
'(?<fbNamespace>(facebook::react::|react::|::|))CallInvoker',
'>',
'\)',
'(?<jsInvokerInstance>[A-Za-z0-9]+)',
'nativeInvoker',
':',
'\(',
'std::shared_ptr',
'<',
'(facebook::react::|react::|::|)CallInvoker',
'>',
'\)',
'(?<nativeInvokerInstance>[A-Za-z0-9]+)',
'perfLogger',
':',
'\(',
'id',
'<',
'RCTTurboModulePerformanceLogger',
'>',
'\)',
'(?<perfLoggerInstance>[A-Za-z0-9]+)',
'{',
'return',
'std::make_shared',
'<',
'(?<specName>(facebook::react::|react::|::|)Native[%A-Za-z0-9]+SpecJSI)',
'>',
'\(',
'self',
',',
'\k<jsInvokerInstance>',
',',
'\k<nativeInvokerInstance>',
',',
'\k<perfLoggerInstance>',
'\)',
';',
'}',
)
var replaceString = `- (std::shared_ptr<$<turboModuleClass>>) getTurboModule:(const $<fbNamespace>ObjCTurboModule::InitParams &)params
{
return std::make_shared<$<specName>>(params);
}`
const exec = require('../lib/exec');
const abspath = require('../lib/abspath');
const relpath = require('../lib/relpath');
const readFile = (filename) => require('fs').readFileSync(filename, 'utf8');
const writeFile = (filename, content) => require('fs').writeFileSync(filename, content);
function main() {
const tmFiles = exec('cd ~/fbsource && xbgs -n 10000 -l getTurboModuleWithJsInvoker:').split('\n').filter(Boolean);
tmFiles
.filter((filename) => !filename.includes('microsoft-fork-of-react-native'))
.map(abspath)
.forEach((filename) => {
const source = readFile(filename);
const newSource = source.replace(new RegExp(regexString, 'g'), replaceString);
if (source == newSource) {
console.log(relpath(filename));
}
writeFile(filename, newSource);
});
}
if (!module.parent) {
main();
}
```
## Re-generating diff
```
> hg revert -r .^ --all
> node index.js # run script
```
Changelog: [iOS][Changed] - Make all ObjC NativeModules create TurboModules using ObjCTurboModule::Args
Reviewed By: PeteTheHeat
Differential Revision: D21036265
fbshipit-source-id: 404bcc548d1775ef23d793527606d02fe384a0a2
2020-04-17 03:23:39 +03:00
return std::make_shared<facebook::react::NativeImageLoaderIOSSpecJSI>(params);
2019-08-15 21:27:29 +03:00
}
2019-09-27 03:32:40 +03:00
RCT_EXPORT_METHOD(getSize:(NSString *)uri resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
{
NSURLRequest *request = [RCTConvert NSURLRequest:uri];
[self getImageSizeForURLRequest:request
block:^(NSError *error, CGSize size) {
if (error) {
reject(
2019-10-03 00:02:59 +03:00
@"E_GET_SIZE_FAILURE",
2019-09-27 03:32:40 +03:00
[NSString stringWithFormat: @"Failed to getSize of %@", uri],
error);
} else {
resolve(@[@(size.width), @(size.height)]);
}
}];
}
2019-10-03 00:02:59 +03:00
RCT_EXPORT_METHOD(getSizeWithHeaders:(NSString *)uri
2019-10-16 21:10:56 +03:00
headers:(NSDictionary *)headers
2019-10-03 00:02:59 +03:00
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
NSURLRequest *request = [RCTConvert NSURLRequest:uri];
[self getImageSizeForURLRequest:request
block:^(NSError *error, CGSize size) {
if (error) {
reject(@"E_GET_SIZE_FAILURE", nil, error);
return;
}
resolve(@{@"width":@(size.width),@"height":@(size.height)});
}];
}
2019-10-03 00:02:59 +03:00
RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
NSURLRequest *request = [RCTConvert NSURLRequest:uri];
[self loadImageWithURLRequest:request
2020-06-06 06:52:02 +03:00
priority:RCTImageLoaderPriorityPrefetch
2019-10-03 00:02:59 +03:00
callback:^(NSError *error, UIImage *image) {
if (error) {
reject(@"E_PREFETCH_FAILURE", nil, error);
return;
}
resolve(@YES);
}];
}
2019-10-03 00:02:59 +03:00
RCT_EXPORT_METHOD(queryCache:(NSArray *)uris
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
{
resolve([self getImageCacheStatus:uris]);
}
2015-03-10 03:08:01 +03:00
@end
2015-07-27 18:48:31 +03:00
2019-07-24 20:45:22 +03:00
/**
* DEPRECATED!! DO NOT USE
* Instead use `[_bridge moduleForClass:[RCTImageLoader class]]`
*/
2015-07-27 18:48:31 +03:00
@implementation RCTBridge (RCTImageLoader)
- (RCTImageLoader *)imageLoader
{
2019-08-20 20:17:05 +03:00
RCTLogWarn(@"Calling bridge.imageLoader is deprecated and will not work in newer versions of RN. Please update to the "
2020-07-08 02:22:59 +03:00
"moduleForClass API or turboModuleRegistry API.");
2019-03-01 09:45:07 +03:00
return [self moduleForClass:[RCTImageLoader class]];
2015-07-27 18:48:31 +03:00
}
@end
2019-08-15 21:27:29 +03:00
Class RCTImageLoaderCls(void)
{
return RCTImageLoader.class;
}