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