Revert D8978844: [react-native][PR] Performance improvement for loading cached images on iOS

Differential Revision:
D8978844

Original commit changeset: 4b86043bc14c

fbshipit-source-id: fdf3ddd111c8c0dec6ddf2814a4e1e0ff58ef529
This commit is contained in:
Mehdi Mulani 2018-07-30 14:39:57 -07:00 committed by Facebook Github Bot
parent 7f0a4f72b4
commit affb135d62
3 changed files with 48 additions and 101 deletions

View File

@ -21,26 +21,22 @@
static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 1048576; // 1MB static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 1048576; // 1MB
static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale, static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale,
RCTResizeMode resizeMode) RCTResizeMode resizeMode, NSString *responseDate)
{ {
return [NSString stringWithFormat:@"%@|%g|%g|%g|%lld", return [NSString stringWithFormat:@"%@|%g|%g|%g|%lld|%@",
imageTag, size.width, size.height, scale, (long long)resizeMode]; imageTag, size.width, size.height, scale, (long long)resizeMode, responseDate];
} }
@implementation RCTImageCache @implementation RCTImageCache
{ {
NSOperationQueue *_imageDecodeQueue; NSOperationQueue *_imageDecodeQueue;
NSCache *_decodedImageCache; NSCache *_decodedImageCache;
NSMutableDictionary *_cacheStaleTimes;
NSDateFormatter *_headerDateFormatter;
} }
- (instancetype)init - (instancetype)init
{ {
_decodedImageCache = [NSCache new]; _decodedImageCache = [NSCache new];
_decodedImageCache.totalCostLimit = 5 * 1024 * 1024; // 5MB _decodedImageCache.totalCostLimit = 5 * 1024 * 1024; // 5MB
_cacheStaleTimes = [[NSMutableDictionary alloc] init];
[[NSNotificationCenter defaultCenter] addObserver:self [[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(clearCache) selector:@selector(clearCache)
@ -62,9 +58,6 @@ static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat sc
- (void)clearCache - (void)clearCache
{ {
[_decodedImageCache removeAllObjects]; [_decodedImageCache removeAllObjects];
@synchronized(_cacheStaleTimes) {
[_cacheStaleTimes removeAllObjects];
}
} }
- (void)addImageToCache:(UIImage *)image - (void)addImageToCache:(UIImage *)image
@ -85,19 +78,9 @@ static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat sc
size:(CGSize)size size:(CGSize)size
scale:(CGFloat)scale scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode resizeMode:(RCTResizeMode)resizeMode
responseDate:(NSString *)responseDate
{ {
NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode); NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode, responseDate);
@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]; return [_decodedImageCache objectForKey:cacheKey];
} }
@ -107,44 +90,9 @@ static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat sc
scale:(CGFloat)scale scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode resizeMode:(RCTResizeMode)resizeMode
responseDate:(NSString *)responseDate responseDate:(NSString *)responseDate
cacheControl:(NSString *)cacheControl
{ {
NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode); NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode, responseDate);
BOOL shouldCache = YES; return [self addImageToCache:image forKey:cacheKey];
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];
NSDate *originalDate = [self dateWithHeaderString:responseDate];
staleTime = [originalDate dateByAddingTimeInterval:(NSTimeInterval)seconds];
}
}
}
if (shouldCache) {
if (staleTime) {
@synchronized(_cacheStaleTimes) {
_cacheStaleTimes[cacheKey] = staleTime;
}
}
return [self addImageToCache:image forKey:cacheKey];
}
}
- (NSDate *)dateWithHeaderString:(NSString *)headerDateString {
if (_headerDateFormatter == nil) {
_headerDateFormatter = [[NSDateFormatter alloc] init];
_headerDateFormatter.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US_POSIX"];
_headerDateFormatter.dateFormat = @"EEE',' dd MMM yyyy HH':'mm':'ss 'GMT'";
_headerDateFormatter.timeZone = [NSTimeZone timeZoneForSecondsFromGMT:0];
}
return [_headerDateFormatter dateFromString:headerDateString];
} }
@end @end

View File

@ -24,15 +24,15 @@ typedef dispatch_block_t RCTImageLoaderCancellationBlock;
- (UIImage *)imageForUrl:(NSString *)url - (UIImage *)imageForUrl:(NSString *)url
size:(CGSize)size size:(CGSize)size
scale:(CGFloat)scale scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode; resizeMode:(RCTResizeMode)resizeMode
responseDate:(NSString *)responseDate;
- (void)addImageToCache:(UIImage *)image - (void)addImageToCache:(UIImage *)image
URL:(NSString *)url URL:(NSString *)url
size:(CGSize)size size:(CGSize)size
scale:(CGFloat)scale scale:(CGFloat)scale
resizeMode:(RCTResizeMode)resizeMode resizeMode:(RCTResizeMode)resizeMode
responseDate:(NSString *)responseDate responseDate:(NSString *)responseDate;
cacheControl:(NSString *)cacheControl;
@end @end

View File

@ -321,7 +321,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
resizeMode:(RCTResizeMode)resizeMode resizeMode:(RCTResizeMode)resizeMode
progressBlock:(RCTImageLoaderProgressBlock)progressHandler progressBlock:(RCTImageLoaderProgressBlock)progressHandler
partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler partialLoadBlock:(RCTImageLoaderPartialLoadBlock)partialLoadHandler
completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate, NSString *cacheControl))completionBlock completionBlock:(void (^)(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate))completionBlock
{ {
{ {
NSMutableURLRequest *mutableRequest = [request mutableCopy]; NSMutableURLRequest *mutableRequest = [request mutableCopy];
@ -344,15 +344,15 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ? BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ?
[loadHandler requiresScheduling] : YES; [loadHandler requiresScheduling] : YES;
BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ?
[loadHandler shouldCacheLoadedImages] : YES;
__block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO);
// TODO: Protect this variable shared between threads. // TODO: Protect this variable shared between threads.
__block dispatch_block_t cancelLoad = nil; __block dispatch_block_t cancelLoad = nil;
void (^completionHandler)(NSError *, id, NSString *, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate, NSString *cacheControl) { void (^completionHandler)(NSError *, id, NSString *) = ^(NSError *error, id imageOrData, NSString *fetchDate) {
cancelLoad = nil; cancelLoad = nil;
BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ?
[loadHandler shouldCacheLoadedImages] : YES;
// If we've received an image, we should try to set it synchronously, // If we've received an image, we should try to set it synchronously,
// if it's data, do decoding on a background thread. // if it's data, do decoding on a background thread.
if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) { if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) {
@ -360,11 +360,11 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
// expecting it, and may do expensive post-processing in the callback // expecting it, and may do expensive post-processing in the callback
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
if (!atomic_load(&cancelled)) { if (!atomic_load(&cancelled)) {
completionBlock(error, imageOrData, cacheResult, fetchDate, cacheControl); completionBlock(error, imageOrData, cacheResult, fetchDate);
} }
}); });
} else if (!atomic_load(&cancelled)) { } else if (!atomic_load(&cancelled)) {
completionBlock(error, imageOrData, cacheResult, fetchDate, cacheControl); completionBlock(error, imageOrData, cacheResult, fetchDate);
} }
}; };
@ -378,7 +378,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
progressHandler:progressHandler progressHandler:progressHandler
partialLoadHandler:partialLoadHandler partialLoadHandler:partialLoadHandler
completionHandler:^(NSError *error, UIImage *image){ completionHandler:^(NSError *error, UIImage *image){
completionHandler(error, image, nil, nil); completionHandler(error, image, nil);
}]; }];
} }
@ -402,25 +402,13 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
progressHandler:progressHandler progressHandler:progressHandler
partialLoadHandler:partialLoadHandler partialLoadHandler:partialLoadHandler
completionHandler:^(NSError *error, UIImage *image) { completionHandler:^(NSError *error, UIImage *image) {
completionHandler(error, image, nil, nil); completionHandler(error, image, nil);
}]; }];
} else { } else {
UIImage *image; // Use networking module to load image
if (cacheResult) { cancelLoad = [strongSelf _loadURLRequest:request
image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString progressBlock:progressHandler
size:size completionBlock:completionHandler];
scale:scale
resizeMode:resizeMode];
}
if (image) {
completionHandler(nil, image, nil, nil);
} else {
// Use networking module to load image
cancelLoad = [strongSelf _loadURLRequest:request
progressBlock:progressHandler
completionBlock:completionHandler];
}
} }
}); });
@ -439,7 +427,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
- (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request
progressBlock:(RCTImageLoaderProgressBlock)progressHandler progressBlock:(RCTImageLoaderProgressBlock)progressHandler
completionBlock:(void (^)(NSError *error, id imageOrData, NSString *fetchDate, NSString *cacheControl))completionHandler completionBlock:(void (^)(NSError *error, id imageOrData, NSString *fetchDate))completionHandler
{ {
// Check if networking module is available // Check if networking module is available
if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) { if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) {
@ -461,19 +449,18 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) { RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) {
// Check for system errors // Check for system errors
if (error) { if (error) {
completionHandler(error, nil, nil, nil); completionHandler(error, nil, nil);
return; return;
} else if (!response) { } else if (!response) {
completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, nil, nil); completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, nil);
return; return;
} else if (!data) { } else if (!data) {
completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, nil, nil); completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, nil);
return; return;
} }
// Check for http errors // Check for http errors
NSString *responseDate; NSString *responseDate;
NSString *cacheControl;
if ([response isKindOfClass:[NSHTTPURLResponse class]]) { if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode;
if (statusCode != 200) { if (statusCode != 200) {
@ -481,16 +468,15 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage}; NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage};
completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain
code:statusCode code:statusCode
userInfo:userInfo], nil, nil, nil); userInfo:userInfo], nil, nil);
return; return;
} }
responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"]; responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"];
cacheControl = ((NSHTTPURLResponse *)response).allHeaderFields[@"Cache-Control"];
} }
// Call handler // Call handler
completionHandler(nil, data, responseDate, cacheControl); completionHandler(nil, data, responseDate);
}; };
// Download image // Download image
@ -512,7 +498,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
} else { } else {
someError = RCTErrorWithMessage(@"Unknown image download error"); someError = RCTErrorWithMessage(@"Unknown image download error");
} }
completionHandler(someError, nil, nil, nil); completionHandler(someError, nil, nil);
[strongSelf dequeueTasks]; [strongSelf dequeueTasks];
return; return;
} }
@ -578,7 +564,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
}; };
__weak RCTImageLoader *weakSelf = self; __weak RCTImageLoader *weakSelf = self;
void (^completionHandler)(NSError *, id, BOOL, NSString *, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate, NSString *cacheControl) { void (^completionHandler)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
__typeof(self) strongSelf = weakSelf; __typeof(self) strongSelf = weakSelf;
if (atomic_load(&cancelled) || !strongSelf) { if (atomic_load(&cancelled) || !strongSelf) {
return; return;
@ -590,6 +576,20 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
return; return;
} }
// Check decoded image cache
if (cacheResult) {
UIImage *image = [[strongSelf imageCache] imageForUrl:imageURLRequest.URL.absoluteString
size:size
scale:scale
resizeMode:resizeMode
responseDate:fetchDate];
if (image) {
cancelLoad = nil;
completionBlock(nil, image);
return;
}
}
RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) { RCTImageLoaderCompletionBlock decodeCompletionHandler = ^(NSError *error_, UIImage *image) {
if (cacheResult && image) { if (cacheResult && image) {
// Store decoded image in cache // Store decoded image in cache
@ -598,8 +598,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
size:size size:size
scale:scale scale:scale
resizeMode:resizeMode resizeMode:resizeMode
responseDate:fetchDate responseDate:fetchDate];
cacheControl:cacheControl];
} }
cancelLoad = nil; cancelLoad = nil;
@ -733,7 +732,7 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image,
- (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest - (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest
block:(void(^)(NSError *error, CGSize size))callback block:(void(^)(NSError *error, CGSize size))callback
{ {
void (^completion)(NSError *, id, BOOL, NSString *, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate, NSString *cacheControl) { void (^completion)(NSError *, id, BOOL, NSString *) = ^(NSError *error, id imageOrData, BOOL cacheResult, NSString *fetchDate) {
CGSize size; CGSize size;
if ([imageOrData isKindOfClass:[NSData class]]) { if ([imageOrData isKindOfClass:[NSData class]]) {
NSDictionary *meta = RCTGetImageMetadata(imageOrData); NSDictionary *meta = RCTGetImageMetadata(imageOrData);