mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
61b013e7ad
Summary: Allow modifying iOS image cache limits. Currently the image cache imposes some strict limits: [NSCache.totalCostLimit](https://developer.apple.com/documentation/foundation/nscache/1407672-totalcostlimit?language=objc) of 20MB. Single Image Size Limit of 2MB (bitmap size). This may not be enough for applications that make heavy use of images. With this commit it is possible to fine tune them to the applications need. This can be set for example in the App Delegate with: ```objc RCTSetImageCacheLimits(4*1024*1024, 200*1024*1024); ``` This would increase the single image size limit to 4 MB and the total cost limit to 200 MB. ## Changelog [iOS] [Added] - Allow modifying iOS image cache limits Pull Request resolved: https://github.com/facebook/react-native/pull/33554 Test Plan: There is no easy way to test this except adding the above snippet and checking via break points that the new limits are used. Reviewed By: cipolleschi Differential Revision: D35485914 Pulled By: cortinico fbshipit-source-id: 646cf7cab5ea5258d0d0d0bce6383317e27e4445
170 lines
5.9 KiB
Objective-C
170 lines
5.9 KiB
Objective-C
/*
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*/
|
|
|
|
#import <React/RCTImageCache.h>
|
|
|
|
#import <objc/runtime.h>
|
|
|
|
#import <ImageIO/ImageIO.h>
|
|
|
|
#import <React/RCTConvert.h>
|
|
#import <React/RCTNetworking.h>
|
|
#import <React/RCTUtils.h>
|
|
#import <React/RCTResizeMode.h>
|
|
|
|
#import <React/RCTImageUtils.h>
|
|
|
|
static NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 2*1024*1024;
|
|
static NSUInteger RCTImageCacheTotalCostLimit = 20*1024*1024;
|
|
|
|
void RCTSetImageCacheLimits(NSUInteger maxCachableDecodedImageSizeInBytes, NSUInteger imageCacheTotalCostLimit) {
|
|
RCTMaxCachableDecodedImageSizeInBytes = maxCachableDecodedImageSizeInBytes;
|
|
RCTImageCacheTotalCostLimit = imageCacheTotalCostLimit;
|
|
}
|
|
|
|
static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale,
|
|
RCTResizeMode resizeMode)
|
|
{
|
|
return [NSString stringWithFormat:@"%@|%g|%g|%g|%lld",
|
|
imageTag, size.width, size.height, scale, (long long)resizeMode];
|
|
}
|
|
|
|
@implementation RCTImageCache
|
|
{
|
|
NSOperationQueue *_imageDecodeQueue;
|
|
NSCache *_decodedImageCache;
|
|
NSMutableDictionary *_cacheStaleTimes;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
if (self = [super init]) {
|
|
_decodedImageCache = [NSCache new];
|
|
_decodedImageCache.totalCostLimit = RCTImageCacheTotalCostLimit;
|
|
_cacheStaleTimes = [NSMutableDictionary new];
|
|
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(clearCache)
|
|
name:UIApplicationDidReceiveMemoryWarningNotification
|
|
object:nil];
|
|
[[NSNotificationCenter defaultCenter] addObserver:self
|
|
selector:@selector(clearCache)
|
|
name:UIApplicationWillResignActiveNotification
|
|
object:nil];
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)clearCache
|
|
{
|
|
[_decodedImageCache removeAllObjects];
|
|
@synchronized(_cacheStaleTimes) {
|
|
[_cacheStaleTimes removeAllObjects];
|
|
}
|
|
}
|
|
|
|
- (void)addImageToCache:(UIImage *)image
|
|
forKey:(NSString *)cacheKey
|
|
{
|
|
if (!image) {
|
|
return;
|
|
}
|
|
NSInteger bytes = image.reactDecodedImageBytes;
|
|
if (bytes <= RCTMaxCachableDecodedImageSizeInBytes) {
|
|
[self->_decodedImageCache setObject:image
|
|
forKey:cacheKey
|
|
cost:bytes];
|
|
}
|
|
}
|
|
|
|
- (UIImage *)imageForUrl:(NSString *)url
|
|
size:(CGSize)size
|
|
scale:(CGFloat)scale
|
|
resizeMode:(RCTResizeMode)resizeMode
|
|
{
|
|
NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode);
|
|
@synchronized(_cacheStaleTimes) {
|
|
id staleTime = _cacheStaleTimes[cacheKey];
|
|
if (staleTime) {
|
|
if ([[NSDate new] compare:(NSDate *)staleTime] == NSOrderedDescending) {
|
|
// cached image has expired, clear it out to make room for others
|
|
[_cacheStaleTimes removeObjectForKey:cacheKey];
|
|
[_decodedImageCache removeObjectForKey:cacheKey];
|
|
return nil;
|
|
}
|
|
}
|
|
}
|
|
return [_decodedImageCache objectForKey:cacheKey];
|
|
}
|
|
|
|
- (void)addImageToCache:(UIImage *)image
|
|
URL:(NSString *)url
|
|
size:(CGSize)size
|
|
scale:(CGFloat)scale
|
|
resizeMode:(RCTResizeMode)resizeMode
|
|
response:(NSURLResponse *)response
|
|
{
|
|
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
|
NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode);
|
|
BOOL shouldCache = YES;
|
|
NSString *responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
|
|
NSDate *originalDate = [self dateWithHeaderString:responseDate];
|
|
NSString *cacheControl = ((NSHTTPURLResponse *)response).allHeaderFields[@"Cache-Control"];
|
|
NSDate *staleTime;
|
|
NSArray<NSString *> *components = [cacheControl componentsSeparatedByString:@","];
|
|
for (NSString *component in components) {
|
|
if ([component containsString:@"no-cache"] || [component containsString:@"no-store"] || [component hasSuffix:@"max-age=0"]) {
|
|
shouldCache = NO;
|
|
break;
|
|
} else {
|
|
NSRange range = [component rangeOfString:@"max-age="];
|
|
if (range.location != NSNotFound) {
|
|
NSInteger seconds = [[component substringFromIndex:range.location + range.length] integerValue];
|
|
staleTime = [originalDate dateByAddingTimeInterval:(NSTimeInterval)seconds];
|
|
}
|
|
}
|
|
}
|
|
if (shouldCache) {
|
|
if (!staleTime && originalDate) {
|
|
NSString *expires = ((NSHTTPURLResponse *)response).allHeaderFields[@"Expires"];
|
|
NSString *lastModified = ((NSHTTPURLResponse *)response).allHeaderFields[@"Last-Modified"];
|
|
if (expires) {
|
|
staleTime = [self dateWithHeaderString:expires];
|
|
} else if (lastModified) {
|
|
NSDate *lastModifiedDate = [self dateWithHeaderString:lastModified];
|
|
if (lastModifiedDate) {
|
|
NSTimeInterval interval = [originalDate timeIntervalSinceDate:lastModifiedDate] / 10;
|
|
staleTime = [originalDate dateByAddingTimeInterval:interval];
|
|
}
|
|
}
|
|
}
|
|
if (staleTime) {
|
|
@synchronized(_cacheStaleTimes) {
|
|
_cacheStaleTimes[cacheKey] = staleTime;
|
|
}
|
|
}
|
|
return [self addImageToCache:image forKey:cacheKey];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (NSDate *)dateWithHeaderString:(NSString *)headerDateString {
|
|
static NSDateFormatter *formatter;
|
|
static dispatch_once_t onceToken;
|
|
dispatch_once(&onceToken, ^{
|
|
formatter = [NSDateFormatter new];
|
|
formatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
|
|
formatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
|
|
formatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
|
|
});
|
|
|
|
return [formatter dateFromString:headerDateString];
|
|
}
|
|
|
|
@end
|