react-native/Libraries/Image/RCTImageStoreManager.m

238 lines
7.0 KiB
Mathematica
Raw Normal View History

/**
* Copyright (c) 2015-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import "RCTImageStoreManager.h"
#import <stdatomic.h>
#import <ImageIO/ImageIO.h>
#import <MobileCoreServices/UTType.h>
#import <React/RCTAssert.h>
#import <React/RCTLog.h>
#import <React/RCTUtils.h>
#import "RCTImageUtils.h"
static NSString *const RCTImageStoreURLScheme = @"rct-image-store";
@implementation RCTImageStoreManager
{
NSMutableDictionary<NSString *, NSData *> *_store;
NSUInteger _id;
}
@synthesize methodQueue = _methodQueue;
RCT_EXPORT_MODULE()
- (void)removeImageForTag:(NSString *)imageTag withBlock:(void (^)())block
{
dispatch_async(_methodQueue, ^{
[self removeImageForTag:imageTag];
if (block) {
block();
}
});
}
- (NSString *)_storeImageData:(NSData *)imageData
{
RCTAssertThread(_methodQueue, @"Must be called on RCTImageStoreManager thread");
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
if (!_store) {
_store = [NSMutableDictionary new];
_id = 0;
}
NSString *imageTag = [NSString stringWithFormat:@"%@://%tu", RCTImageStoreURLScheme, _id++];
_store[imageTag] = imageData;
return imageTag;
}
- (void)storeImageData:(NSData *)imageData withBlock:(void (^)(NSString *imageTag))block
{
RCTAssertParam(block);
dispatch_async(_methodQueue, ^{
block([self _storeImageData:imageData]);
});
}
- (void)getImageDataForTag:(NSString *)imageTag withBlock:(void (^)(NSData *imageData))block
{
RCTAssertParam(block);
dispatch_async(_methodQueue, ^{
block(self->_store[imageTag]);
});
}
- (void)storeImage:(UIImage *)image withBlock:(void (^)(NSString *imageTag))block
{
RCTAssertParam(block);
dispatch_async(_methodQueue, ^{
NSString *imageTag = [self _storeImageData:RCTGetImageData(image, 0.75)];
dispatch_async(dispatch_get_main_queue(), ^{
block(imageTag);
});
});
}
RCT_EXPORT_METHOD(removeImageForTag:(NSString *)imageTag)
{
[_store removeObjectForKey:imageTag];
}
RCT_EXPORT_METHOD(hasImageForTag:(NSString *)imageTag
callback:(RCTResponseSenderBlock)callback)
{
callback(@[@(_store[imageTag] != nil)]);
}
// TODO (#5906496): Name could be more explicit - something like getBase64EncodedDataForTag:?
RCT_EXPORT_METHOD(getBase64ForTag:(NSString *)imageTag
successCallback:(RCTResponseSenderBlock)successCallback
errorCallback:(RCTResponseErrorBlock)errorCallback)
{
NSData *imageData = _store[imageTag];
if (!imageData) {
errorCallback(RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]));
return;
}
// Dispatching to a background thread to perform base64 encoding
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
successCallback(@[[imageData base64EncodedStringWithOptions:0]]);
});
}
RCT_EXPORT_METHOD(addImageFromBase64:(NSString *)base64String
successCallback:(RCTResponseSenderBlock)successCallback
errorCallback:(RCTResponseErrorBlock)errorCallback)
{
// Dispatching to a background thread to perform base64 decoding
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
NSData *imageData = [[NSData alloc] initWithBase64EncodedString:base64String options:0];
if (imageData) {
dispatch_async(self->_methodQueue, ^{
successCallback(@[[self _storeImageData:imageData]]);
});
} else {
errorCallback(RCTErrorWithMessage(@"Failed to add image from base64String"));
}
});
}
#pragma mark - RCTURLRequestHandler
2015-09-02 15:25:10 +00:00
- (BOOL)canHandleRequest:(NSURLRequest *)request
2015-09-02 15:25:10 +00:00
{
return [request.URL.scheme caseInsensitiveCompare:RCTImageStoreURLScheme] == NSOrderedSame;
2015-09-02 15:25:10 +00:00
}
- (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
2015-09-02 15:25:10 +00:00
{
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
void (^cancellationBlock)(void) = ^{
atomic_store(&cancelled, YES);
};
// Dispatch async to give caller time to cancel the request
dispatch_async(_methodQueue, ^{
if (atomic_load(&cancelled)) {
return;
2015-09-02 15:25:10 +00:00
}
NSString *imageTag = request.URL.absoluteString;
NSData *imageData = self->_store[imageTag];
if (!imageData) {
NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Invalid imageTag: %@", imageTag]);
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
return;
}
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)imageData, NULL);
if (!sourceRef) {
NSError *error = RCTErrorWithMessage([NSString stringWithFormat:@"Unable to decode data for imageTag: %@", imageTag]);
[delegate URLRequest:cancellationBlock didCompleteWithError:error];
return;
}
CFStringRef UTI = CGImageSourceGetType(sourceRef);
CFRelease(sourceRef);
NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass(UTI, kUTTagClassMIMEType);
NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
MIMEType:MIMEType
expectedContentLength:imageData.length
textEncodingName:nil];
[delegate URLRequest:cancellationBlock didReceiveResponse:response];
[delegate URLRequest:cancellationBlock didReceiveData:imageData];
[delegate URLRequest:cancellationBlock didCompleteWithError:nil];
});
return cancellationBlock;
}
- (void)cancelRequest:(id)requestToken
{
if (requestToken) {
((void (^)(void))requestToken)();
}
}
@end
@implementation RCTImageStoreManager (Deprecated)
- (NSString *)storeImage:(UIImage *)image
{
RCTAssertMainQueue();
RCTLogWarn(@"RCTImageStoreManager.storeImage() is deprecated and has poor performance. Use an alternative method instead.");
__block NSString *imageTag;
dispatch_sync(_methodQueue, ^{
imageTag = [self _storeImageData:RCTGetImageData(image, 0.75)];
});
return imageTag;
}
- (UIImage *)imageForTag:(NSString *)imageTag
{
RCTAssertMainQueue();
RCTLogWarn(@"RCTImageStoreManager.imageForTag() is deprecated and has poor performance. Use an alternative method instead.");
__block NSData *imageData;
dispatch_sync(_methodQueue, ^{
imageData = self->_store[imageTag];
});
return [UIImage imageWithData:imageData];
}
- (void)getImageForTag:(NSString *)imageTag withBlock:(void (^)(UIImage *image))block
{
RCTAssertParam(block);
dispatch_async(_methodQueue, ^{
NSData *imageData = self->_store[imageTag];
dispatch_async(dispatch_get_main_queue(), ^{
// imageWithData: is not thread-safe, so we can't do this on methodQueue
block([UIImage imageWithData:imageData]);
});
});
2015-09-02 15:25:10 +00:00
}
@end
@implementation RCTBridge (RCTImageStoreManager)
- (RCTImageStoreManager *)imageStoreManager
{
Refactored module access to allow for lazy loading Summary: public The `bridge.modules` dictionary provides access to all native modules, but this API requires that every module is initialized in advance so that any module can be accessed. This diff introduces a better API that will allow modules to be initialized lazily as they are needed, and deprecates `bridge.modules` (modules that use it will still work, but should be rewritten to use `bridge.moduleClasses` or `-[bridge moduleForName/Class:` instead. The rules are now as follows: * Any module that overrides `init` or `setBridge:` will be initialized on the main thread when the bridge is created * Any module that implements `constantsToExport:` will be initialized later when the config is exported (the module itself will be initialized on a background queue, but `constantsToExport:` will still be called on the main thread. * All other modules will be initialized lazily when a method is first called on them. These rules may seem slightly arcane, but they have the advantage of not violating any assumptions that may have been made by existing code - any module written under the original assumption that it would be initialized synchronously on the main thread when the bridge is created should still function exactly the same, but modules that avoid overriding `init` or `setBridge:` will now be loaded lazily. I've rewritten most of the standard modules to take advantage of this new lazy loading, with the following results: Out of the 65 modules included in UIExplorer: * 16 are initialized on the main thread when the bridge is created * A further 8 are initialized when the config is exported to JS * The remaining 41 will be initialized lazily on-demand Reviewed By: jspahrsummers Differential Revision: D2677695 fb-gh-sync-id: 507ae7e9fd6b563e89292c7371767c978e928f33
2015-11-25 11:09:00 +00:00
return [self moduleForClass:[RCTImageStoreManager class]];
}
@end