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