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)
- [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)
- [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
- [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 -
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
* notifications such as finished decoding and downloading an image.
@@ -137,6 +152,18 @@ NS_ASSUME_NONNULL_BEGIN
@protocol ASNetworkImageNodeDelegate <NSObject>
@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.
*

View File

@@ -56,6 +56,7 @@
unsigned int delegateDidFailWithError:1;
unsigned int delegateDidFinishDecoding:1;
unsigned int delegateDidLoadImage:1;
unsigned int delegateDidLoadImageWithInfo:1;
} _delegateFlags;
@@ -305,6 +306,7 @@
_delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)];
_delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)];
_delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)];
_delegateFlags.delegateDidLoadImageWithInfo = [delegate respondsToSelector:@selector(imageNode:didLoadImage:info:)];
}
- (id<ASNetworkImageNodeDelegate>)delegate
@@ -353,8 +355,18 @@
if (result) {
[self _locked_setCurrentImageQuality:1.0];
[self _locked__setImage:result];
_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;
}
}
@@ -688,14 +700,19 @@
[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__);
[delegate imageNode:self didLoadImage:self.image];
}
});
} else {
__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;
if (strongSelf == nil) {
@@ -732,7 +749,12 @@
strongSelf->_cacheUUID = 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__);
[delegate imageNode:strongSelf didLoadImage:strongSelf.image];
}
@@ -763,10 +785,12 @@
}
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 {
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];
}
} 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
completion:(ASImageCacherCompletion)completion
{
// We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice.
// If we're targeting the main queue and we're on the main thread, complete immediately.
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
completion(nil);
} else {
dispatch_async(callbackQueue, ^{
completion(nil);
});
}
[[self sharedPINRemoteImageManager] imageFromCacheWithURL:URL processorKey:nil options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult * _Nonnull result) {
[ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{
completion(result.image);
}];
}];
}
- (void)cachedImageWithURLs:(NSArray <NSURL *> *)URLs
@@ -256,51 +252,38 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
completion:(ASImageDownloaderCompletion)completion
{
PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) {
if (downloadProgress == nil) { return; }
/// If we're targeting the main queue and we're on the main thread, call immediately.
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
downloadProgress(completedBytes / (CGFloat)totalBytes);
} else {
dispatch_async(callbackQueue, ^{
downloadProgress(completedBytes / (CGFloat)totalBytes);
});
}
};
PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) {
/// If we're targeting the main queue and we're on the main thread, complete immediately.
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) {
if (downloadProgress == nil) { return; }
[ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{
downloadProgress(completedBytes / (CGFloat)totalBytes);
}];
};
PINRemoteImageManagerImageCompletion imageCompletion = ^(PINRemoteImageManagerResult * _Nonnull result) {
[ASPINRemoteImageDownloader _performWithCallbackQueue:callbackQueue work:^{
#if PIN_ANIMATED_AVAILABLE
if (result.alternativeRepresentation) {
completion(result.alternativeRepresentation, result.error, result.UUID);
} else {
completion(result.image, result.error, result.UUID);
}
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);
completion(result.image, result.error, result.UUID);
#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
});
}
};
return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs
options:PINRemoteImageManagerDownloadOptionsSkipDecode
progressImage:nil
progressDownload:progressDownload
completion:imageCompletion];
}];
};
// 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
options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache
progressImage:nil
progressDownload:progressDownload
completion:imageCompletion];
}
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
@@ -369,5 +352,29 @@ static ASPINRemoteImageDownloader *sharedDownloader = 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
#endif