From 1658a4c0806c0d866973f98061f9e6e7c94af769 Mon Sep 17 00:00:00 2001 From: "EUROPE\\laprosek" Date: Mon, 20 Aug 2018 16:06:18 -0700 Subject: [PATCH] Implement Image.queryCache on iOS (#18782) Summary: The API was available only on Android (with no mention to that effect in the docs, AFAICT). This commit adds a simple iOS implementation based on NSURLCache. It should be possible to query the decoded image cache as well to provide higher fidelity (i.e. "disk", "memory", "decoded") if the caller passes size, scale, etc. in addition to the image URL, but it's probably not worth the complexity. The assumption is that callers are interested in the durability rather than performance aspect of the returned information. Tested with RNTester on iPhone emulator. [IOS] [ENHANCEMENT] [Image] - Implemented queryCache Pull Request resolved: https://github.com/facebook/react-native/pull/18782 Differential Revision: D9411533 Pulled By: hramos fbshipit-source-id: b430263959bb5f9b8ed9e28bb0a95f8879df881a --- Libraries/Image/Image.ios.js | 12 ++++++++++++ Libraries/Image/RCTImageLoader.h | 8 ++++++++ Libraries/Image/RCTImageLoader.m | 19 +++++++++++++++++++ Libraries/Image/RCTImageViewManager.m | 7 +++++++ RNTester/js/ImageExample.js | 12 ++++++++++++ 5 files changed, 58 insertions(+) diff --git a/Libraries/Image/Image.ios.js b/Libraries/Image/Image.ios.js index fc082b098..c308e7008 100644 --- a/Libraries/Image/Image.ios.js +++ b/Libraries/Image/Image.ios.js @@ -45,11 +45,16 @@ function prefetch(url: string) { return ImageViewManager.prefetchImage(url); } +async function queryCache(urls: Array): Promise> { + return await ImageViewManager.queryCache(urls); +} + declare class ImageComponentType extends ReactNative.NativeComponent< ImagePropsType, > { static getSize: typeof getSize; static prefetch: typeof prefetch; + static queryCache: typeof queryCache; static resolveAssetSource: typeof resolveAssetSource; static propTypes: typeof ImageProps; } @@ -133,6 +138,13 @@ Image.getSize = getSize; */ Image.prefetch = prefetch; +/** + * Performs cache interrogation. + * + * See https://facebook.github.io/react-native/docs/image.html#querycache + */ +Image.queryCache = queryCache; + /** * Resolves an asset reference into an object. * diff --git a/Libraries/Image/RCTImageLoader.h b/Libraries/Image/RCTImageLoader.h index a0c6e798a..17552632a 100644 --- a/Libraries/Image/RCTImageLoader.h +++ b/Libraries/Image/RCTImageLoader.h @@ -129,6 +129,14 @@ typedef dispatch_block_t RCTImageLoaderCancellationBlock; */ - (RCTImageLoaderCancellationBlock)getImageSizeForURLRequest:(NSURLRequest *)imageURLRequest block:(void(^)(NSError *error, CGSize size))completionBlock; +/** + * Determines whether given image URLs are cached locally. The `requests` array is expected + * to contain objects convertible to NSURLRequest. The return value maps URLs to strings: + * "disk" for images known to be cached in non-volatile storage, "memory" for images known + * to be cached in memory. Dictionary items corresponding to images that are not known to be + * cached are simply missing. + */ +- (NSDictionary *)getImageCacheStatus:(NSArray *)requests; /** * Allows developers to set their own caching implementation for diff --git a/Libraries/Image/RCTImageLoader.m b/Libraries/Image/RCTImageLoader.m index 775e53e95..baaf9f0d6 100644 --- a/Libraries/Image/RCTImageLoader.m +++ b/Libraries/Image/RCTImageLoader.m @@ -779,6 +779,25 @@ static UIImage *RCTResizeImageIfNeeded(UIImage *image, completionBlock:completion]; } +- (NSDictionary *)getImageCacheStatus:(NSArray *)requests +{ + NSMutableDictionary *results = [NSMutableDictionary dictionary]; + for (id request in requests) { + NSURLRequest *urlRequest = [RCTConvert NSURLRequest:request]; + if (urlRequest) { + NSCachedURLResponse *cachedResponse = [NSURLCache.sharedURLCache cachedResponseForRequest:urlRequest]; + if (cachedResponse) { + if (cachedResponse.storagePolicy == NSURLCacheStorageAllowedInMemoryOnly) { + [results setObject:@"memory" forKey:urlRequest.URL.absoluteString]; + } else { + [results setObject:@"disk" forKey:urlRequest.URL.absoluteString]; + } + } + } + } + return results; +} + #pragma mark - RCTURLRequestHandler - (BOOL)canHandleRequest:(NSURLRequest *)request diff --git a/Libraries/Image/RCTImageViewManager.m b/Libraries/Image/RCTImageViewManager.m index bd382e4bf..b038c18d3 100644 --- a/Libraries/Image/RCTImageViewManager.m +++ b/Libraries/Image/RCTImageViewManager.m @@ -82,4 +82,11 @@ RCT_EXPORT_METHOD(prefetchImage:(NSURLRequest *)request }]; } +RCT_EXPORT_METHOD(queryCache:(NSArray *)requests + resolve:(RCTPromiseResolveBlock)resolve + reject:(RCTPromiseRejectBlock)reject) +{ + resolve([self.bridge.imageLoader getImageCacheStatus:requests]); +} + @end diff --git a/RNTester/js/ImageExample.js b/RNTester/js/ImageExample.js index 6672195e9..b8fc6bfe3 100644 --- a/RNTester/js/ImageExample.js +++ b/RNTester/js/ImageExample.js @@ -77,6 +77,18 @@ var NetworkImageCallbackExample = createReactClass({ this._loadEventFired( `✔ Prefetch OK (+${new Date() - mountTime}ms)`, ); + Image.queryCache([IMAGE_PREFETCH_URL]).then((map) => { + var result = map.get(IMAGE_PREFETCH_URL); + if (result) { + this._loadEventFired( + `✔ queryCache "${result}" (+${new Date() - mountTime}ms)`, + ); + } else { + this._loadEventFired( + `✘ queryCache (+${new Date() - mountTime}ms)`, + ); + } + }); }, error => { this._loadEventFired(