@@ -37,7 +37,8 @@ @implementation RCTImageLoader
3737{
3838 NSArray <id <RCTImageURLLoader>> *_loaders;
3939 NSArray <id <RCTImageDataDecoder>> *_decoders;
40- NSURLCache *_cache;
40+ dispatch_queue_t _URLCacheQueue;
41+ NSURLCache *_URLCache;
4142}
4243
4344@synthesize bridge = _bridge;
@@ -87,9 +88,10 @@ - (void)setBridge:(RCTBridge *)bridge
8788 _bridge = bridge;
8889 _loaders = loaders;
8990 _decoders = decoders;
90- _cache = [[NSURLCache alloc ] initWithMemoryCapacity: 5 * 1024 * 1024 // 5MB
91- diskCapacity: 200 * 1024 * 1024 // 200MB
92- diskPath: @" React/RCTImageDownloader" ];
91+ _URLCacheQueue = dispatch_queue_create (" com.facebook.react.ImageLoaderURLCacheQueue" , DISPATCH_QUEUE_SERIAL);
92+ _URLCache = [[NSURLCache alloc ] initWithMemoryCapacity: 5 * 1024 * 1024 // 5MB
93+ diskCapacity: 200 * 1024 * 1024 // 200MB
94+ diskPath: @" React/RCTImageDownloader" ];
9395}
9496
9597- (id <RCTImageURLLoader>)imageURLLoaderForURL : (NSURL *)URL
@@ -185,12 +187,10 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
185187 progressBlock : (RCTImageLoaderProgressBlock)progressHandler
186188 completionBlock : (RCTImageLoaderCompletionBlock)completionBlock
187189{
188- if (imageTag.length == 0 ) {
189- RCTLogWarn (@" source.uri should not be an empty string <Native>" );
190- return ^{};
191- }
192-
193190 __block volatile uint32_t cancelled = 0 ;
191+ __block void (^cancelLoad)(void ) = nil ;
192+ __weak RCTImageLoader *weakSelf = self;
193+
194194 RCTImageLoaderCompletionBlock completionHandler = ^(NSError *error, UIImage *image) {
195195 if ([NSThread isMainThread ]) {
196196
@@ -206,111 +206,133 @@ - (RCTImageLoaderCancellationBlock)loadImageWithTag:(NSString *)imageTag
206206 }
207207 };
208208
209- // Find suitable image URL loader
210- NSURLRequest *request = [RCTConvert NSURLRequest: imageTag];
211- id <RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL: request.URL];
212- if (loadHandler) {
213- return [loadHandler loadImageForURL: request.URL
214- size: size
215- scale: scale
216- resizeMode: resizeMode
217- progressHandler: progressHandler
218- completionHandler: completionHandler] ?: ^{};
219- }
220-
221- // Check if networking module is available
222- if (RCT_DEBUG && ![_bridge respondsToSelector: @selector (networking )]) {
223- RCTLogError (@" No suitable image URL loader found for %@ . You may need to "
224- " import the RCTNetworking library in order to load images." ,
225- imageTag);
209+ if (imageTag.length == 0 ) {
210+ completionHandler (RCTErrorWithMessage (@" source.uri should not be an empty string" ), nil );
226211 return ^{};
227212 }
228213
229- // Check if networking module can load image
230- if (RCT_DEBUG && ![_bridge.networking canHandleRequest: request]) {
231- RCTLogError (@" No suitable image URL loader found for %@ " , imageTag);
232- return ^{};
233- }
214+ // All access to URL cache must be serialized
215+ dispatch_async (_URLCacheQueue, ^{
216+ RCTImageLoader *strongSelf = weakSelf;
217+ if (cancelled || !strongSelf) {
218+ return ;
219+ }
234220
235- // Use networking module to load image
236- __weak RCTImageLoader *weakSelf = self;
237- __block RCTImageLoaderCancellationBlock decodeCancel = nil ;
238- RCTURLRequestCompletionBlock processResponse =
239- ^(NSURLResponse *response, NSData *data, NSError *error) {
221+ // Find suitable image URL loader
222+ NSURLRequest *request = [RCTConvert NSURLRequest: imageTag];
223+ id <RCTImageURLLoader> loadHandler = [strongSelf imageURLLoaderForURL: request.URL];
224+ if (loadHandler) {
225+ cancelLoad = [loadHandler loadImageForURL: request.URL
226+ size: size
227+ scale: scale
228+ resizeMode: resizeMode
229+ progressHandler: progressHandler
230+ completionHandler: completionHandler] ?: ^{};
231+ return ;
232+ }
240233
241- // Check for system errors
242- if (error) {
243- completionHandler (error, nil );
234+ // Check if networking module is available
235+ if (RCT_DEBUG && ![_bridge respondsToSelector: @selector (networking )]) {
236+ RCTLogError (@" No suitable image URL loader found for %@ . You may need to "
237+ " import the RCTNetworking library in order to load images." ,
238+ imageTag);
244239 return ;
245- } else if (!data) {
246- completionHandler (RCTErrorWithMessage (@" Unknown image download error" ), nil );
240+ }
241+
242+ // Check if networking module can load image
243+ if (RCT_DEBUG && ![_bridge.networking canHandleRequest: request]) {
244+ RCTLogError (@" No suitable image URL loader found for %@ " , imageTag);
247245 return ;
248246 }
249247
250- // Check for http errors
251- if ([response isKindOfClass: [NSHTTPURLResponse class ]]) {
252- NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode ;
253- if (statusCode != 200 ) {
254- completionHandler ([[NSError alloc ] initWithDomain: NSURLErrorDomain
255- code: statusCode
256- userInfo: nil ], nil );
248+ // Use networking module to load image
249+ __block RCTImageLoaderCancellationBlock cancelDecode = nil ;
250+ RCTURLRequestCompletionBlock processResponse =
251+ ^(NSURLResponse *response, NSData *data, NSError *error) {
252+
253+ // Check for system errors
254+ if (error) {
255+ completionHandler (error, nil );
256+ return ;
257+ } else if (!data) {
258+ completionHandler (RCTErrorWithMessage (@" Unknown image download error" ), nil );
257259 return ;
258260 }
259- }
260261
261- // Decode image
262- decodeCancel = [weakSelf decodeImageData: data
263- size: size
264- scale: scale
265- resizeMode: resizeMode
266- completionBlock: completionHandler];
267- };
268-
269- // Check for cached response before reloading
270- // TODO: move URL cache out of RCTImageLoader into its own module
271- NSCachedURLResponse *cachedResponse = [_cache cachedResponseForRequest: request];
272- if (cachedResponse) {
273- processResponse (cachedResponse.response , cachedResponse.data , nil );
274- return ^{};
275- }
262+ // Check for http errors
263+ if ([response isKindOfClass: [NSHTTPURLResponse class ]]) {
264+ NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode ;
265+ if (statusCode != 200 ) {
266+ completionHandler ([[NSError alloc ] initWithDomain: NSURLErrorDomain
267+ code: statusCode
268+ userInfo: nil ], nil );
269+ return ;
270+ }
271+ }
276272
277- // Add missing png extension
278- if (request.URL .fileURL && request.URL .pathExtension .length == 0 ) {
279- NSMutableURLRequest *mutableRequest = [request mutableCopy ];
280- mutableRequest.URL = [NSURL fileURLWithPath: [request.URL.path stringByAppendingPathExtension: @" png" ]];
281- request = mutableRequest;
282- }
273+ // Decode image
274+ cancelDecode = [strongSelf decodeImageData: data
275+ size: size
276+ scale: scale
277+ resizeMode: resizeMode
278+ completionBlock: completionHandler];
279+ };
283280
284- // Download image
285- RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest: request completionBlock:
286- ^(NSURLResponse *response, NSData *data, NSError *error) {
287- if (error) {
288- completionHandler (error, nil );
289- return ;
281+ // Add missing png extension
282+ if (request.URL .fileURL && request.URL .pathExtension .length == 0 ) {
283+ NSMutableURLRequest *mutableRequest = [request mutableCopy ];
284+ mutableRequest.URL = [NSURL fileURLWithPath: [request.URL.path stringByAppendingPathExtension: @" png" ]];
285+ request = mutableRequest;
290286 }
291287
292- // Cache the response
288+ // Check for cached response before reloading
293289 // TODO: move URL cache out of RCTImageLoader into its own module
294- RCTImageLoader *strongSelf = weakSelf;
295- BOOL isHTTPRequest = [request.URL.scheme hasPrefix: @" http" ];
296- [strongSelf->_cache storeCachedResponse:
297- [[NSCachedURLResponse alloc ] initWithResponse: response
298- data: data
299- userInfo: nil
300- storagePolicy: isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
301- forRequest: request];
290+ NSCachedURLResponse *cachedResponse = [_URLCache cachedResponseForRequest: request];
291+ if (cachedResponse) {
292+ processResponse (cachedResponse.response , cachedResponse.data , nil );
293+ }
302294
303- // Process image data
304- processResponse (response, data, nil );
295+ // Download image
296+ RCTNetworkTask *task = [_bridge.networking networkTaskWithRequest: request completionBlock:
297+ ^(NSURLResponse *response, NSData *data, NSError *error) {
298+ if (error) {
299+ completionHandler (error, nil );
300+ return ;
301+ }
305302
306- }];
307- task.downloadProgressBlock = progressHandler;
308- [task start ];
303+ dispatch_async (_URLCacheQueue, ^{
304+
305+ // Cache the response
306+ // TODO: move URL cache out of RCTImageLoader into its own module
307+ BOOL isHTTPRequest = [request.URL.scheme hasPrefix: @" http" ];
308+ [strongSelf->_URLCache storeCachedResponse:
309+ [[NSCachedURLResponse alloc ] initWithResponse: response
310+ data: data
311+ userInfo: nil
312+ storagePolicy: isHTTPRequest ? NSURLCacheStorageAllowed: NSURLCacheStorageAllowedInMemoryOnly]
313+ forRequest: request];
314+
315+ // Process image data
316+ processResponse (response, data, nil );
317+
318+ });
319+
320+ }];
321+ task.downloadProgressBlock = progressHandler;
322+ [task start ];
323+
324+ cancelLoad = ^{
325+ [task cancel ];
326+ if (cancelDecode) {
327+ cancelDecode ();
328+ }
329+ };
330+
331+ });
309332
310333 return ^{
311- [task cancel ];
312- if (decodeCancel) {
313- decodeCancel ();
334+ if (cancelLoad) {
335+ cancelLoad ();
314336 }
315337 OSAtomicOr32Barrier (1 , &cancelled);
316338 };
@@ -342,7 +364,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
342364 if (cancelled) {
343365 return ;
344366 }
345- UIImage *image = [UIImage imageWithData: data scale: scale] ;
367+ UIImage *image = RCTDecodeImageWithData ( data, size, scale, resizeMode) ;
346368 if (image) {
347369 completionHandler (nil , image);
348370 } else {
0 commit comments