mirror of
https://github.com/status-im/react-native.git
synced 2025-02-10 08:26:23 +00:00
Summary: This sort of logging helped me identify issues with reloading images too frequently (and for trivial reasons), so leaving it in might be useful for future optimization work, or for anyone building apps using these components. @public Reviewed By: @alexeylang Differential Revision: D2475613
187 lines
6.3 KiB
Objective-C
187 lines
6.3 KiB
Objective-C
/**
|
|
* Copyright (c) 2015-present, Facebook, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This source code is licensed under the BSD-style license found in the
|
|
* LICENSE file in the root directory of this source tree. An additional grant
|
|
* of patent rights can be found in the PATENTS file in the same directory.
|
|
*/
|
|
|
|
#import "RCTAssetsLibraryImageLoader.h"
|
|
|
|
#import <AssetsLibrary/AssetsLibrary.h>
|
|
#import <ImageIO/ImageIO.h>
|
|
#import <libkern/OSAtomic.h>
|
|
#import <UIKit/UIKit.h>
|
|
|
|
#import "RCTBridge.h"
|
|
#import "RCTConvert.h"
|
|
#import "RCTImageLoader.h"
|
|
#import "RCTImageUtils.h"
|
|
#import "RCTUtils.h"
|
|
|
|
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void);
|
|
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error);
|
|
|
|
@implementation RCTAssetsLibraryImageLoader
|
|
{
|
|
ALAssetsLibrary *_assetsLibrary;
|
|
}
|
|
|
|
RCT_EXPORT_MODULE()
|
|
|
|
@synthesize bridge = _bridge;
|
|
|
|
- (ALAssetsLibrary *)assetsLibrary
|
|
{
|
|
return _assetsLibrary ?: (_assetsLibrary = [ALAssetsLibrary new]);
|
|
}
|
|
|
|
#pragma mark - RCTImageLoader
|
|
|
|
- (BOOL)canLoadImageURL:(NSURL *)requestURL
|
|
{
|
|
return [requestURL.scheme.lowercaseString isEqualToString:@"assets-library"];
|
|
}
|
|
|
|
- (RCTImageLoaderCancellationBlock)loadImageForURL:(NSURL *)imageURL size:(CGSize)size scale:(CGFloat)scale resizeMode:(UIViewContentMode)resizeMode progressHandler:(RCTImageLoaderProgressBlock)progressHandler completionHandler:(RCTImageLoaderCompletionBlock)completionHandler
|
|
{
|
|
__block volatile uint32_t cancelled = 0;
|
|
|
|
[[self assetsLibrary] assetForURL:imageURL resultBlock:^(ALAsset *asset) {
|
|
if (cancelled) {
|
|
return;
|
|
}
|
|
|
|
if (asset) {
|
|
// ALAssetLibrary API is async and will be multi-threaded. Loading a few full
|
|
// resolution images at once will spike the memory up to store the image data,
|
|
// and might trigger memory warnings and/or OOM crashes.
|
|
// To improve this, process the loaded asset in a serial queue.
|
|
dispatch_async(RCTAssetsLibraryImageLoaderQueue(), ^{
|
|
if (cancelled) {
|
|
return;
|
|
}
|
|
|
|
// Also make sure the image is released immediately after it's used so it
|
|
// doesn't spike the memory up during the process.
|
|
@autoreleasepool {
|
|
BOOL useMaximumSize = CGSizeEqualToSize(size, CGSizeZero);
|
|
ALAssetRepresentation *representation = [asset defaultRepresentation];
|
|
|
|
#if RCT_DEV
|
|
CGSize sizeBeingLoaded = size;
|
|
if (useMaximumSize) {
|
|
CGSize pointSize = representation.dimensions;
|
|
sizeBeingLoaded = CGSizeMake(pointSize.width * representation.scale, pointSize.height * representation.scale);
|
|
}
|
|
|
|
CGSize screenSize = UIScreen.mainScreen.nativeBounds.size;
|
|
CGFloat maximumPixelDimension = fmax(screenSize.width, screenSize.height);
|
|
|
|
if (sizeBeingLoaded.width > maximumPixelDimension || sizeBeingLoaded.height > maximumPixelDimension) {
|
|
RCTLogInfo(@"[PERF ASSETS] Loading %@ at size %@, which is larger than screen size %@", representation.filename, NSStringFromCGSize(sizeBeingLoaded), NSStringFromCGSize(screenSize));
|
|
}
|
|
#endif
|
|
|
|
UIImage *image;
|
|
NSError *error = nil;
|
|
if (useMaximumSize) {
|
|
image = [UIImage imageWithCGImage:representation.fullResolutionImage
|
|
scale:scale
|
|
orientation:(UIImageOrientation)representation.orientation];
|
|
} else {
|
|
image = RCTScaledImageForAsset(representation, size, scale, resizeMode, &error);
|
|
}
|
|
|
|
completionHandler(error, image);
|
|
}
|
|
});
|
|
} else {
|
|
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@ with no error message.", imageURL];
|
|
NSError *error = RCTErrorWithMessage(errorText);
|
|
completionHandler(error, nil);
|
|
}
|
|
} failureBlock:^(NSError *loadError) {
|
|
if (cancelled) {
|
|
return;
|
|
}
|
|
|
|
NSString *errorText = [NSString stringWithFormat:@"Failed to load asset at URL %@.\niOS Error: %@", imageURL, loadError];
|
|
NSError *error = RCTErrorWithMessage(errorText);
|
|
completionHandler(error, nil);
|
|
}];
|
|
|
|
return ^{
|
|
OSAtomicOr32Barrier(1, &cancelled);
|
|
};
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation RCTBridge (RCTAssetsLibraryImageLoader)
|
|
|
|
- (RCTAssetsLibraryImageLoader *)assetsLibraryImageLoader
|
|
{
|
|
return self.modules[RCTBridgeModuleNameForClass([RCTAssetsLibraryImageLoader class])];
|
|
}
|
|
|
|
- (ALAssetsLibrary *)assetsLibrary
|
|
{
|
|
return [self.assetsLibraryImageLoader assetsLibrary];
|
|
}
|
|
|
|
@end
|
|
|
|
static dispatch_queue_t RCTAssetsLibraryImageLoaderQueue(void)
|
|
{
|
|
static dispatch_queue_t queue;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
queue = dispatch_queue_create("com.facebook.RCTAssetsLibraryImageLoader", DISPATCH_QUEUE_SERIAL);
|
|
});
|
|
|
|
return queue;
|
|
}
|
|
|
|
// Why use a custom scaling method? Greater efficiency, reduced memory overhead:
|
|
// http://www.mindsea.com/2012/12/downscaling-huge-alassets-without-fear-of-sigkill
|
|
|
|
static UIImage *RCTScaledImageForAsset(ALAssetRepresentation *representation, CGSize size, CGFloat scale, UIViewContentMode resizeMode, NSError **error)
|
|
{
|
|
NSUInteger length = (NSUInteger)representation.size;
|
|
NSMutableData *data = [NSMutableData dataWithLength:length];
|
|
if (![representation getBytes:data.mutableBytes
|
|
fromOffset:0
|
|
length:length
|
|
error:error]) {
|
|
return nil;
|
|
}
|
|
|
|
CGSize sourceSize = representation.dimensions;
|
|
CGSize targetSize = RCTTargetSize(sourceSize, representation.scale,
|
|
size, scale, resizeMode, NO);
|
|
|
|
NSDictionary *options = @{
|
|
(id)kCGImageSourceShouldAllowFloat: @YES,
|
|
(id)kCGImageSourceCreateThumbnailWithTransform: @YES,
|
|
(id)kCGImageSourceCreateThumbnailFromImageAlways: @YES,
|
|
(id)kCGImageSourceThumbnailMaxPixelSize: @(MAX(targetSize.width, targetSize.height) * scale)
|
|
};
|
|
|
|
CGImageSourceRef sourceRef = CGImageSourceCreateWithData((__bridge CFDataRef)data, nil);
|
|
CGImageRef imageRef = CGImageSourceCreateThumbnailAtIndex(sourceRef, 0, (__bridge CFDictionaryRef)options);
|
|
if (sourceRef) {
|
|
CFRelease(sourceRef);
|
|
}
|
|
|
|
if (imageRef) {
|
|
UIImage *image = [UIImage imageWithCGImage:imageRef scale:scale
|
|
orientation:UIImageOrientationUp];
|
|
CGImageRelease(imageRef);
|
|
return image;
|
|
}
|
|
|
|
return nil;
|
|
}
|