diff --git a/Libraries/Image/RCTImageCache.m b/Libraries/Image/RCTImageCache.m index 22b65d03612c61..5fc52bc02b4535 100644 --- a/Libraries/Image/RCTImageCache.m +++ b/Libraries/Image/RCTImageCache.m @@ -21,26 +21,22 @@ static const NSUInteger RCTMaxCachableDecodedImageSizeInBytes = 2097152; // 2 MB static NSString *RCTCacheKeyForImage(NSString *imageTag, CGSize size, CGFloat scale, - RCTResizeMode resizeMode) + RCTResizeMode resizeMode, NSString *responseDate) { - return [NSString stringWithFormat:@"%@|%g|%g|%g|%lld", - imageTag, size.width, size.height, scale, (long long)resizeMode]; + return [NSString stringWithFormat:@"%@|%g|%g|%g|%lld|%@", + imageTag, size.width, size.height, scale, (long long)resizeMode, responseDate]; } @implementation RCTImageCache { NSOperationQueue *_imageDecodeQueue; NSCache *_decodedImageCache; - NSMutableDictionary *_cacheStaleTimes; - - NSDateFormatter *_headerDateFormatter; } - (instancetype)init { _decodedImageCache = [NSCache new]; _decodedImageCache.totalCostLimit = 20 * 1024 * 1024; // 20 MB - _cacheStaleTimes = [[NSMutableDictionary alloc] init]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(clearCache) @@ -50,7 +46,7 @@ - (instancetype)init selector:@selector(clearCache) name:UIApplicationWillResignActiveNotification object:nil]; - + return self; } @@ -62,9 +58,6 @@ - (void)dealloc - (void)clearCache { [_decodedImageCache removeAllObjects]; - @synchronized(_cacheStaleTimes) { - [_cacheStaleTimes removeAllObjects]; - } } - (void)addImageToCache:(UIImage *)image @@ -85,19 +78,9 @@ - (UIImage *)imageForUrl:(NSString *)url size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode + responseDate:(NSString *)responseDate { - 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; - } - } - } + NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode, responseDate); return [_decodedImageCache objectForKey:cacheKey]; } @@ -107,44 +90,9 @@ - (void)addImageToCache:(UIImage *)image scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode responseDate:(NSString *)responseDate - cacheControl:(NSString *)cacheControl { - NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode); - BOOL shouldCache = YES; - NSDate *staleTime; - NSArray *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]; + NSString *cacheKey = RCTCacheKeyForImage(url, size, scale, resizeMode, responseDate); + return [self addImageToCache:image forKey:cacheKey]; } @end diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index ffc2dfb30b98a4..a0c6e798a8a960 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -24,15 +24,15 @@ typedef dispatch_block_t RCTImageLoaderCancellationBlock; - (UIImage *)imageForUrl:(NSString *)url size:(CGSize)size scale:(CGFloat)scale - resizeMode:(RCTResizeMode)resizeMode; + resizeMode:(RCTResizeMode)resizeMode + responseDate:(NSString *)responseDate; - (void)addImageToCache:(UIImage *)image URL:(NSString *)url size:(CGSize)size scale:(CGFloat)scale resizeMode:(RCTResizeMode)resizeMode - responseDate:(NSString *)responseDate - cacheControl:(NSString *)cacheControl; + responseDate:(NSString *)responseDate; @end diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 0cf864c836fd0a..775e53e95f7a08 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -321,7 +321,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest resizeMode:(RCTResizeMode)resizeMode progressBlock:(RCTImageLoaderProgressBlock)progressHandler 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]; @@ -344,15 +344,15 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest BOOL requiresScheduling = [loadHandler respondsToSelector:@selector(requiresScheduling)] ? [loadHandler requiresScheduling] : YES; - BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? - [loadHandler shouldCacheLoadedImages] : YES; - __block atomic_bool cancelled = ATOMIC_VAR_INIT(NO); // TODO: Protect this variable shared between threads. __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; + BOOL cacheResult = [loadHandler respondsToSelector:@selector(shouldCacheLoadedImages)] ? + [loadHandler shouldCacheLoadedImages] : YES; + // If we've received an image, we should try to set it synchronously, // if it's data, do decoding on a background thread. if (RCTIsMainQueue() && ![imageOrData isKindOfClass:[UIImage class]]) { @@ -360,11 +360,11 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest // expecting it, and may do expensive post-processing in the callback dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ if (!atomic_load(&cancelled)) { - completionBlock(error, imageOrData, cacheResult, fetchDate, cacheControl); + completionBlock(error, imageOrData, cacheResult, fetchDate); } }); } else if (!atomic_load(&cancelled)) { - completionBlock(error, imageOrData, cacheResult, fetchDate, cacheControl); + completionBlock(error, imageOrData, cacheResult, fetchDate); } }; @@ -378,7 +378,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest progressHandler:progressHandler partialLoadHandler:partialLoadHandler completionHandler:^(NSError *error, UIImage *image){ - completionHandler(error, image, nil, nil); + completionHandler(error, image, nil); }]; } @@ -402,25 +402,13 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest progressHandler:progressHandler partialLoadHandler:partialLoadHandler completionHandler:^(NSError *error, UIImage *image) { - completionHandler(error, image, nil, nil); + completionHandler(error, image, nil); }]; } else { - UIImage *image; - if (cacheResult) { - image = [[strongSelf imageCache] imageForUrl:request.URL.absoluteString - size:size - 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]; - } + // Use networking module to load image + cancelLoad = [strongSelf _loadURLRequest:request + progressBlock:progressHandler + completionBlock:completionHandler]; } }); @@ -439,7 +427,7 @@ - (RCTImageLoaderCancellationBlock)_loadImageOrDataWithURLRequest:(NSURLRequest - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request 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 if (RCT_DEBUG && ![_bridge respondsToSelector:@selector(networking)]) { @@ -461,19 +449,18 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request RCTURLRequestCompletionBlock processResponse = ^(NSURLResponse *response, NSData *data, NSError *error) { // Check for system errors if (error) { - completionHandler(error, nil, nil, nil); + completionHandler(error, nil, nil); return; } else if (!response) { - completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, nil, nil); + completionHandler(RCTErrorWithMessage(@"Response metadata error"), nil, nil); return; } else if (!data) { - completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, nil, nil); + completionHandler(RCTErrorWithMessage(@"Unknown image download error"), nil, nil); return; } // Check for http errors NSString *responseDate; - NSString *cacheControl; if ([response isKindOfClass:[NSHTTPURLResponse class]]) { NSInteger statusCode = ((NSHTTPURLResponse *)response).statusCode; if (statusCode != 200) { @@ -481,16 +468,15 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request NSDictionary *userInfo = @{NSLocalizedDescriptionKey: errorMessage}; completionHandler([[NSError alloc] initWithDomain:NSURLErrorDomain code:statusCode - userInfo:userInfo], nil, nil, nil); + userInfo:userInfo], nil, nil); return; } responseDate = ((NSHTTPURLResponse *)response).allHeaderFields[@"Date"]; - cacheControl = ((NSHTTPURLResponse *)response).allHeaderFields[@"Cache-Control"]; } // Call handler - completionHandler(nil, data, responseDate, cacheControl); + completionHandler(nil, data, responseDate); }; // Download image @@ -512,7 +498,7 @@ - (RCTImageLoaderCancellationBlock)_loadURLRequest:(NSURLRequest *)request } else { someError = RCTErrorWithMessage(@"Unknown image download error"); } - completionHandler(someError, nil, nil, nil); + completionHandler(someError, nil, nil); [strongSelf dequeueTasks]; return; } @@ -578,7 +564,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image }; __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; if (atomic_load(&cancelled) || !strongSelf) { return; @@ -590,6 +576,20 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image 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) { if (cacheResult && image) { // Store decoded image in cache @@ -598,8 +598,7 @@ - (RCTImageLoaderCancellationBlock)loadImageWithURLRequest:(NSURLRequest *)image size:size scale:scale resizeMode:resizeMode - responseDate:fetchDate - cacheControl:cacheControl]; + responseDate:fetchDate]; } cancelLoad = nil; @@ -733,7 +732,7 @@ - (RCTImageLoaderCancellationBlock)decodeImageData:(NSData *)data - (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest 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; if ([imageOrData isKindOfClass:[NSData class]]) { NSDictionary *meta = RCTGetImageMetadata(imageOrData);