2015-03-23 20:28:42 +00:00
|
|
|
/**
|
|
|
|
* 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.
|
|
|
|
*/
|
2015-03-10 00:08:01 +00:00
|
|
|
|
2016-11-23 15:47:52 +00:00
|
|
|
#import <libkern/OSAtomic.h>
|
|
|
|
#import <objc/runtime.h>
|
2015-03-10 00:08:01 +00: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 02:50:26 +00:00
|
|
|
#import <ImageIO/ImageIO.h>
|
2015-03-10 00:08:01 +00:00
|
|
|
|
2016-11-23 15:47:52 +00:00
|
|
|
#import <React/RCTConvert.h>
|
|
|
|
#import <React/RCTDefines.h>
|
|
|
|
#import <React/RCTImageLoader.h>
|
|
|
|
#import <React/RCTLog.h>
|
|
|
|
#import <React/RCTNetworking.h>
|
|
|
|
#import <React/RCTUtils.h>
|
2016-07-18 14:12:19 +00:00
|
|
|
|
2016-07-28 15:46:51 +00:00
|
|
|
#import "RCTImageCache.h"
|
2015-10-08 18:32:11 +00:00
|
|
|
#import "RCTImageUtils.h"
|
2015-03-10 00:08:01 +00:00
|
|
|
|
2015-09-04 11:35:44 +00:00
|
|
|
@implementation UIImage (React)
|
|
|
|
|
|
|
|
- (CAKeyframeAnimation *)reactKeyframeAnimation
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
return objc_getAssociatedObject(self, _cmd);
|
2015-09-04 11:35:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setReactKeyframeAnimation:(CAKeyframeAnimation *)reactKeyframeAnimation
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
objc_setAssociatedObject(self, @selector(reactKeyframeAnimation), reactKeyframeAnimation, OBJC_ASSOCIATION_COPY_NONATOMIC);
|
2015-09-04 11:35:44 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|
|
|
|
|
2015-03-10 00:08:01 +00:00
|
|
|
@implementation RCTImageLoader
|
2015-10-19 16:04:54 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00: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;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
2015-03-10 00:08:01 +00:00
|
|
|
|
2015-07-27 15:48:31 +00:00
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
|
2015-11-25 11:09:00 +00:00
|
|
|
- (void)setUp
|
2015-07-14 11:06:17 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
// Set defaults
|
|
|
|
_maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
|
|
|
|
_maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
|
|
|
|
_maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB
|
2016-02-16 20:41:20 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
_URLRequestQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLRequestQueue", DISPATCH_QUEUE_SERIAL);
|
2016-05-25 13:50:16 +00:00
|
|
|
}
|
|
|
|
|
2016-08-02 05:00:56 +00:00
|
|
|
- (float)handlerPriority
|
2016-05-25 13:50:16 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
return 1;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
|
|
|
|
2016-08-02 05:00:56 +00:00
|
|
|
- (id<RCTImageCache>)imageCache
|
2016-07-27 12:58:51 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
if (!_imageCache) {
|
|
|
|
//set up with default cache
|
|
|
|
_imageCache = [RCTImageCache new];
|
|
|
|
}
|
|
|
|
return _imageCache;
|
2016-08-02 05:00:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)setImageCache:(id<RCTImageCache>)cache
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
if (_imageCache) {
|
|
|
|
RCTLogWarn(@"RCTImageCache was already set and has now been overriden.");
|
|
|
|
}
|
|
|
|
_imageCache = cache;
|
2016-07-27 12:58:51 +00:00
|
|
|
}
|
|
|
|
|
2015-10-19 16:04:54 +00:00
|
|
|
- (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
if (!_maxConcurrentLoadingTasks) {
|
|
|
|
[self setUp];
|
|
|
|
}
|
2016-03-03 10:20:20 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
if (!_loaders) {
|
|
|
|
// Get loaders, sorted in reverse priority order (highest priority first)
|
|
|
|
RCTAssert(_bridge, @"Bridge not set");
|
|
|
|
_loaders = [[_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)] sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (RCT_DEBUG) {
|
|
|
|
// Check for handler conflicts
|
|
|
|
float previousPriority = 0;
|
|
|
|
id<RCTImageURLLoader> previousLoader = nil;
|
|
|
|
for (id<RCTImageURLLoader> loader in _loaders) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
return previousLoader;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
// Normal code path
|
|
|
|
for (id<RCTImageURLLoader> loader in _loaders) {
|
|
|
|
if ([loader canLoadImageURL:URL]) {
|
|
|
|
return loader;
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
return nil;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
if (!_maxConcurrentLoadingTasks) {
|
|
|
|
[self setUp];
|
|
|
|
}
|
2016-03-03 10:20:20 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
if (!_decoders) {
|
|
|
|
// Get decoders, sorted in reverse priority order (highest priority first)
|
|
|
|
RCTAssert(_bridge, @"Bridge not set");
|
|
|
|
_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;
|
|
|
|
}
|
|
|
|
}];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (RCT_DEBUG) {
|
|
|
|
// Check for handler conflicts
|
|
|
|
float previousPriority = 0;
|
|
|
|
id<RCTImageDataDecoder> previousDecoder = nil;
|
|
|
|
for (id<RCTImageDataDecoder> decoder in _decoders) {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
return previousDecoder;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
// Normal code path
|
|
|
|
for (id<RCTImageDataDecoder> decoder in _decoders) {
|
|
|
|
if ([decoder canDecodeImageData:data]) {
|
|
|
|
return decoder;
|
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
return nil;
|
2015-10-19 16:04:54 +00:00
|
|
|
}
|
|
|
|
|
2016-01-20 19:03:22 +00:00
|
|
|
static UIImage *RCTResizeImageIfNeeded(UIImage *image,
|
|
|
|
CGSize size,
|
|
|
|
CGFloat scale,
|
|
|
|
RCTResizeMode resizeMode)
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
if (CGSizeEqualToSize(size, CGSizeZero) ||
|
|
|
|
CGSizeEqualToSize(image.size, CGSizeZero) ||
|
|
|
|
CGSizeEqualToSize(image.size, size)) {
|
|
|
|
return image;
|
|
|
|
}
|
|
|
|
CAKeyframeAnimation *animation = image.reactKeyframeAnimation;
|
|
|
|
CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode);
|
|
|
|
CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize);
|
|
|
|
image = RCTTransformImage(image, size, scale, transform);
|
|
|
|
image.reactKeyframeAnimation = animation;
|
2016-01-20 19:03:22 +00:00
|
|
|
return image;
|
|
|
|
}
|
|
|
|
|
2016-06-01 17:32:20 +00:00
|
|
|
- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(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 14:45:55 +00:00
|
|
|
callback:(RCTImageLoaderCompletionBlock)callback
|
2015-10-19 16:04:54 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
return [self loadImageWithURLRequest:imageURLRequest
|
|
|
|
size:CGSizeZero
|
|
|
|
scale:1
|
|
|
|
clipped:YES
|
|
|
|
resizeMode:RCTResizeModeStretch
|
|
|
|
progressBlock:nil
|
|
|
|
partialLoadBlock:nil
|
|
|
|
completionBlock:callback];
|
2015-07-27 15:48:31 +00:00
|
|
|
}
|
|
|
|
|
2016-02-16 20:41:20 +00:00
|
|
|
- (void)dequeueTasks
|
|
|
|
{
|
2017-06-13 15:02:13 +00: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];
|
|
|
|
}
|
|
|
|
[tasksToRemove addObject:task];
|
|
|
|
self->_activeTasks--;
|
|
|
|
[task cancel];
|
|
|
|
}
|
|
|
|
break;
|
2016-12-05 18:01:28 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
}
|
2016-02-16 20:41:20 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
if (tasksToRemove) {
|
|
|
|
[self->_pendingTasks removeObjectsInArray:tasksToRemove];
|
|
|
|
}
|
2016-02-16 20:41:20 +00:00
|
|
|
|
2017-06-13 15:02:13 +00: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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 20:41:20 +00: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 02:50:26 +00: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.
|
|
|
|
*/
|
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 14:45:55 +00:00
|
|
|
- (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest *)request
|
|
|
|
size:(CGSize)size
|
|
|
|
scale:(CGFloat)scale
|
|
|
|
resizeMode:(RCTResizeMode)resizeMode
|
|
|
|
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
|
2016-09-21 19:11:19 +00:00
|
|
|
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
|
2016-07-28 15:46:51 +00:00
|
|
|
completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate))completionBlock
|
2015-03-10 00:08:01 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
{
|
|
|
|
NSMutableURLRequest *mutableRequest = [request mutableCopy];
|
|
|
|
[NSURLProtocol setProperty:@"RCTImageLoader"
|
|
|
|
forKey:@"trackingName"
|
|
|
|
inRequest:mutableRequest];
|
|
|
|
|
|
|
|
// Add missing png extension
|
|
|
|
if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
|
|
|
|
mutableRequest.URL = [NSURL fileURLWithPath:[request.URL.path stringByAppendingPathExtension:@"png"]];
|
2015-10-20 12:00:50 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
request = mutableRequest;
|
2015-11-20 13:15:49 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
// Find suitable image URL loader
|
|
|
|
id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:request.URL];
|
|
|
|
BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ?
|
|
|
|
[loadHandler requiresScheduling] : YES;
|
|
|
|
|
|
|
|
__block volatile uint32_t cancelled = 0;
|
|
|
|
__block dispatch_block_t cancelLoad = nil;
|
|
|
|
void (^completionHandler)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate) {
|
|
|
|
cancelLoad = nil;
|
|
|
|
|
|
|
|
BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ?
|
|
|
|
[loadHandler shouldCacheLoadedImages] : YES;
|
|
|
|
|
|
|
|
// 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), ^{
|
|
|
|
if (!cancelled) {
|
|
|
|
completionBlock(error, imageOrData, cacheResult, fetchDate);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else if (!cancelled) {
|
|
|
|
completionBlock(error, imageOrData, cacheResult, fetchDate);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// If the loader doesn't require scheduling we call it directly on
|
|
|
|
// the main queue.
|
|
|
|
if (loadHandler && !requiresScheduling) {
|
|
|
|
return [loadHandler loadImageForURL:request.URL
|
|
|
|
size:size
|
|
|
|
scale:scale
|
|
|
|
resizeMode:resizeMode
|
|
|
|
progressHandler:progressHandler
|
|
|
|
partialLoadHandler:partialLoadHandler
|
|
|
|
completionHandler:^(NSError *error, UIImage *image){
|
|
|
|
completionHandler(error, image, nil);
|
|
|
|
}];
|
2015-11-20 13:15:49 +00:00
|
|
|
}
|
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
// All access to URL cache must be serialized
|
|
|
|
if (!_URLRequestQueue) {
|
|
|
|
[self setUp];
|
2015-11-17 15:18:55 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
|
|
|
|
__weak RCTImageLoader *weakSelf = self;
|
|
|
|
dispatch_async(_URLRequestQueue, ^{
|
|
|
|
__typeof(self) strongSelf = weakSelf;
|
|
|
|
if (cancelled || !strongSelf) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (loadHandler) {
|
|
|
|
cancelLoad = [loadHandler loadImageForURL:request.URL
|
|
|
|
size:size
|
|
|
|
scale:scale
|
|
|
|
resizeMode:resizeMode
|
|
|
|
progressHandler:progressHandler
|
|
|
|
partialLoadHandler:partialLoadHandler
|
|
|
|
completionHandler:^(NSError *error, UIImage *image) {
|
|
|
|
completionHandler(error, image, nil);
|
|
|
|
}];
|
|
|
|
} else {
|
|
|
|
// Use networking module to load image
|
|
|
|
cancelLoad = [strongSelf _loadURLRequest:request
|
|
|
|
progressBlock:progressHandler
|
|
|
|
completionBlock:completionHandler];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
return ^{
|
|
|
|
dispatch_block_t cancelLoadLocal = cancelLoad;
|
|
|
|
cancelLoad = nil;
|
|
|
|
if (cancelLoadLocal && !cancelled) {
|
|
|
|
cancelLoadLocal();
|
|
|
|
}
|
|
|
|
OSAtomicOr32Barrier(1, &cancelled);
|
|
|
|
};
|
2016-07-11 20:23:40 +00:00
|
|
|
}
|
2015-11-05 17:06:53 +00:00
|
|
|
|
2016-07-11 20:23:40 +00:00
|
|
|
- (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
|
|
|
|
progressBlock:(RCTImageLoaderProgressBlock)progressHandler
|
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 14:45:55 +00:00
|
|
|
completionBlock:(void (^)(NSError *error, id imageOrData, NSString *fetchDate))completionHandler
|
2016-07-11 20:23:40 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
// Check if networking module is available
|
|
|
|
if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) {
|
|
|
|
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;
|
2015-11-20 13:15:49 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
RCTNetworking *networking = [_bridge networking];
|
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 14:45:55 +00:00
|
|
|
|
2017-06-13 15:02:13 +00: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;
|
2016-07-11 20:23:40 +00:00
|
|
|
}
|
2015-10-19 16:04:54 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
// Use networking module to load image
|
|
|
|
RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) {
|
|
|
|
// Check for system errors
|
|
|
|
if (error) {
|
|
|
|
completionHandler(error, nil, nil);
|
|
|
|
return;
|
|
|
|
} else if (!response) {
|
|
|
|
completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, nil);
|
|
|
|
return;
|
|
|
|
} else if (!data) {
|
|
|
|
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, nil);
|
|
|
|
return;
|
|
|
|
}
|
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 14:45:55 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
// Check for http errors
|
|
|
|
NSString *responseDate;
|
|
|
|
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, nil);
|
|
|
|
return;
|
|
|
|
}
|
2015-11-20 13:15:49 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
|
|
|
|
}
|
2016-07-11 20:23:40 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
// Call handler
|
|
|
|
completionHandler(nil, data, responseDate);
|
|
|
|
};
|
2016-07-11 20:23:40 +00:00
|
|
|
|
2017-06-13 15:02:13 +00: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;
|
|
|
|
}
|
|
|
|
|
|
|
|
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, nil);
|
|
|
|
[strongSelf dequeueTasks];
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
dispatch_async(strongSelf->_URLRequestQueue, ^{
|
|
|
|
// Process image data
|
|
|
|
processResponse(response, data, nil);
|
|
|
|
|
|
|
|
// Prepare for next task
|
|
|
|
[strongSelf dequeueTasks];
|
|
|
|
});
|
|
|
|
}];
|
|
|
|
|
|
|
|
task.downloadProgressBlock = ^(int64_t progress, int64_t total) {
|
|
|
|
if (progressHandler) {
|
|
|
|
progressHandler(progress, total);
|
|
|
|
}
|
|
|
|
};
|
2016-07-11 20:23:40 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
if (task) {
|
|
|
|
if (!_pendingTasks) {
|
|
|
|
_pendingTasks = [NSMutableArray new];
|
|
|
|
}
|
|
|
|
[_pendingTasks addObject:task];
|
|
|
|
[self dequeueTasks];
|
2016-10-11 19:34:58 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
|
|
|
|
return ^{
|
|
|
|
__typeof(self) strongSelf = weakSelf;
|
|
|
|
if (!strongSelf || !task) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
dispatch_async(strongSelf->_URLRequestQueue, ^{
|
|
|
|
[task cancel];
|
|
|
|
task = nil;
|
|
|
|
});
|
|
|
|
[strongSelf dequeueTasks];
|
|
|
|
};
|
2015-09-02 15:25:10 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 17:32:20 +00:00
|
|
|
- (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
|
|
|
|
size:(CGSize)size
|
|
|
|
scale:(CGFloat)scale
|
|
|
|
clipped:(BOOL)clipped
|
|
|
|
resizeMode:(RCTResizeMode)resizeMode
|
2016-08-19 17:31:42 +00:00
|
|
|
progressBlock:(RCTImageLoaderProgressBlock)progressBlock
|
2016-09-21 19:11:19 +00:00
|
|
|
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
|
2016-06-01 17:32:20 +00:00
|
|
|
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 02:50:26 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
__block volatile uint32_t cancelled = 0;
|
|
|
|
__block dispatch_block_t cancelLoad = nil;
|
|
|
|
dispatch_block_t cancellationBlock = ^{
|
|
|
|
dispatch_block_t cancelLoadLocal = cancelLoad;
|
|
|
|
if (cancelLoadLocal && !cancelled) {
|
|
|
|
cancelLoadLocal();
|
|
|
|
}
|
|
|
|
OSAtomicOr32Barrier(1, &cancelled);
|
|
|
|
};
|
2016-05-23 10:31:51 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
__weak RCTImageLoader *weakSelf = self;
|
|
|
|
void (^completionHandler)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
|
|
|
|
__typeof(self) strongSelf = weakSelf;
|
|
|
|
if (cancelled || !strongSelf) {
|
|
|
|
return;
|
|
|
|
}
|
2016-05-23 10:31:51 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
|
|
|
|
cancelLoad = nil;
|
|
|
|
completionBlock(error, imageOrData);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check decoded image cache
|
|
|
|
if (cacheResult) {
|
|
|
|
UIImage *image = [[strongSelf imageCache] imageForUrl:imageURLRequest.URL.absoluteString
|
|
|
|
size:size
|
|
|
|
scale:scale
|
|
|
|
resizeMode:resizeMode
|
|
|
|
responseDate:fetchDate];
|
|
|
|
if (image) {
|
|
|
|
cancelLoad = nil;
|
|
|
|
completionBlock(nil, image);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
responseDate:fetchDate];
|
|
|
|
}
|
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 14:45:55 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
cancelLoad = nil;
|
|
|
|
completionBlock(error_, image);
|
|
|
|
};
|
|
|
|
|
|
|
|
cancelLoad = [strongSelf decodeImageData:imageOrData
|
2016-08-19 17:31:42 +00:00
|
|
|
size:size
|
|
|
|
scale:scale
|
2017-06-13 15:02:13 +00:00
|
|
|
clipped:clipped
|
2016-08-19 17:31:42 +00:00
|
|
|
resizeMode:resizeMode
|
2017-06-13 15:02:13 +00:00
|
|
|
completionBlock:decodeCompletionHandler];
|
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 14:45:55 +00:00
|
|
|
};
|
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
cancelLoad = [self _loadImageOrDataWithURLRequest:imageURLRequest
|
|
|
|
size:size
|
|
|
|
scale:scale
|
|
|
|
resizeMode:resizeMode
|
|
|
|
progressBlock:progressBlock
|
|
|
|
partialLoadBlock:partialLoadBlock
|
|
|
|
completionBlock:completionHandler];
|
|
|
|
return 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 02:50:26 +00:00
|
|
|
}
|
|
|
|
|
2015-09-02 15:25:10 +00:00
|
|
|
- (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
|
|
|
|
size:(CGSize)size
|
|
|
|
scale:(CGFloat)scale
|
2016-06-01 17:32:20 +00:00
|
|
|
clipped:(BOOL)clipped
|
2016-01-20 19:03:22 +00:00
|
|
|
resizeMode:(RCTResizeMode)resizeMode
|
|
|
|
completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
|
2015-09-02 15:25:10 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
if (data.length == 0) {
|
|
|
|
completionBlock(RCTErrorWithMessage(@"No image data"), nil);
|
|
|
|
return ^{};
|
2016-01-20 19:03:22 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
|
|
|
|
__block volatile uint32_t cancelled = 0;
|
|
|
|
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), ^{
|
|
|
|
if (!cancelled) {
|
|
|
|
completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
} else if (!cancelled) {
|
|
|
|
completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
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;
|
|
|
|
|
|
|
|
// Mark these bytes as in-use
|
|
|
|
self->_activeBytes += decodedImageBytes;
|
|
|
|
|
|
|
|
// Do actual decompression on a concurrent background queue
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
|
|
if (!cancelled) {
|
|
|
|
|
|
|
|
// Decompress the image data (this may be CPU and memory intensive)
|
|
|
|
UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
|
2016-01-20 19:03:22 +00:00
|
|
|
|
|
|
|
#if RCT_DEV
|
2017-06-13 15:02:13 +00: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 19:03:22 +00:00
|
|
|
#endif
|
|
|
|
|
2017-06-13 15:02:13 +00: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);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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];
|
|
|
|
});
|
|
|
|
});
|
|
|
|
};
|
|
|
|
|
|
|
|
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 14:45:55 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00: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++;
|
|
|
|
|
|
|
|
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];
|
|
|
|
}
|
2016-02-16 20:41:20 +00:00
|
|
|
});
|
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
return ^{
|
|
|
|
OSAtomicOr32Barrier(1, &cancelled);
|
|
|
|
};
|
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 14:45:55 +00:00
|
|
|
}
|
2015-03-10 00:08:01 +00:00
|
|
|
}
|
|
|
|
|
2016-06-01 17:32:20 +00: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 14:45:55 +00: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 02:50:26 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
void (^completion)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
|
|
|
|
CGSize size;
|
|
|
|
if ([imageOrData isKindOfClass:[NSData class]]) {
|
|
|
|
NSDictionary *meta = RCTGetImageMetadata(imageOrData);
|
|
|
|
size = (CGSize){
|
|
|
|
[meta[(id)kCGImagePropertyPixelWidth] doubleValue],
|
|
|
|
[meta[(id)kCGImagePropertyPixelHeight] doubleValue],
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
UIImage *image = imageOrData;
|
|
|
|
size = (CGSize){
|
|
|
|
image.size.width * image.scale,
|
|
|
|
image.size.height * image.scale,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
callback(error, size);
|
|
|
|
};
|
|
|
|
|
|
|
|
return [self _loadImageOrDataWithURLRequest:imageURLRequest
|
|
|
|
size:CGSizeZero
|
|
|
|
scale:1
|
|
|
|
resizeMode:RCTResizeModeStretch
|
|
|
|
progressBlock:NULL
|
|
|
|
partialLoadBlock:NULL
|
|
|
|
completionBlock:completion];
|
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 02:50:26 +00:00
|
|
|
}
|
|
|
|
|
2015-09-02 15:25:10 +00:00
|
|
|
#pragma mark - RCTURLRequestHandler
|
|
|
|
|
|
|
|
- (BOOL)canHandleRequest:(NSURLRequest *)request
|
2015-07-14 11:06:17 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
NSURL *requestURL = request.URL;
|
|
|
|
|
|
|
|
// 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 = nil;
|
|
|
|
if (!videoRegex) {
|
|
|
|
NSError *error = nil;
|
|
|
|
videoRegex = [NSRegularExpression regularExpressionWithPattern:@"(?:&|^)ext=MOV(?:&|$)"
|
|
|
|
options:NSRegularExpressionCaseInsensitive
|
|
|
|
error:&error];
|
|
|
|
if (error) {
|
|
|
|
RCTLogError(@"%@", error);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
NSString *query = requestURL.query;
|
|
|
|
if (query != nil && [videoRegex firstMatchInString:query
|
|
|
|
options:0
|
|
|
|
range:NSMakeRange(0, query.length)]) {
|
|
|
|
return NO;
|
|
|
|
}
|
|
|
|
|
|
|
|
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)] &&
|
|
|
|
[loader canLoadImageURL:requestURL]) {
|
|
|
|
return YES;
|
|
|
|
}
|
2016-01-20 19:03:22 +00:00
|
|
|
}
|
2017-06-13 15:02:13 +00:00
|
|
|
return NO;
|
2015-07-15 20:17:13 +00:00
|
|
|
}
|
|
|
|
|
2015-09-02 15:25:10 +00:00
|
|
|
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
|
2015-07-15 20:17:13 +00:00
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
__block RCTImageLoaderCancellationBlock requestToken;
|
|
|
|
requestToken = [self loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) {
|
|
|
|
if (error) {
|
|
|
|
[delegate URLRequest:requestToken didCompleteWithError:error];
|
|
|
|
return;
|
|
|
|
}
|
2015-09-02 15:25:10 +00:00
|
|
|
|
2017-06-13 15:02:13 +00: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);
|
|
|
|
}
|
2015-09-02 15:25:10 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
|
|
|
|
MIMEType:mimeType
|
|
|
|
expectedContentLength:imageData.length
|
|
|
|
textEncodingName:nil];
|
2015-09-02 15:25:10 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
[delegate URLRequest:requestToken didReceiveResponse:response];
|
|
|
|
[delegate URLRequest:requestToken didReceiveData:imageData];
|
|
|
|
[delegate URLRequest:requestToken didCompleteWithError:nil];
|
|
|
|
}];
|
2015-09-02 15:25:10 +00:00
|
|
|
|
2017-06-13 15:02:13 +00:00
|
|
|
return requestToken;
|
2015-09-02 15:25:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
- (void)cancelRequest:(id)requestToken
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
if (requestToken) {
|
|
|
|
((RCTImageLoaderCancellationBlock)requestToken)();
|
|
|
|
}
|
2015-07-14 11:06:17 +00:00
|
|
|
}
|
|
|
|
|
2015-03-10 00:08:01 +00:00
|
|
|
@end
|
2015-07-27 15:48:31 +00:00
|
|
|
|
|
|
|
@implementation RCTBridge (RCTImageLoader)
|
|
|
|
|
|
|
|
- (RCTImageLoader *)imageLoader
|
|
|
|
{
|
2017-06-13 15:02:13 +00:00
|
|
|
return [self moduleForClass:[RCTImageLoader class]];
|
2015-07-27 15:48:31 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
@end
|