Have ASNetworkImageNode report whether images were cached or not (#639)

* Have ASNetworkImageNode report whether images were cached or not.

* Update changelog

* Add fileURL case
This commit is contained in:
Adlai Holler
2017-10-25 15:57:30 -07:00
committed by GitHub
parent 8317c11e42
commit af99ff5ef2
4 changed files with 119 additions and 58 deletions

View File

@@ -4,6 +4,7 @@
- [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy) - [ASCollectionView] Improve index space translation of Flow Layout Delegate methods. [Scott Goodson](https://github.com/appleguy)
- [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon) - [Animated Image] Adds support for animated WebP as well as improves GIF handling. [#605](https://github.com/TextureGroup/Texture/pull/605) [Garrett Moon](https://github.com/garrettmoon)
- [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon) - [ASCollectionView] Check if batch fetching is needed if batch fetching parameter has been changed. [#624](https://github.com/TextureGroup/Texture/pull/624) [Garrett Moon](https://github.com/garrettmoon)
- [ASNetworkImageNode] New delegate callback to tell the consumer whether the image was loaded from cache or download. [Adlai Holler](https://github.com/Adlai-Holler)
## 2.6 ## 2.6
- [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon)

View File

@@ -130,6 +130,21 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - #pragma mark -
typedef NS_ENUM(NSInteger, ASNetworkImageSource) {
ASNetworkImageSourceUnspecified = 0,
ASNetworkImageSourceSynchronousCache,
ASNetworkImageSourceAsynchronousCache,
ASNetworkImageSourceFileURL,
ASNetworkImageSourceDownload,
};
/// A struct that carries details about ASNetworkImageNode's image loads.
typedef struct {
/// The source from which the image was loaded.
ASNetworkImageSource imageSource;
} ASNetworkImageNodeDidLoadInfo;
/** /**
* The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to * The methods declared by the ASNetworkImageNodeDelegate protocol allow the adopting delegate to respond to
* notifications such as finished decoding and downloading an image. * notifications such as finished decoding and downloading an image.
@@ -137,6 +152,18 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ASNetworkImageNodeDelegate <NSObject> @protocol ASNetworkImageNodeDelegate <NSObject>
@optional @optional
/**
* Notification that the image node finished downloading an image, with additional info.
* If implemented, this method will be called instead of `imageNode:didLoadImage:`.
*
* @param imageNode The sender.
* @param image The newly-loaded image.
* @param info Misc information about the image load.
*
* @discussion Called on a background queue.
*/
- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image info:(ASNetworkImageNodeDidLoadInfo)info;
/** /**
* Notification that the image node finished downloading an image. * Notification that the image node finished downloading an image.
* *

View File

@@ -56,6 +56,7 @@
unsigned int delegateDidFailWithError:1; unsigned int delegateDidFailWithError:1;
unsigned int delegateDidFinishDecoding:1; unsigned int delegateDidFinishDecoding:1;
unsigned int delegateDidLoadImage:1; unsigned int delegateDidLoadImage:1;
unsigned int delegateDidLoadImageWithInfo:1;
} _delegateFlags; } _delegateFlags;
@@ -305,6 +306,7 @@
_delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)]; _delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)];
_delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]; _delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)];
_delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)]; _delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)];
_delegateFlags.delegateDidLoadImageWithInfo = [delegate respondsToSelector:@selector(imageNode:didLoadImage:info:)];
} }
- (id<ASNetworkImageNodeDelegate>)delegate - (id<ASNetworkImageNodeDelegate>)delegate
@@ -353,8 +355,18 @@
if (result) { if (result) {
[self _locked_setCurrentImageQuality:1.0]; [self _locked_setCurrentImageQuality:1.0];
[self _locked__setImage:result]; [self _locked__setImage:result];
_imageLoaded = YES; _imageLoaded = YES;
// Call out to the delegate.
if (_delegateFlags.delegateDidLoadImageWithInfo) {
ASDN::MutexUnlocker l(__instanceLock__);
ASNetworkImageNodeDidLoadInfo info = {};
info.imageSource = ASNetworkImageSourceSynchronousCache;
[_delegate imageNode:self didLoadImage:result info:info];
} else if (_delegateFlags.delegateDidLoadImage) {
ASDN::MutexUnlocker l(__instanceLock__);
[_delegate imageNode:self didLoadImage:result];
}
break; break;
} }
} }
@@ -688,14 +700,19 @@
[self _locked_setCurrentImageQuality:1.0]; [self _locked_setCurrentImageQuality:1.0];
if (_delegateFlags.delegateDidLoadImage) { if (_delegateFlags.delegateDidLoadImageWithInfo) {
ASDN::MutexUnlocker u(__instanceLock__);
ASNetworkImageNodeDidLoadInfo info = {};
info.imageSource = ASNetworkImageSourceFileURL;
[delegate imageNode:self didLoadImage:self.image info:info];
} else if (_delegateFlags.delegateDidLoadImage) {
ASDN::MutexUnlocker u(__instanceLock__); ASDN::MutexUnlocker u(__instanceLock__);
[delegate imageNode:self didLoadImage:self.image]; [delegate imageNode:self didLoadImage:self.image];
} }
}); });
} else { } else {
__weak __typeof__(self) weakSelf = self; __weak __typeof__(self) weakSelf = self;
auto finished = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier) { auto finished = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier, ASNetworkImageSource imageSource) {
__typeof__(self) strongSelf = weakSelf; __typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) { if (strongSelf == nil) {
@@ -732,7 +749,12 @@
strongSelf->_cacheUUID = nil; strongSelf->_cacheUUID = nil;
if (imageContainer != nil) { if (imageContainer != nil) {
if (strongSelf->_delegateFlags.delegateDidLoadImage) { if (strongSelf->_delegateFlags.delegateDidLoadImageWithInfo) {
ASDN::MutexUnlocker u(strongSelf->__instanceLock__);
ASNetworkImageNodeDidLoadInfo info = {};
info.imageSource = imageSource;
[delegate imageNode:strongSelf didLoadImage:strongSelf.image info:info];
} else if (strongSelf->_delegateFlags.delegateDidLoadImage) {
ASDN::MutexUnlocker u(strongSelf->__instanceLock__); ASDN::MutexUnlocker u(strongSelf->__instanceLock__);
[delegate imageNode:strongSelf didLoadImage:strongSelf.image]; [delegate imageNode:strongSelf didLoadImage:strongSelf.image];
} }
@@ -763,10 +785,12 @@
} }
if ([imageContainer asdk_image] == nil && _downloader != nil) { if ([imageContainer asdk_image] == nil && _downloader != nil) {
[self _downloadImageWithCompletion:finished]; [self _downloadImageWithCompletion:^(id<ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier) {
finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload);
}];
} else { } else {
as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs); as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs);
finished(imageContainer, nil, nil); finished(imageContainer, nil, nil, ASNetworkImageSourceAsynchronousCache);
} }
}; };
@@ -780,7 +804,9 @@
completion:completion]; completion:completion];
} }
} else { } else {
[self _downloadImageWithCompletion:finished]; [self _downloadImageWithCompletion:^(id<ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier) {
finished(imageContainer, error, downloadIdentifier, ASNetworkImageSourceDownload);
}];
} }
} }
} }

View File

@@ -202,15 +202,11 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
callbackQueue:(dispatch_queue_t)callbackQueue callbackQueue:(dispatch_queue_t)callbackQueue
completion:(ASImageCacherCompletion)completion completion:(ASImageCacherCompletion)completion
{ {
// We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice. [[self sharedPINRemoteImageManager] imageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult * _Nonnull result) {
// If we're targeting the main queue and we're on the main thread, complete immediately. [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { completion(result.image);
completion(nil); }];
} else { }];
dispatch_async(callbackQueue, ^{
completion(nil);
});
}
} }
- (void)cachedImageWithURLs:(NSArray <NSURL *> *)URLs - (void)cachedImageWithURLs:(NSArray <NSURL *> *)URLs
@@ -259,19 +255,13 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) {
if (downloadProgress == nil) { return; } if (downloadProgress == nil) { return; }
/// If we're targeting the main queue and we're on the main thread, call immediately. [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
downloadProgress(completedBytes / (CGFloat)totalBytes); downloadProgress(completedBytes / (CGFloat)totalBytes);
} else { }];
dispatch_async(callbackQueue, ^{
downloadProgress(completedBytes / (CGFloat)totalBytes);
});
}
}; };
PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) { PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) {
/// If we're targeting the main queue and we're on the main thread, complete immediately. [ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
#if PIN_ANIMATED_AVAILABLE #if PIN_ANIMATED_AVAILABLE
if (result.alternativeRepresentation) { if (result.alternativeRepresentation) {
completion(result.alternativeRepresentation, result.error, result.UUID); completion(result.alternativeRepresentation, result.error, result.UUID);
@@ -281,23 +271,16 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
#else #else
completion(result.image, result.error, result.UUID); completion(result.image, result.error, result.UUID);
#endif #endif
} else { }];
dispatch_async(callbackQueue, ^{
#if PIN_ANIMATED_AVAILABLE
if (result.alternativeRepresentation) {
completion(result.alternativeRepresentation, result.error, result.UUID);
} else {
completion(result.image, result.error, result.UUID);
}
#else
completion(result.image, result.error, result.UUID);
#endif
});
}
}; };
// add "IgnoreCache" option since we have a caching API so we already checked it, not worth checking again.
// PINRemoteImage is responsible for coalescing downloads, and even if it wasn't, the tiny probability of
// extra downloads isn't worth the effort of rechecking caches every single time. In order to provide
// feedback to the consumer about whether images are cached, we can't simply make the cache a no-op and
// check the cache as part of this download.
return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs
options:PINRemoteImageManagerDownloadOptionsSkipDecode options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache
progressImage:nil progressImage:nil
progressDownload:progressDownload progressDownload:progressDownload
completion:imageCompletion]; completion:imageCompletion];
@@ -369,5 +352,29 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
return nil; return nil;
} }
#pragma mark - Private
/**
* If on main thread and queue is main, perform now.
* If queue is nil, assert and perform now.
* Otherwise, dispatch async to queue.
*/
+ (void)_performWithCallbackQueue:(dispatch_queue_t)queue work:(void (^)())work
{
if (work == nil) {
// No need to assert here, really. We aren't expecting any feedback from this method.
return;
}
if (ASDisplayNodeThreadIsMain() && queue == dispatch_get_main_queue()) {
work();
} else if (queue == nil) {
ASDisplayNodeFailAssert(@"Callback queue should not be nil.");
work();
} else {
dispatch_async(queue, work);
}
}
@end @end
#endif #endif