RCTImageLoader.mm 48 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212
  1. /*
  2. * Copyright (c) Facebook, Inc. and its affiliates.
  3. *
  4. * This source code is licensed under the MIT license found in the
  5. * LICENSE file in the root directory of this source tree.
  6. */
  7. #import <objc/runtime.h>
  8. #import <atomic>
  9. #import <mach/mach_time.h>
  10. #import <ImageIO/ImageIO.h>
  11. #import <FBReactNativeSpec/FBReactNativeSpec.h>
  12. #import <React/RCTConvert.h>
  13. #import <React/RCTDefines.h>
  14. #import <React/RCTImageCache.h>
  15. #import <React/RCTImageLoader.h>
  16. #import <React/RCTImageLoaderWithAttributionProtocol.h>
  17. #import <React/RCTImageUtils.h>
  18. #import <React/RCTLog.h>
  19. #import <React/RCTNetworking.h>
  20. #import <React/RCTUtils.h>
  21. #import "RCTImagePlugins.h"
  22. using namespace facebook::react;
  23. static BOOL imagePerfInstrumentationEnabled = NO;
  24. BOOL RCTImageLoadingPerfInstrumentationEnabled(void)
  25. {
  26. return imagePerfInstrumentationEnabled;
  27. }
  28. void RCTEnableImageLoadingPerfInstrumentation(BOOL enabled)
  29. {
  30. imagePerfInstrumentationEnabled = enabled;
  31. }
  32. static NSInteger RCTImageBytesForImage(UIImage *image)
  33. {
  34. NSInteger singleImageBytes = image.size.width * image.size.height * image.scale * image.scale * 4;
  35. return image.images ? image.images.count * singleImageBytes : singleImageBytes;
  36. }
  37. static uint64_t monotonicTimeGetCurrentNanoseconds(void)
  38. {
  39. static struct mach_timebase_info tb_info = {0};
  40. static dispatch_once_t onceToken;
  41. dispatch_once(&onceToken, ^{
  42. __unused int ret = mach_timebase_info(&tb_info);
  43. assert(0 == ret);
  44. });
  45. return (mach_absolute_time() * tb_info.numer) / tb_info.denom;
  46. }
  47. @interface RCTImageLoader() <NativeImageLoaderIOSSpec, RCTImageLoaderWithAttributionProtocol>
  48. @end
  49. @implementation UIImage (React)
  50. - (NSInteger)reactDecodedImageBytes
  51. {
  52. NSNumber *imageBytes = objc_getAssociatedObject(self, _cmd);
  53. if (!imageBytes) {
  54. imageBytes = @(RCTImageBytesForImage(self));
  55. }
  56. return [imageBytes integerValue];
  57. }
  58. - (void)setReactDecodedImageBytes:(NSInteger)bytes
  59. {
  60. objc_setAssociatedObject(self, @selector(reactDecodedImageBytes), @(bytes), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
  61. }
  62. @end
  63. @implementation RCTImageLoader
  64. {
  65. NSArray<id<RCTImageURLLoader>> * (^_loadersProvider)(void);
  66. NSArray<id<RCTImageDataDecoder>> * (^_decodersProvider)(void);
  67. NSArray<id<RCTImageURLLoader>> *_loaders;
  68. NSArray<id<RCTImageDataDecoder>> *_decoders;
  69. NSOperationQueue *_imageDecodeQueue;
  70. dispatch_queue_t _URLRequestQueue;
  71. id<RCTImageCache> _imageCache;
  72. NSMutableArray *_pendingTasks;
  73. NSInteger _activeTasks;
  74. NSMutableArray *_pendingDecodes;
  75. NSInteger _scheduledDecodes;
  76. NSUInteger _activeBytes;
  77. __weak id<RCTImageRedirectProtocol> _redirectDelegate;
  78. }
  79. @synthesize bridge = _bridge;
  80. @synthesize maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks;
  81. @synthesize maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks;
  82. @synthesize maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes;
  83. @synthesize turboModuleLookupDelegate = _turboModuleLookupDelegate;
  84. RCT_EXPORT_MODULE()
  85. - (instancetype)init
  86. {
  87. return [self initWithRedirectDelegate:nil];
  88. }
  89. + (BOOL)requiresMainQueueSetup
  90. {
  91. return NO;
  92. }
  93. - (instancetype)initWithRedirectDelegate:(id<RCTImageRedirectProtocol>)redirectDelegate
  94. {
  95. if (self = [super init]) {
  96. _redirectDelegate = redirectDelegate;
  97. }
  98. return self;
  99. }
  100. - (instancetype)initWithRedirectDelegate:(id<RCTImageRedirectProtocol>)redirectDelegate
  101. loadersProvider:(NSArray<id<RCTImageURLLoader>> * (^)(void))getLoaders
  102. decodersProvider:(NSArray<id<RCTImageDataDecoder>> * (^)(void))getHandlers
  103. {
  104. if (self = [self initWithRedirectDelegate:redirectDelegate]) {
  105. _loadersProvider = getLoaders;
  106. _decodersProvider = getHandlers;
  107. }
  108. return self;
  109. }
  110. - (void)setUp
  111. {
  112. // Set defaults
  113. _maxConcurrentLoadingTasks = _maxConcurrentLoadingTasks ?: 4;
  114. _maxConcurrentDecodingTasks = _maxConcurrentDecodingTasks ?: 2;
  115. _maxConcurrentDecodingBytes = _maxConcurrentDecodingBytes ?: 30 * 1024 * 1024; // 30MB
  116. _URLRequestQueue = dispatch_queue_create("com.facebook.react.ImageLoaderURLRequestQueue", DISPATCH_QUEUE_SERIAL);
  117. }
  118. - (float)handlerPriority
  119. {
  120. return 2;
  121. }
  122. #pragma mark - RCTImageLoaderProtocol 1/3
  123. - (id<RCTImageCache>)imageCache
  124. {
  125. if (!_imageCache) {
  126. //set up with default cache
  127. _imageCache = [RCTImageCache new];
  128. }
  129. return _imageCache;
  130. }
  131. - (void)setImageCache:(id<RCTImageCache>)cache
  132. {
  133. if (_imageCache) {
  134. RCTLogWarn(@"RCTImageCache was already set and has now been overridden.");
  135. }
  136. _imageCache = cache;
  137. }
  138. - (id<RCTImageURLLoader>)imageURLLoaderForURL:(NSURL *)URL
  139. {
  140. if (!_maxConcurrentLoadingTasks) {
  141. [self setUp];
  142. }
  143. if (!_loaders) {
  144. // Get loaders, sorted in reverse priority order (highest priority first)
  145. if (_loadersProvider) {
  146. _loaders = _loadersProvider();
  147. } else {
  148. RCTAssert(_bridge, @"Trying to find RCTImageURLLoaders and bridge not set.");
  149. _loaders = [_bridge modulesConformingToProtocol:@protocol(RCTImageURLLoader)];
  150. }
  151. _loaders = [_loaders sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageURLLoader> a, id<RCTImageURLLoader> b) {
  152. float priorityA = [a respondsToSelector:@selector(loaderPriority)] ? [a loaderPriority] : 0;
  153. float priorityB = [b respondsToSelector:@selector(loaderPriority)] ? [b loaderPriority] : 0;
  154. if (priorityA > priorityB) {
  155. return NSOrderedAscending;
  156. } else if (priorityA < priorityB) {
  157. return NSOrderedDescending;
  158. } else {
  159. return NSOrderedSame;
  160. }
  161. }];
  162. }
  163. if (RCT_DEBUG) {
  164. // Check for handler conflicts
  165. float previousPriority = 0;
  166. id<RCTImageURLLoader> previousLoader = nil;
  167. for (id<RCTImageURLLoader> loader in _loaders) {
  168. float priority = [loader respondsToSelector:@selector(loaderPriority)] ? [loader loaderPriority] : 0;
  169. if (previousLoader && priority < previousPriority) {
  170. return previousLoader;
  171. }
  172. if ([loader canLoadImageURL:URL]) {
  173. if (previousLoader) {
  174. if (priority == previousPriority) {
  175. RCTLogError(@"The RCTImageURLLoaders %@ and %@ both reported that"
  176. " they can load the URL %@, and have equal priority"
  177. " (%g). This could result in non-deterministic behavior.",
  178. loader, previousLoader, URL, priority);
  179. }
  180. } else {
  181. previousLoader = loader;
  182. previousPriority = priority;
  183. }
  184. }
  185. }
  186. return previousLoader;
  187. }
  188. // Normal code path
  189. for (id<RCTImageURLLoader> loader in _loaders) {
  190. if ([loader canLoadImageURL:URL]) {
  191. return loader;
  192. }
  193. }
  194. return nil;
  195. }
  196. # pragma mark - Private Image Decoding & Resizing
  197. - (id<RCTImageDataDecoder>)imageDataDecoderForData:(NSData *)data
  198. {
  199. if (!_maxConcurrentLoadingTasks) {
  200. [self setUp];
  201. }
  202. if (!_decoders) {
  203. // Get decoders, sorted in reverse priority order (highest priority first)
  204. if (_decodersProvider) {
  205. _decoders = _decodersProvider();
  206. } else {
  207. RCTAssert(_bridge, @"Trying to find RCTImageDataDecoders and bridge not set.");
  208. _decoders = [_bridge modulesConformingToProtocol:@protocol(RCTImageDataDecoder)];
  209. }
  210. _decoders = [_decoders sortedArrayUsingComparator:^NSComparisonResult(id<RCTImageDataDecoder> a, id<RCTImageDataDecoder> b) {
  211. float priorityA = [a respondsToSelector:@selector(decoderPriority)] ? [a decoderPriority] : 0;
  212. float priorityB = [b respondsToSelector:@selector(decoderPriority)] ? [b decoderPriority] : 0;
  213. if (priorityA > priorityB) {
  214. return NSOrderedAscending;
  215. } else if (priorityA < priorityB) {
  216. return NSOrderedDescending;
  217. } else {
  218. return NSOrderedSame;
  219. }
  220. }];
  221. }
  222. if (RCT_DEBUG) {
  223. // Check for handler conflicts
  224. float previousPriority = 0;
  225. id<RCTImageDataDecoder> previousDecoder = nil;
  226. for (id<RCTImageDataDecoder> decoder in _decoders) {
  227. float priority = [decoder respondsToSelector:@selector(decoderPriority)] ? [decoder decoderPriority] : 0;
  228. if (previousDecoder && priority < previousPriority) {
  229. return previousDecoder;
  230. }
  231. if ([decoder canDecodeImageData:data]) {
  232. if (previousDecoder) {
  233. if (priority == previousPriority) {
  234. RCTLogError(@"The RCTImageDataDecoders %@ and %@ both reported that"
  235. " they can decode the data <NSData %p; %tu bytes>, and"
  236. " have equal priority (%g). This could result in"
  237. " non-deterministic behavior.",
  238. decoder, previousDecoder, data, data.length, priority);
  239. }
  240. } else {
  241. previousDecoder = decoder;
  242. previousPriority = priority;
  243. }
  244. }
  245. }
  246. return previousDecoder;
  247. }
  248. // Normal code path
  249. for (id<RCTImageDataDecoder> decoder in _decoders) {
  250. if ([decoder canDecodeImageData:data]) {
  251. return decoder;
  252. }
  253. }
  254. return nil;
  255. }
  256. static UIImage *RCTResizeImageIfNeeded(UIImage *image,
  257. CGSize size,
  258. CGFloat scale,
  259. RCTResizeMode resizeMode)
  260. {
  261. if (CGSizeEqualToSize(size, CGSizeZero) ||
  262. CGSizeEqualToSize(image.size, CGSizeZero) ||
  263. CGSizeEqualToSize(image.size, size)) {
  264. return image;
  265. }
  266. CGRect targetSize = RCTTargetRect(image.size, size, scale, resizeMode);
  267. CGAffineTransform transform = RCTTransformFromTargetRect(image.size, targetSize);
  268. image = RCTTransformImage(image, size, scale, transform);
  269. return image;
  270. }
  271. #pragma mark - RCTImageLoaderProtocol 2/3
  272. - (nullable RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
  273. callback:(RCTImageLoaderCompletionBlock)callback
  274. {
  275. return [self loadImageWithURLRequest:imageURLRequest
  276. priority:RCTImageLoaderPriorityImmediate
  277. callback:callback];
  278. }
  279. - (nullable RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
  280. priority:(RCTImageLoaderPriority)priority
  281. callback:(RCTImageLoaderCompletionBlock)callback {
  282. return [self loadImageWithURLRequest:imageURLRequest
  283. size:CGSizeZero
  284. scale:1
  285. clipped:YES
  286. resizeMode:RCTResizeModeStretch
  287. priority:priority
  288. progressBlock:nil
  289. partialLoadBlock:nil
  290. completionBlock:callback];
  291. }
  292. - (nullable RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
  293. size:(CGSize)size
  294. scale:(CGFloat)scale
  295. clipped:(BOOL)clipped
  296. resizeMode:(RCTResizeMode)resizeMode
  297. progressBlock:(RCTImageLoaderProgressBlock)progressBlock
  298. partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
  299. completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
  300. {
  301. return [self loadImageWithURLRequest:imageURLRequest
  302. size:size
  303. scale:scale
  304. clipped:clipped
  305. resizeMode:resizeMode
  306. priority:RCTImageLoaderPriorityImmediate
  307. progressBlock:progressBlock
  308. partialLoadBlock:partialLoadBlock
  309. completionBlock:completionBlock];
  310. }
  311. - (nullable RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
  312. size:(CGSize)size
  313. scale:(CGFloat)scale
  314. clipped:(BOOL)clipped
  315. resizeMode:(RCTResizeMode)resizeMode
  316. priority:(RCTImageLoaderPriority)priority
  317. progressBlock:(RCTImageLoaderProgressBlock)progressBlock
  318. partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
  319. completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
  320. {
  321. RCTImageURLLoaderRequest *request = [self loadImageWithURLRequest:imageURLRequest
  322. size:size
  323. scale:scale
  324. clipped:clipped
  325. resizeMode:resizeMode
  326. priority:priority
  327. attribution:{}
  328. progressBlock:progressBlock
  329. partialLoadBlock:partialLoadBlock
  330. completionBlock:completionBlock];
  331. return ^{
  332. [request cancel];
  333. };
  334. }
  335. #pragma mark - Private Downloader Methods
  336. - (void)dequeueTasks
  337. {
  338. dispatch_async(_URLRequestQueue, ^{
  339. // Remove completed tasks
  340. NSMutableArray *tasksToRemove = nil;
  341. for (RCTNetworkTask *task in self->_pendingTasks.reverseObjectEnumerator) {
  342. switch (task.status) {
  343. case RCTNetworkTaskFinished:
  344. if (!tasksToRemove) {
  345. tasksToRemove = [NSMutableArray new];
  346. }
  347. [tasksToRemove addObject:task];
  348. self->_activeTasks--;
  349. break;
  350. case RCTNetworkTaskPending:
  351. break;
  352. case RCTNetworkTaskInProgress:
  353. // Check task isn't "stuck"
  354. if (task.requestToken == nil) {
  355. RCTLogWarn(@"Task orphaned for request %@", task.request);
  356. if (!tasksToRemove) {
  357. tasksToRemove = [NSMutableArray new];
  358. }
  359. [tasksToRemove addObject:task];
  360. self->_activeTasks--;
  361. [task cancel];
  362. }
  363. break;
  364. }
  365. }
  366. if (tasksToRemove) {
  367. [self->_pendingTasks removeObjectsInArray:tasksToRemove];
  368. }
  369. // Start queued decode
  370. NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count;
  371. while (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes &&
  372. activeDecodes <= self->_maxConcurrentDecodingTasks)) {
  373. dispatch_block_t decodeBlock = self->_pendingDecodes.firstObject;
  374. if (decodeBlock) {
  375. [self->_pendingDecodes removeObjectAtIndex:0];
  376. decodeBlock();
  377. } else {
  378. break;
  379. }
  380. }
  381. // Start queued tasks
  382. for (RCTNetworkTask *task in self->_pendingTasks) {
  383. if (MAX(self->_activeTasks, self->_scheduledDecodes) >= self->_maxConcurrentLoadingTasks) {
  384. break;
  385. }
  386. if (task.status == RCTNetworkTaskPending) {
  387. [task start];
  388. self->_activeTasks++;
  389. }
  390. }
  391. });
  392. }
  393. /**
  394. * This returns either an image, or raw image data, depending on the loading
  395. * path taken. This is useful if you want to skip decoding, e.g. when preloading
  396. * the image, or retrieving metadata.
  397. */
  398. - (RCTImageURLLoaderRequest *)_loadImageOrDataWithURLRequest:(NSURLRequest *)request
  399. size:(CGSize)size
  400. scale:(CGFloat)scale
  401. resizeMode:(RCTResizeMode)resizeMode
  402. priority:(RCTImageLoaderPriority)priority
  403. attribution:(const ImageURLLoaderAttribution &)attribution
  404. progressBlock:(RCTImageLoaderProgressBlock)progressHandler
  405. partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
  406. completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response))completionBlock
  407. {
  408. {
  409. NSMutableURLRequest *mutableRequest = [request mutableCopy];
  410. [NSURLProtocol setProperty:@"RCTImageLoader"
  411. forKey:@"trackingName"
  412. inRequest:mutableRequest];
  413. // Add missing png extension
  414. if (request.URL.fileURL && request.URL.pathExtension.length == 0) {
  415. mutableRequest.URL = [request.URL URLByAppendingPathExtension:@"png"];
  416. }
  417. if (_redirectDelegate != nil) {
  418. mutableRequest.URL = [_redirectDelegate redirectAssetsURL:mutableRequest.URL];
  419. }
  420. request = mutableRequest;
  421. }
  422. // Create a copy here so the value is retained when accessed in the blocks below.
  423. ImageURLLoaderAttribution attributionCopy(attribution);
  424. // Find suitable image URL loader
  425. id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:request.URL];
  426. BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ?
  427. [loadHandler requiresScheduling] : YES;
  428. BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ?
  429. [loadHandler shouldCacheLoadedImages] : YES;
  430. auto cancelled = std::make_shared<std::atomic<int>>(0);
  431. __block dispatch_block_t cancelLoad = nil;
  432. __block NSLock *cancelLoadLock = [NSLock new];
  433. NSString *requestId = [NSString stringWithFormat:@"%@-%llu",[[NSUUID UUID] UUIDString], monotonicTimeGetCurrentNanoseconds()];
  434. void (^completionHandler)(NSError *, id, NSURLResponse *) = ^(NSError *error, id imageOrData, NSURLResponse *response) {
  435. [cancelLoadLock lock];
  436. cancelLoad = nil;
  437. [cancelLoadLock unlock];
  438. // If we've received an image, we should try to set it synchronously,
  439. // if it's data, do decoding on a background thread.
  440. if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) {
  441. // Most loaders do not return on the main thread, so caller is probably not
  442. // expecting it, and may do expensive post-processing in the callback
  443. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  444. if (!std::atomic_load(cancelled.get())) {
  445. completionBlock(error, imageOrData, cacheResult, response);
  446. }
  447. });
  448. } else if (!std::atomic_load(cancelled.get())) {
  449. completionBlock(error, imageOrData, cacheResult, response);
  450. }
  451. };
  452. // If the loader doesn't require scheduling we call it directly on
  453. // the main queue.
  454. if (loadHandler && !requiresScheduling) {
  455. if ([loadHandler conformsToProtocol:@protocol(RCTImageURLLoaderWithAttribution)]) {
  456. return [(id<RCTImageURLLoaderWithAttribution>)loadHandler loadImageForURL:request.URL
  457. size:size
  458. scale:scale
  459. resizeMode:resizeMode
  460. requestId:requestId
  461. priority:priority
  462. attribution:attributionCopy
  463. progressHandler:progressHandler
  464. partialLoadHandler:partialLoadHandler
  465. completionHandler:^(NSError *error, UIImage *image) {
  466. completionHandler(error, image, nil);
  467. }];
  468. }
  469. RCTImageLoaderCancellationBlock cb = [loadHandler loadImageForURL:request.URL
  470. size:size
  471. scale:scale
  472. resizeMode:resizeMode
  473. progressHandler:progressHandler
  474. partialLoadHandler:partialLoadHandler
  475. completionHandler:^(NSError *error, UIImage *image) {
  476. completionHandler(error, image, nil);
  477. }];
  478. return [[RCTImageURLLoaderRequest alloc] initWithRequestId:nil imageURL:request.URL cancellationBlock:cb];
  479. }
  480. // All access to URL cache must be serialized
  481. if (!_URLRequestQueue) {
  482. [self setUp];
  483. }
  484. __weak RCTImageLoader *weakSelf = self;
  485. dispatch_async(_URLRequestQueue, ^{
  486. __typeof(self) strongSelf = weakSelf;
  487. if (atomic_load(cancelled.get()) || !strongSelf) {
  488. return;
  489. }
  490. if (loadHandler) {
  491. dispatch_block_t cancelLoadLocal;
  492. if ([loadHandler conformsToProtocol:@protocol(RCTImageURLLoaderWithAttribution)]) {
  493. RCTImageURLLoaderRequest *loaderRequest = [(id<RCTImageURLLoaderWithAttribution>)loadHandler loadImageForURL:request.URL
  494. size:size
  495. scale:scale
  496. resizeMode:resizeMode
  497. requestId:requestId
  498. priority:priority
  499. attribution:attributionCopy
  500. progressHandler:progressHandler
  501. partialLoadHandler:partialLoadHandler
  502. completionHandler:^(NSError *error, UIImage *image) {
  503. completionHandler(error, image, nil);
  504. }];
  505. cancelLoadLocal = loaderRequest.cancellationBlock;
  506. } else {
  507. cancelLoadLocal = [loadHandler loadImageForURL:request.URL
  508. size:size
  509. scale:scale
  510. resizeMode:resizeMode
  511. progressHandler:progressHandler
  512. partialLoadHandler:partialLoadHandler
  513. completionHandler:^(NSError *error, UIImage *image) {
  514. completionHandler(error, image, nil);
  515. }];
  516. }
  517. [cancelLoadLock lock];
  518. cancelLoad = cancelLoadLocal;
  519. [cancelLoadLock unlock];
  520. } else {
  521. UIImage *image;
  522. if (cacheResult) {
  523. image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString
  524. size:size
  525. scale:scale
  526. resizeMode:resizeMode];
  527. }
  528. if (image) {
  529. completionHandler(nil, image, nil);
  530. } else {
  531. // Use networking module to load image
  532. dispatch_block_t cancelLoadLocal = [strongSelf _loadURLRequest:request
  533. progressBlock:progressHandler
  534. completionBlock:completionHandler];
  535. [cancelLoadLock lock];
  536. cancelLoad = cancelLoadLocal;
  537. [cancelLoadLock unlock];
  538. }
  539. }
  540. });
  541. return [[RCTImageURLLoaderRequest alloc] initWithRequestId:requestId imageURL:request.URL cancellationBlock:^{
  542. BOOL alreadyCancelled = atomic_fetch_or(cancelled.get(), 1);
  543. if (alreadyCancelled) {
  544. return;
  545. }
  546. [cancelLoadLock lock];
  547. dispatch_block_t cancelLoadLocal = cancelLoad;
  548. cancelLoad = nil;
  549. [cancelLoadLock unlock];
  550. if (cancelLoadLocal) {
  551. cancelLoadLocal();
  552. }
  553. }];
  554. }
  555. - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
  556. progressBlock:(RCTImageLoaderProgressBlock)progressHandler
  557. completionBlock:(void (^)(NSError *error, id imageOrData, NSURLResponse *response))completionHandler
  558. {
  559. // Check if networking module is available
  560. if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]
  561. && ![_turboModuleLookupDelegate moduleForName:"RCTNetworking"]) {
  562. RCTLogError(@"No suitable image URL loader found for %@. You may need to "
  563. " import the RCTNetwork library in order to load images.",
  564. request.URL.absoluteString);
  565. return NULL;
  566. }
  567. RCTNetworking *networking = [_bridge networking];
  568. if (!networking) {
  569. networking = [_turboModuleLookupDelegate moduleForName:"RCTNetworking"];
  570. }
  571. // Check if networking module can load image
  572. if (RCT_DEBUG && ![networking canHandleRequest:request]) {
  573. RCTLogError(@"No suitable image URL loader found for %@", request.URL.absoluteString);
  574. return NULL;
  575. }
  576. // Use networking module to load image
  577. RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) {
  578. // Check for system errors
  579. if (error) {
  580. completionHandler(error, nil, response);
  581. return;
  582. } else if (!response) {
  583. completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, response);
  584. return;
  585. } else if (!data) {
  586. completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, response);
  587. return;
  588. }
  589. // Check for http errors
  590. if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
  591. NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
  592. if (statusCode != 200) {
  593. NSString *errorMessage = [NSString stringWithFormat:@"Failed to load %@", response.URL];
  594. NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage};
  595. completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
  596. code:statusCode
  597. userInfo:userInfo], nil, response);
  598. return;
  599. }
  600. }
  601. // Call handler
  602. completionHandler(nil, data, response);
  603. };
  604. // Download image
  605. __weak __typeof(self) weakSelf = self;
  606. __block RCTNetworkTask *task =
  607. [networking networkTaskWithRequest:request
  608. completionBlock:^(NSURLResponse *response, NSData *data, NSError *error) {
  609. __typeof(self) strongSelf = weakSelf;
  610. if (!strongSelf) {
  611. return;
  612. }
  613. if (error || !response || !data) {
  614. NSError *someError = nil;
  615. if (error) {
  616. someError = error;
  617. } else if (!response) {
  618. someError = RCTErrorWithMessage(@"Response metadata error");
  619. } else {
  620. someError = RCTErrorWithMessage(@"Unknown image download error");
  621. }
  622. completionHandler(someError, nil, response);
  623. [strongSelf dequeueTasks];
  624. return;
  625. }
  626. dispatch_async(strongSelf->_URLRequestQueue, ^{
  627. // Process image data
  628. processResponse(response, data, nil);
  629. // Prepare for next task
  630. [strongSelf dequeueTasks];
  631. });
  632. }];
  633. task.downloadProgressBlock = ^(int64_t progress, int64_t total) {
  634. if (progressHandler) {
  635. progressHandler(progress, total);
  636. }
  637. };
  638. if (task) {
  639. if (!_pendingTasks) {
  640. _pendingTasks = [NSMutableArray new];
  641. }
  642. [_pendingTasks addObject:task];
  643. [self dequeueTasks];
  644. }
  645. return ^{
  646. __typeof(self) strongSelf = weakSelf;
  647. if (!strongSelf || !task) {
  648. return;
  649. }
  650. dispatch_async(strongSelf->_URLRequestQueue, ^{
  651. [task cancel];
  652. task = nil;
  653. });
  654. [strongSelf dequeueTasks];
  655. };
  656. }
  657. #pragma mark - RCTImageLoaderWithAttributionProtocol
  658. - (RCTImageURLLoaderRequest *)loadImageWithURLRequest:(NSURLRequest *)imageURLRequest
  659. size:(CGSize)size
  660. scale:(CGFloat)scale
  661. clipped:(BOOL)clipped
  662. resizeMode:(RCTResizeMode)resizeMode
  663. priority:(RCTImageLoaderPriority)priority
  664. attribution:(const ImageURLLoaderAttribution &)attribution
  665. progressBlock:(RCTImageLoaderProgressBlock)progressBlock
  666. partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadBlock
  667. completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
  668. {
  669. auto cancelled = std::make_shared<std::atomic<int>>(0);
  670. __block dispatch_block_t cancelLoad = nil;
  671. __block NSLock *cancelLoadLock = [NSLock new];
  672. dispatch_block_t cancellationBlock = ^{
  673. BOOL alreadyCancelled = atomic_fetch_or(cancelled.get(), 1);
  674. if (alreadyCancelled) {
  675. return;
  676. }
  677. [cancelLoadLock lock];
  678. dispatch_block_t cancelLoadLocal = cancelLoad;
  679. cancelLoad = nil;
  680. [cancelLoadLock unlock];
  681. if (cancelLoadLocal) {
  682. cancelLoadLocal();
  683. }
  684. };
  685. __weak RCTImageLoader *weakSelf = self;
  686. void (^completionHandler)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) {
  687. __typeof(self) strongSelf = weakSelf;
  688. if (std::atomic_load(cancelled.get()) || !strongSelf) {
  689. return;
  690. }
  691. if (!imageOrData || [imageOrData isKindOfClass:[UIImage class]]) {
  692. [cancelLoadLock lock];
  693. cancelLoad = nil;
  694. [cancelLoadLock unlock];
  695. completionBlock(error, imageOrData);
  696. return;
  697. }
  698. RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {
  699. if (cacheResult && image) {
  700. // Store decoded image in cache
  701. [[strongSelf imageCache] addImageToCache:image
  702. URL:imageURLRequest.URL.absoluteString
  703. size:size
  704. scale:scale
  705. resizeMode:resizeMode
  706. response:response];
  707. }
  708. [cancelLoadLock lock];
  709. cancelLoad = nil;
  710. [cancelLoadLock unlock];
  711. completionBlock(error_, image);
  712. };
  713. dispatch_block_t cancelLoadLocal = [strongSelf decodeImageData:imageOrData
  714. size:size
  715. scale:scale
  716. clipped:clipped
  717. resizeMode:resizeMode
  718. completionBlock:decodeCompletionHandler];
  719. [cancelLoadLock lock];
  720. cancelLoad = cancelLoadLocal;
  721. [cancelLoadLock unlock];
  722. };
  723. RCTImageURLLoaderRequest *loaderRequest = [self _loadImageOrDataWithURLRequest:imageURLRequest
  724. size:size
  725. scale:scale
  726. resizeMode:resizeMode
  727. priority:priority
  728. attribution:attribution
  729. progressBlock:progressBlock
  730. partialLoadBlock:partialLoadBlock
  731. completionBlock:completionHandler];
  732. cancelLoad = loaderRequest.cancellationBlock;
  733. return [[RCTImageURLLoaderRequest alloc] initWithRequestId:loaderRequest.requestId imageURL:imageURLRequest.URL cancellationBlock:cancellationBlock];
  734. }
  735. - (void)trackURLImageContentDidSetForRequest:(RCTImageURLLoaderRequest *)loaderRequest
  736. {
  737. if (!loaderRequest) {
  738. return;
  739. }
  740. id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
  741. if ([loadHandler respondsToSelector:@selector(trackURLImageContentDidSetForRequest:)]) {
  742. [(id<RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageContentDidSetForRequest:loaderRequest];
  743. }
  744. }
  745. - (void)trackURLImageVisibilityForRequest:(RCTImageURLLoaderRequest *)loaderRequest imageView:(UIView *)imageView
  746. {
  747. if (!loaderRequest || !imageView) {
  748. return;
  749. }
  750. id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
  751. if ([loadHandler respondsToSelector:@selector(trackURLImageVisibilityForRequest:imageView:)]) {
  752. [(id<RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageVisibilityForRequest:loaderRequest imageView:imageView];
  753. }
  754. }
  755. - (void)trackURLImageDidDestroy:(RCTImageURLLoaderRequest *)loaderRequest
  756. {
  757. if (!loaderRequest) {
  758. return;
  759. }
  760. id<RCTImageURLLoader> loadHandler = [self imageURLLoaderForURL:loaderRequest.imageURL];
  761. if ([loadHandler respondsToSelector:@selector(trackURLImageDidDestroy:)]) {
  762. [(id<RCTImageURLLoaderWithAttribution>)loadHandler trackURLImageDidDestroy:loaderRequest];
  763. }
  764. }
  765. #pragma mark - RCTImageLoaderProtocol 3/3
  766. - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data
  767. size:(CGSize)size
  768. scale:(CGFloat)scale
  769. clipped:(BOOL)clipped
  770. resizeMode:(RCTResizeMode)resizeMode
  771. completionBlock:(RCTImageLoaderCompletionBlock)completionBlock
  772. {
  773. if (data.length == 0) {
  774. completionBlock(RCTErrorWithMessage(@"No image data"), nil);
  775. return ^{};
  776. }
  777. auto cancelled = std::make_shared<std::atomic<int>>(0);
  778. void (^completionHandler)(NSError *, UIImage *) = ^(NSError *error, UIImage *image) {
  779. if (RCTIsMainQueue()) {
  780. // Most loaders do not return on the main thread, so caller is probably not
  781. // expecting it, and may do expensive post-processing in the callback
  782. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  783. if (!std::atomic_load(cancelled.get())) {
  784. completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
  785. }
  786. });
  787. } else if (!std::atomic_load(cancelled.get())) {
  788. completionBlock(error, clipped ? RCTResizeImageIfNeeded(image, size, scale, resizeMode) : image);
  789. }
  790. };
  791. id<RCTImageDataDecoder> imageDecoder = [self imageDataDecoderForData:data];
  792. if (imageDecoder) {
  793. return [imageDecoder decodeImageData:data
  794. size:size
  795. scale:scale
  796. resizeMode:resizeMode
  797. completionHandler:completionHandler] ?: ^{};
  798. } else {
  799. dispatch_block_t decodeBlock = ^{
  800. // Calculate the size, in bytes, that the decompressed image will require
  801. NSInteger decodedImageBytes = (size.width * scale) * (size.height * scale) * 4;
  802. // Mark these bytes as in-use
  803. self->_activeBytes += decodedImageBytes;
  804. // Do actual decompression on a concurrent background queue
  805. dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
  806. if (!std::atomic_load(cancelled.get())) {
  807. // Decompress the image data (this may be CPU and memory intensive)
  808. UIImage *image = RCTDecodeImageWithData(data, size, scale, resizeMode);
  809. #if RCT_DEV
  810. CGSize imagePixelSize = RCTSizeInPixels(image.size, image.scale);
  811. CGSize screenPixelSize = RCTSizeInPixels(RCTScreenSize(), RCTScreenScale());
  812. if (imagePixelSize.width * imagePixelSize.height >
  813. screenPixelSize.width * screenPixelSize.height) {
  814. RCTLogInfo(@"[PERF ASSETS] Loading image at size %@, which is larger "
  815. "than the screen size %@", NSStringFromCGSize(imagePixelSize),
  816. NSStringFromCGSize(screenPixelSize));
  817. }
  818. #endif
  819. if (image) {
  820. completionHandler(nil, image);
  821. } else {
  822. NSString *errorMessage = [NSString stringWithFormat:@"Error decoding image data <NSData %p; %tu bytes>", data, data.length];
  823. NSError *finalError = RCTErrorWithMessage(errorMessage);
  824. completionHandler(finalError, nil);
  825. }
  826. }
  827. // We're no longer retaining the uncompressed data, so now we'll mark
  828. // the decoding as complete so that the loading task queue can resume.
  829. dispatch_async(self->_URLRequestQueue, ^{
  830. self->_scheduledDecodes--;
  831. self->_activeBytes -= decodedImageBytes;
  832. [self dequeueTasks];
  833. });
  834. });
  835. };
  836. if (!_URLRequestQueue) {
  837. [self setUp];
  838. }
  839. dispatch_async(_URLRequestQueue, ^{
  840. // The decode operation retains the compressed image data until it's
  841. // complete, so we'll mark it as having started, in order to block
  842. // further image loads from happening until we're done with the data.
  843. self->_scheduledDecodes++;
  844. if (!self->_pendingDecodes) {
  845. self->_pendingDecodes = [NSMutableArray new];
  846. }
  847. NSInteger activeDecodes = self->_scheduledDecodes - self->_pendingDecodes.count - 1;
  848. if (activeDecodes == 0 || (self->_activeBytes <= self->_maxConcurrentDecodingBytes &&
  849. activeDecodes <= self->_maxConcurrentDecodingTasks)) {
  850. decodeBlock();
  851. } else {
  852. [self->_pendingDecodes addObject:decodeBlock];
  853. }
  854. });
  855. return ^{
  856. std::atomic_store(cancelled.get(), 1);
  857. };
  858. }
  859. }
  860. - (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest
  861. block:(void(^)(NSError *error, CGSize size))callback
  862. {
  863. void (^completion)(NSError *, id, BOOL, NSURLResponse *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSURLResponse *response) {
  864. CGSize size;
  865. if ([imageOrData isKindOfClass:[NSData class]]) {
  866. NSDictionary *meta = RCTGetImageMetadata(imageOrData);
  867. NSInteger imageOrientation = [meta[(id)kCGImagePropertyOrientation] integerValue];
  868. switch (imageOrientation) {
  869. case kCGImagePropertyOrientationLeft:
  870. case kCGImagePropertyOrientationRight:
  871. case kCGImagePropertyOrientationLeftMirrored:
  872. case kCGImagePropertyOrientationRightMirrored:
  873. // swap width and height
  874. size = (CGSize){
  875. [meta[(id)kCGImagePropertyPixelHeight] floatValue],
  876. [meta[(id)kCGImagePropertyPixelWidth] floatValue],
  877. };
  878. break;
  879. case kCGImagePropertyOrientationUp:
  880. case kCGImagePropertyOrientationDown:
  881. case kCGImagePropertyOrientationUpMirrored:
  882. case kCGImagePropertyOrientationDownMirrored:
  883. default:
  884. size = (CGSize){
  885. [meta[(id)kCGImagePropertyPixelWidth] floatValue],
  886. [meta[(id)kCGImagePropertyPixelHeight] floatValue],
  887. };
  888. break;
  889. }
  890. } else {
  891. UIImage *image = imageOrData;
  892. size = (CGSize){
  893. image.size.width * image.scale,
  894. image.size.height * image.scale,
  895. };
  896. }
  897. callback(error, size);
  898. };
  899. RCTImageURLLoaderRequest *loaderRequest = [self _loadImageOrDataWithURLRequest:imageURLRequest
  900. size:CGSizeZero
  901. scale:1
  902. resizeMode:RCTResizeModeStretch
  903. priority: RCTImageLoaderPriorityImmediate
  904. attribution:{}
  905. progressBlock:NULL
  906. partialLoadBlock:NULL
  907. completionBlock:completion];
  908. return loaderRequest.cancellationBlock;
  909. }
  910. - (NSDictionary *)getImageCacheStatus:(NSArray *)requests
  911. {
  912. NSMutableDictionary *results = [NSMutableDictionary dictionary];
  913. for (id request in requests) {
  914. NSURLRequest *urlRequest = [RCTConvert NSURLRequest:request];
  915. if (urlRequest) {
  916. NSCachedURLResponse *cachedResponse = [NSURLCache.sharedURLCache cachedResponseForRequest:urlRequest];
  917. if (cachedResponse) {
  918. if (cachedResponse.storagePolicy == NSURLCacheStorageAllowedInMemoryOnly) {
  919. results[urlRequest.URL.absoluteString] = @"memory";
  920. } else if (NSURLCache.sharedURLCache.currentMemoryUsage == 0) {
  921. // We can't check whether the file is cached on disk or memory.
  922. // However, if currentMemoryUsage is disabled, it must be read from disk.
  923. results[urlRequest.URL.absoluteString] = @"disk";
  924. } else {
  925. results[urlRequest.URL.absoluteString] = @"disk/memory";
  926. }
  927. }
  928. }
  929. }
  930. return results;
  931. }
  932. #pragma mark - RCTURLRequestHandler
  933. - (BOOL)canHandleRequest:(NSURLRequest *)request
  934. {
  935. NSURL *requestURL = request.URL;
  936. // If the data being loaded is a video, return NO
  937. // Even better may be to implement this on the RCTImageURLLoader that would try to load it,
  938. // but we'd have to run the logic both in RCTPhotoLibraryImageLoader and
  939. // RCTAssetsLibraryRequestHandler. Once we drop iOS7 though, we'd drop
  940. // RCTAssetsLibraryRequestHandler and can move it there.
  941. static NSRegularExpression *videoRegex;
  942. static dispatch_once_t onceToken;
  943. dispatch_once(&onceToken, ^{
  944. NSError *error = nil;
  945. videoRegex = [NSRegularExpression regularExpressionWithPattern:@"(?:&|^)ext=MOV(?:&|$)"
  946. options:NSRegularExpressionCaseInsensitive
  947. error:&error];
  948. if (error) {
  949. RCTLogError(@"%@", error);
  950. }
  951. });
  952. NSString *query = requestURL.query;
  953. if (
  954. query != nil &&
  955. [videoRegex firstMatchInString:query
  956. options:0
  957. range:NSMakeRange(0, query.length)]
  958. ) {
  959. return NO;
  960. }
  961. for (id<RCTImageURLLoader> loader in _loaders) {
  962. // Don't use RCTImageURLLoader protocol for modules that already conform to
  963. // RCTURLRequestHandler as it's inefficient to decode an image and then
  964. // convert it back into data
  965. if (![loader conformsToProtocol:@protocol(RCTURLRequestHandler)] &&
  966. [loader canLoadImageURL:requestURL]) {
  967. return YES;
  968. }
  969. }
  970. return NO;
  971. }
  972. - (id)sendRequest:(NSURLRequest *)request withDelegate:(id<RCTURLRequestDelegate>)delegate
  973. {
  974. __block RCTImageLoaderCancellationBlock requestToken;
  975. requestToken = [self loadImageWithURLRequest:request callback:^(NSError *error, UIImage *image) {
  976. if (error) {
  977. [delegate URLRequest:requestToken didCompleteWithError:error];
  978. return;
  979. }
  980. NSString *mimeType = nil;
  981. NSData *imageData = nil;
  982. if (RCTImageHasAlpha(image.CGImage)) {
  983. mimeType = @"image/png";
  984. imageData = UIImagePNGRepresentation(image);
  985. } else {
  986. mimeType = @"image/jpeg";
  987. imageData = UIImageJPEGRepresentation(image, 1.0);
  988. }
  989. NSURLResponse *response = [[NSURLResponse alloc] initWithURL:request.URL
  990. MIMEType:mimeType
  991. expectedContentLength:imageData.length
  992. textEncodingName:nil];
  993. [delegate URLRequest:requestToken didReceiveResponse:response];
  994. [delegate URLRequest:requestToken didReceiveData:imageData];
  995. [delegate URLRequest:requestToken didCompleteWithError:nil];
  996. }];
  997. return requestToken;
  998. }
  999. - (void)cancelRequest:(id)requestToken
  1000. {
  1001. if (requestToken) {
  1002. ((RCTImageLoaderCancellationBlock)requestToken)();
  1003. }
  1004. }
  1005. - (std::shared_ptr<facebook::react::TurboModule>)
  1006. getTurboModuleWithJsInvoker:(std::shared_ptr<facebook::react::CallInvoker>)jsInvoker
  1007. nativeInvoker:(std::shared_ptr<facebook::react::CallInvoker>)nativeInvoker
  1008. perfLogger:(id<RCTTurboModulePerformanceLogger>)perfLogger
  1009. {
  1010. return std::make_shared<facebook::react::NativeImageLoaderIOSSpecJSI>(self, jsInvoker, nativeInvoker, perfLogger);
  1011. }
  1012. RCT_EXPORT_METHOD(getSize:(NSString *)uri resolve:(RCTPromiseResolveBlock)resolve reject:(RCTPromiseRejectBlock)reject)
  1013. {
  1014. NSURLRequest *request = [RCTConvert NSURLRequest:uri];
  1015. [self getImageSizeForURLRequest:request
  1016. block:^(NSError *error, CGSize size) {
  1017. if (error) {
  1018. reject(
  1019. @"E_GET_SIZE_FAILURE",
  1020. [NSString stringWithFormat: @"Failed to getSize of %@", uri],
  1021. error);
  1022. } else {
  1023. resolve(@[@(size.width), @(size.height)]);
  1024. }
  1025. }];
  1026. }
  1027. RCT_EXPORT_METHOD(getSizeWithHeaders:(NSString *)uri
  1028. headers:(NSDictionary *)headers
  1029. resolve:(RCTPromiseResolveBlock)resolve
  1030. reject:(RCTPromiseRejectBlock)reject)
  1031. {
  1032. NSURL *URL = [RCTConvert NSURL:uri];
  1033. NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:URL];
  1034. [headers enumerateKeysAndObjectsUsingBlock:^(NSString *key, id value, BOOL *stop) {
  1035. [request addValue:[RCTConvert NSString:value] forHTTPHeaderField:key];
  1036. }];
  1037. [self getImageSizeForURLRequest:request
  1038. block:^(NSError *error, CGSize size) {
  1039. if (error) {
  1040. reject(@"E_GET_SIZE_FAILURE", nil, error);
  1041. return;
  1042. }
  1043. resolve(@{@"width":@(size.width),@"height":@(size.height)});
  1044. }];
  1045. }
  1046. RCT_EXPORT_METHOD(prefetchImage:(NSString *)uri
  1047. resolve:(RCTPromiseResolveBlock)resolve
  1048. reject:(RCTPromiseRejectBlock)reject)
  1049. {
  1050. NSURLRequest *request = [RCTConvert NSURLRequest:uri];
  1051. [self loadImageWithURLRequest:request
  1052. priority:RCTImageLoaderPriorityPrefetch
  1053. callback:^(NSError *error, UIImage *image) {
  1054. if (error) {
  1055. reject(@"E_PREFETCH_FAILURE", nil, error);
  1056. return;
  1057. }
  1058. resolve(@YES);
  1059. }];
  1060. }
  1061. RCT_EXPORT_METHOD(queryCache:(NSArray *)uris
  1062. resolve:(RCTPromiseResolveBlock)resolve
  1063. reject:(RCTPromiseRejectBlock)reject)
  1064. {
  1065. resolve([self getImageCacheStatus:uris]);
  1066. }
  1067. @end
  1068. /**
  1069. * DEPRECATED!! DO NOT USE
  1070. * Instead use `[_bridge moduleForClass:[RCTImageLoader class]]`
  1071. */
  1072. @implementation RCTBridge (RCTImageLoader)
  1073. - (RCTImageLoader *)imageLoader
  1074. {
  1075. RCTLogWarn(@"Calling bridge.imageLoader is deprecated and will not work in newer versions of RN. Please update to the "
  1076. "moduleForClass API or turboModuleLookupDelegate API.");
  1077. return [self moduleForClass:[RCTImageLoader class]];
  1078. }
  1079. @end
  1080. Class RCTImageLoaderCls(void)
  1081. {
  1082. return RCTImageLoader.class;
  1083. }