99
1010#import " RCTImageStoreManager.h"
1111
12+ #import < ImageIO/ImageIO.h>
13+ #import < libkern/OSAtomic.h>
14+ #import < MobileCoreServices/UTType.h>
15+
1216#import " RCTAssert.h"
17+ #import " RCTImageUtils.h"
18+ #import " RCTLog.h"
1319#import " RCTUtils.h"
1420
21+ static NSString *const RCTImageStoreURLScheme = @" rct-image-store" ;
22+
1523@implementation RCTImageStoreManager
1624{
17- NSMutableDictionary <NSString *, UIImage *> *_store;
25+ NSMutableDictionary <NSString *, NSData *> *_store;
26+ NSUInteger *_id;
1827}
1928
2029@synthesize methodQueue = _methodQueue;
@@ -24,100 +33,191 @@ @implementation RCTImageStoreManager
2433- (instancetype )init
2534{
2635 if ((self = [super init ])) {
27-
28- // TODO: need a way to clear this store
2936 _store = [NSMutableDictionary new ];
37+ _id = 0 ;
3038 }
3139 return self;
3240}
3341
34- - (NSString *)storeImage : (UIImage *) image
42+ - (void ) removeImageForTag : ( NSString *)imageTag withBlock : ( void (^)()) block
3543{
36- RCTAssertMainThread ();
37- NSString *tag = [NSString stringWithFormat: @" rct-image-store://%tu " , _store.count];
38- _store[tag] = image;
39- return tag;
44+ dispatch_async (_methodQueue, ^{
45+ [self removeImageForTag: imageTag];
46+ if (block) {
47+ block ();
48+ }
49+ });
4050}
4151
42- - (UIImage *)imageForTag : ( NSString *)imageTag
52+ - (NSString *)_storeImageData : ( NSData *)imageData
4353{
44- RCTAssertMainThread ();
45- return _store[imageTag];
54+ RCTAssertThread (_methodQueue, @" Must be called on RCTImageStoreManager thread" );
55+ NSString *imageTag = [NSString stringWithFormat: @" %@ ://%tu " , RCTImageStoreURLScheme, _id++];
56+ _store[imageTag] = imageData;
57+ return imageTag;
58+ }
59+
60+ - (void )storeImageData : (NSData *)imageData withBlock : (void (^)(NSString *imageTag))block
61+ {
62+ RCTAssertParam (block);
63+ dispatch_async (_methodQueue, ^{
64+ block ([self _storeImageData: imageData]);
65+ });
66+ }
67+
68+ - (void )getImageDataForTag : (NSString *)imageTag withBlock : (void (^)(NSData *imageData))block
69+ {
70+ RCTAssertParam (block);
71+ dispatch_async (_methodQueue, ^{
72+ block (_store[imageTag]);
73+ });
4674}
4775
4876- (void )storeImage : (UIImage *)image withBlock : (void (^)(NSString *imageTag))block
4977{
50- dispatch_async (dispatch_get_main_queue (), ^{
51- NSString *imageTag = [self storeImage: image];
52- if (block) {
78+ RCTAssertParam (block);
79+ dispatch_async (_methodQueue, ^{
80+ NSString *imageTag = [self _storeImageData: RCTGetImageData (image.CGImage, 0.75 )];
81+ dispatch_async (dispatch_get_main_queue (), ^{
5382 block (imageTag);
54- }
83+ });
5584 });
5685}
5786
58- - ( void ) getImageForTag : (NSString *)imageTag withBlock : ( void (^)(UIImage *image)) block
87+ RCT_EXPORT_METHOD (removeImageForTag :(NSString *)imageTag)
5988{
60- RCTAssert (block != nil , @" block must not be nil" );
61- dispatch_async (dispatch_get_main_queue (), ^{
62- block ([self imageForTag: imageTag]);
63- });
89+ [_store removeObjectForKey: imageTag];
6490}
6591
66- // TODO (#5906496): Name could be more explicit - something like getBase64EncodedJPEGDataForTag :?
92+ // TODO (#5906496): Name could be more explicit - something like getBase64EncodedDataForTag :?
6793RCT_EXPORT_METHOD (getBase64ForTag:(NSString *)imageTag
6894 successCallback:(RCTResponseSenderBlock)successCallback
6995 errorCallback:(RCTResponseErrorBlock)errorCallback)
7096{
71- [self getImageForTag: imageTag withBlock: ^(UIImage *image) {
72- if (!image) {
73- errorCallback (RCTErrorWithMessage ([NSString stringWithFormat: @" Invalid imageTag: %@ " , imageTag]));
74- return ;
75- }
76- dispatch_async (_methodQueue, ^{
77- NSData *imageData = UIImageJPEGRepresentation (image, 1.0 );
78- NSString *base64 = [imageData base64EncodedStringWithOptions: NSDataBase64EncodingEndLineWithLineFeed ];
79- successCallback (@[[base64 stringByReplacingOccurrencesOfString: @" \n " withString: @" " ]]);
80- });
81- }];
97+ NSData *imageData = _store[imageTag];
98+ if (!imageData) {
99+ errorCallback (RCTErrorWithMessage ([NSString stringWithFormat: @" Invalid imageTag: %@ " , imageTag]));
100+ return ;
101+ }
102+ // Dispatching to a background thread to perform base64 encoding
103+ dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
104+ successCallback (@[[imageData base64EncodedStringWithOptions: 0 ]]);
105+ });
82106}
83107
84108RCT_EXPORT_METHOD (addImageFromBase64:(NSString *)base64String
85109 successCallback:(RCTResponseSenderBlock)successCallback
86110 errorCallback:(RCTResponseErrorBlock)errorCallback)
87111
88112{
89- NSData *imageData = [[NSData alloc ] initWithBase64EncodedString: base64String options: 0 ];
90- if (imageData) {
91- UIImage *image = [[UIImage alloc ] initWithData: imageData];
92- [self storeImage: image withBlock: ^(NSString *imageTag) {
93- successCallback (@[imageTag]);
94- }];
95- } else {
96- errorCallback (RCTErrorWithMessage (@" Failed to add image from base64String" ));
97- }
113+ // Dispatching to a background thread to perform base64 decoding
114+ dispatch_async (dispatch_get_global_queue (DISPATCH_QUEUE_PRIORITY_DEFAULT, 0 ), ^{
115+ NSData *imageData = [[NSData alloc ] initWithBase64EncodedString: base64String options: 0 ];
116+ if (imageData) {
117+ dispatch_async (_methodQueue, ^{
118+ successCallback (@[[self _storeImageData: imageData]]);
119+ });
120+ } else {
121+ errorCallback (RCTErrorWithMessage (@" Failed to add image from base64String" ));
122+ }
123+ });
98124}
99125
100- #pragma mark - RCTImageLoader
126+ #pragma mark - RCTURLRequestHandler
101127
102- - (BOOL )canLoadImageURL : ( NSURL *)requestURL
128+ - (BOOL )canHandleRequest : ( NSURLRequest *)request
103129{
104- return [requestURL.scheme.lowercaseString isEqualToString: @" rct-image-store " ] ;
130+ return [request.URL.scheme caseInsensitiveCompare: RCTImageStoreURLScheme] == NSOrderedSame ;
105131}
106132
107- - (RCTImageLoaderCancellationBlock) loadImageForURL : ( NSURL *)imageURL size : (CGSize) size scale : (CGFloat) scale resizeMode : (UIViewContentMode) resizeMode progressHandler : (RCTImageLoaderProgressBlock) progressHandler completionHandler : (RCTImageLoaderCompletionBlock) completionHandler
133+ - (id ) sendRequest : ( NSURLRequest *)request withDelegate : ( id <RCTURLRequestDelegate>) delegate
108134{
109- NSString *imageTag = imageURL.absoluteString ;
110- [self getImageForTag: imageTag withBlock: ^(UIImage *image) {
111- if (image) {
112- completionHandler (nil , image);
113- } else {
114- NSString *errorMessage = [NSString stringWithFormat: @" Unable to load image from image store: %@ " , imageTag];
115- NSError *error = RCTErrorWithMessage (errorMessage);
116- completionHandler (error, nil );
135+ __block volatile uint32_t cancelled = 0 ;
136+ void (^cancellationBlock)(void ) = ^{
137+ OSAtomicOr32Barrier (1 , &cancelled);
138+ };
139+
140+ // Dispatch async to give caller time to cancel the request
141+ dispatch_async (_methodQueue, ^{
142+ if (cancelled) {
143+ return ;
117144 }
118- }];
119145
120- return nil ;
146+ NSString *imageTag = request.URL .absoluteString ;
147+ NSData *imageData = _store[imageTag];
148+ if (!imageData) {
149+ NSError *error = RCTErrorWithMessage ([NSString stringWithFormat: @" Invalid imageTag: %@ " , imageTag]);
150+ [delegate URLRequest: cancellationBlock didCompleteWithError: error];
151+ return ;
152+ }
153+
154+ CGImageSourceRef sourceRef = CGImageSourceCreateWithData ((__bridge CFDataRef)imageData, NULL );
155+ if (!sourceRef) {
156+ NSError *error = RCTErrorWithMessage ([NSString stringWithFormat: @" Unable to decode data for imageTag: %@ " , imageTag]);
157+ [delegate URLRequest: cancellationBlock didCompleteWithError: error];
158+ return ;
159+ }
160+ CFStringRef UTI = CGImageSourceGetType (sourceRef);
161+ CFRelease (sourceRef);
162+
163+ NSString *MIMEType = (__bridge_transfer NSString *)UTTypeCopyPreferredTagWithClass (UTI, kUTTagClassMIMEType );
164+ NSURLResponse *response = [[NSURLResponse alloc ] initWithURL: request.URL
165+ MIMEType: MIMEType
166+ expectedContentLength: imageData.length
167+ textEncodingName: nil ];
168+
169+ [delegate URLRequest: cancellationBlock didReceiveResponse: response];
170+ [delegate URLRequest: cancellationBlock didReceiveData: imageData];
171+ [delegate URLRequest: cancellationBlock didCompleteWithError: nil ];
172+
173+ });
174+
175+ return cancellationBlock;
176+ }
177+
178+ - (void )cancelRequest : (id )requestToken
179+ {
180+ if (requestToken) {
181+ ((void (^)(void ))requestToken)();
182+ }
183+ }
184+
185+ @end
186+
187+ @implementation RCTImageStoreManager (Deprecated)
188+
189+ - (NSString *)storeImage : (UIImage *)image
190+ {
191+ RCTAssertMainThread ();
192+ RCTLogWarn (@" RCTImageStoreManager.storeImage() is deprecated and has poor performance. Use an alternative method instead." );
193+ __block NSString *imageTag;
194+ dispatch_sync (_methodQueue, ^{
195+ imageTag = [self _storeImageData: RCTGetImageData (image.CGImage, 0.75 )];
196+ });
197+ return imageTag;
198+ }
199+
200+ - (UIImage *)imageForTag : (NSString *)imageTag
201+ {
202+ RCTAssertMainThread ();
203+ RCTLogWarn (@" RCTImageStoreManager.imageForTag() is deprecated and has poor performance. Use an alternative method instead." );
204+ __block NSData *imageData;
205+ dispatch_sync (_methodQueue, ^{
206+ imageData = _store[imageTag];
207+ });
208+ return [UIImage imageWithData: imageData];
209+ }
210+
211+ - (void )getImageForTag : (NSString *)imageTag withBlock : (void (^)(UIImage *image))block
212+ {
213+ RCTAssertParam (block);
214+ dispatch_async (_methodQueue, ^{
215+ NSData *imageData = _store[imageTag];
216+ UIImage *image = [UIImage imageWithData: imageData];
217+ dispatch_async (dispatch_get_main_queue (), ^{
218+ block (image);
219+ });
220+ });
121221}
122222
123223@end
0 commit comments