Adds support for specifying a quality indexed array of URLs (#557)

* Add support for downloading a set of URLs on ASNetworkImageNode

* Should be building now;

* Remove old unused code

* Add a changelog message

* Bump PINRemoteImage

* Huy's comments
This commit is contained in:
Garrett Moon 2017-09-11 11:12:45 -07:00 committed by GitHub
parent 786963c6a9
commit 3c77d4a5da
7 changed files with 228 additions and 99 deletions

View File

@ -20,6 +20,7 @@
- Fixed a memory corruption issue in the ASImageNode display system. [Adlai Holler](https://github.com/Adlai-Holler) [#555](https://github.com/TextureGroup/Texture/pull/555) - Fixed a memory corruption issue in the ASImageNode display system. [Adlai Holler](https://github.com/Adlai-Holler) [#555](https://github.com/TextureGroup/Texture/pull/555)
- [Breaking] Rename ASCollectionGalleryLayoutSizeProviding to ASCollectionGalleryLayoutPropertiesProviding. Besides a fixed item size, it now can provide interitem and line spacings, as well as section inset [Huy Nguyen](https://github.com/nguyenhuy) [#496](https://github.com/TextureGroup/Texture/pull/496) [#533](https://github.com/TextureGroup/Texture/pull/533) - [Breaking] Rename ASCollectionGalleryLayoutSizeProviding to ASCollectionGalleryLayoutPropertiesProviding. Besides a fixed item size, it now can provide interitem and line spacings, as well as section inset [Huy Nguyen](https://github.com/nguyenhuy) [#496](https://github.com/TextureGroup/Texture/pull/496) [#533](https://github.com/TextureGroup/Texture/pull/533)
- Deprecate `-[ASDisplayNode displayWillStart]` in favor of `-displayWillStartAsynchronously:` [Huy Nguyen](https://github.com/nguyenhuy) [536](https://github.com/TextureGroup/Texture/pull/536) - Deprecate `-[ASDisplayNode displayWillStart]` in favor of `-displayWillStartAsynchronously:` [Huy Nguyen](https://github.com/nguyenhuy) [536](https://github.com/TextureGroup/Texture/pull/536)
- Add support for URLs on ASNetworkImageNode. [Garrett Moon](https://github.com/garrettmoon)
##2.4 ##2.4
- Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler) - Fix an issue where inserting/deleting sections could lead to inconsistent supplementary element behavior. [Adlai Holler](https://github.com/Adlai-Holler)

View File

@ -1,2 +1,2 @@
github "pinterest/PINRemoteImage" "3.0.0-beta.11" github "pinterest/PINRemoteImage" "3.0.0-beta.12"
github "pinterest/PINCache" "3.0.1-beta.5" github "pinterest/PINCache" "3.0.1-beta.5"

View File

@ -82,6 +82,15 @@ NS_ASSUME_NONNULL_BEGIN
*/ */
@property (nullable, nonatomic, strong, readwrite) NSURL *URL; @property (nullable, nonatomic, strong, readwrite) NSURL *URL;
/**
* An array of URLs of increasing cost to download.
*
* @discussion By setting an array of URLs, the image property of this node will be managed internally. This means previously
* directly set images to the image property will be cleared out and replaced by the placeholder (<defaultImage>) image
* while loading and the final image after the new image data was downloaded and processed.
*/
@property (nullable, nonatomic, strong, readwrite) NSArray <NSURL *> *URLs;
/** /**
* Download and display a new image. * Download and display a new image.
* *

View File

@ -39,7 +39,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
// Only access any of these with __instanceLock__. // Only access any of these with __instanceLock__.
__weak id<ASNetworkImageNodeDelegate> _delegate; __weak id<ASNetworkImageNodeDelegate> _delegate;
NSURL *_URL; NSArray *_URLs;
UIImage *_defaultImage; UIImage *_defaultImage;
NSUUID *_cacheUUID; NSUUID *_cacheUUID;
@ -68,6 +68,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
unsigned int downloaderImplementsSetPriority:1; unsigned int downloaderImplementsSetPriority:1;
unsigned int downloaderImplementsAnimatedImage:1; unsigned int downloaderImplementsAnimatedImage:1;
unsigned int downloaderImplementsCancelWithResume:1; unsigned int downloaderImplementsCancelWithResume:1;
unsigned int downloaderImplementsDownloadURLs:1;
} _downloaderFlags; } _downloaderFlags;
// Immutable and set on init only. We don't need to lock in this case. // Immutable and set on init only. We don't need to lock in this case.
@ -75,6 +76,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
struct { struct {
unsigned int cacheSupportsClearing:1; unsigned int cacheSupportsClearing:1;
unsigned int cacheSupportsSynchronousFetch:1; unsigned int cacheSupportsSynchronousFetch:1;
unsigned int cacheSupportsCachedURLs:1;
} _cacheFlags; } _cacheFlags;
} }
@ -96,9 +98,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];
_downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)];
_downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)]; _downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)];
_downloaderFlags.downloaderImplementsDownloadURLs = [downloader respondsToSelector:@selector(downloadImageWithURLs:callbackQueue:downloadProgress:completion:)];
_cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)];
_cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)];
_cacheFlags.cacheSupportsCachedURLs = [cache respondsToSelector:@selector(cachedImageWithURLs:callbackQueue:completion:)];
_shouldCacheImage = YES; _shouldCacheImage = YES;
_shouldRenderProgressImages = YES; _shouldRenderProgressImages = YES;
@ -136,8 +140,8 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
BOOL shouldCancelAndClear = imageWasSetExternally && (imageWasSetExternally != _imageWasSetExternally); BOOL shouldCancelAndClear = imageWasSetExternally && (imageWasSetExternally != _imageWasSetExternally);
_imageWasSetExternally = imageWasSetExternally; _imageWasSetExternally = imageWasSetExternally;
if (shouldCancelAndClear) { if (shouldCancelAndClear) {
ASDisplayNodeAssertNil(_URL, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first."); ASDisplayNodeAssert(_URLs == nil || _URLs.count == 0, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first.");
_URL = nil; _URLs = nil;
[self _locked_cancelDownloadAndClearImageWithResumePossibility:NO]; [self _locked_cancelDownloadAndClearImageWithResumePossibility:NO];
} }
@ -158,15 +162,38 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
- (void)setURL:(NSURL *)URL - (void)setURL:(NSURL *)URL
{ {
[self setURL:URL resetToDefault:YES]; if (URL) {
[self setURLs:@[URL]];
} else {
[self setURLs:nil];
}
} }
- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset - (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset
{
if (URL) {
[self setURLs:@[URL] resetToDefault:reset];
} else {
[self setURLs:nil resetToDefault:reset];
}
}
- (NSURL *)URL
{
return [self.URLs lastObject];
}
- (void)setURLs:(NSArray <NSURL *> *)URLs
{
[self setURLs:URLs resetToDefault:YES];
}
- (void)setURLs:(NSArray <NSURL *> *)URLs resetToDefault:(BOOL)reset
{ {
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (ASObjectIsEqual(URL, _URL)) { if (ASObjectIsEqual(URLs, _URLs)) {
return; return;
} }
@ -175,25 +202,25 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_imageWasSetExternally = NO; _imageWasSetExternally = NO;
[self _locked_cancelImageDownloadWithResumePossibility:NO]; [self _locked_cancelImageDownloadWithResumePossibility:NO];
_imageLoaded = NO; _imageLoaded = NO;
_URL = URL; _URLs = URLs;
BOOL hasURL = (_URL == nil); BOOL hasURL = (_URLs.count == 0);
if (reset || hasURL) { if (reset || hasURL) {
[self _locked_setCurrentImageQuality:(hasURL ? 0.0 : 1.0)]; [self _locked_setCurrentImageQuality:(hasURL ? 0.0 : 1.0)];
[self _locked__setImage:_defaultImage]; [self _locked__setImage:_defaultImage];
} }
} }
[self setNeedsPreload]; [self setNeedsPreload];
} }
- (NSURL *)URL - (NSArray <NSURL *>*)URLs
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
return _URL; return _URLs;
} }
- (void)setDefaultImage:(UIImage *)defaultImage - (void)setDefaultImage:(UIImage *)defaultImage
@ -212,7 +239,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_defaultImage = defaultImage; _defaultImage = defaultImage;
if (!_imageLoaded) { if (!_imageLoaded) {
[self _locked_setCurrentImageQuality:((_URL == nil) ? 0.0 : 1.0)]; [self _locked_setCurrentImageQuality:((_URLs.count == 0) ? 0.0 : 1.0)];
[self _locked__setImage:defaultImage]; [self _locked__setImage:defaultImage];
} }
@ -310,7 +337,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
- (BOOL)placeholderShouldPersist - (BOOL)placeholderShouldPersist
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
return (self.image == nil && self.animatedImage == nil && _URL != nil); return (self.image == nil && self.animatedImage == nil && _URLs.count != 0);
} }
/* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary /* displayWillStartAsynchronously: in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary
@ -322,13 +349,16 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) { if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) { if (_imageLoaded == NO && _URLs.count > 0 && _downloadIdentifier == nil) {
UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image]; for (NSURL *url in [_URLs reverseObjectEnumerator]) {
if (result) { UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image];
[self _locked_setCurrentImageQuality:1.0]; if (result) {
[self _locked__setImage:result]; [self _locked_setCurrentImageQuality:1.0];
[self _locked__setImage:result];
_imageLoaded = YES;
_imageLoaded = YES;
break;
}
} }
} }
} }
@ -510,9 +540,11 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_imageLoaded = NO; _imageLoaded = NO;
if (_cacheFlags.cacheSupportsClearing) { if (_cacheFlags.cacheSupportsClearing) {
if (_URL != nil) { if (_URLs.count != 0) {
as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URL); as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URLs);
[_cache clearFetchedImageFromCacheWithURL:_URL]; for (NSURL *url in _URLs) {
[_cache clearFetchedImageFromCacheWithURL:url];
}
} }
} }
} }
@ -546,7 +578,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
- (void)_downloadImageWithCompletion:(void (^)(id <ASImageContainerProtocol> imageContainer, NSError*, id downloadIdentifier))finished - (void)_downloadImageWithCompletion:(void (^)(id <ASImageContainerProtocol> imageContainer, NSError*, id downloadIdentifier))finished
{ {
ASPerformBlockOnBackgroundThread(^{ ASPerformBlockOnBackgroundThread(^{
NSURL *url; NSArray <NSURL *> *urls;
id downloadIdentifier; id downloadIdentifier;
BOOL cancelAndReattempt = NO; BOOL cancelAndReattempt = NO;
@ -555,23 +587,34 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
// it and try again. // it and try again.
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
url = _URL; urls = _URLs;
} }
if (_downloaderFlags.downloaderImplementsDownloadURLs) {
downloadIdentifier = [_downloader downloadImageWithURL:url downloadIdentifier = [_downloader downloadImageWithURLs:urls
callbackQueue:dispatch_get_main_queue() callbackQueue:dispatch_get_main_queue()
downloadProgress:NULL downloadProgress:NULL
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) { completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) {
if (finished != NULL) { if (finished != NULL) {
finished(imageContainer, error, downloadIdentifier); finished(imageContainer, error, downloadIdentifier);
} }
}]; }];
} else {
downloadIdentifier = [_downloader downloadImageWithURL:[urls lastObject]
callbackQueue:dispatch_get_main_queue()
downloadProgress:NULL
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) {
if (finished != NULL) {
finished(imageContainer, error, downloadIdentifier);
}
}];
}
as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url);
{ {
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
if (ASObjectIsEqual(_URL, url)) { if (ASObjectIsEqual(_URLs, urls)) {
// The download we kicked off is correct, no need to do any more work. // The download we kicked off is correct, no need to do any more work.
_downloadIdentifier = downloadIdentifier; _downloadIdentifier = downloadIdentifier;
} else { } else {
@ -600,34 +643,36 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
__weak id<ASNetworkImageNodeDelegate> delegate = _delegate; __weak id<ASNetworkImageNodeDelegate> delegate = _delegate;
BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData; BOOL delegateDidStartFetchingData = _delegateFlags.delegateDidStartFetchingData;
BOOL isImageLoaded = _imageLoaded; BOOL isImageLoaded = _imageLoaded;
NSURL *URL = _URL; NSArray <NSURL *>*URLs = _URLs;
id currentDownloadIdentifier = _downloadIdentifier; id currentDownloadIdentifier = _downloadIdentifier;
__instanceLock__.unlock(); __instanceLock__.unlock();
if (!isImageLoaded && URL != nil && currentDownloadIdentifier == nil) { if (!isImageLoaded && URLs.count > 0 && currentDownloadIdentifier == nil) {
if (delegateDidStartFetchingData) { if (delegateDidStartFetchingData) {
[delegate imageNodeDidStartFetchingData:self]; [delegate imageNodeDidStartFetchingData:self];
} }
if (URL.isFileURL) { // We only support file URLs if there is one URL currently
if (URLs.count == 1 && [URLs lastObject].isFileURL) {
dispatch_async(dispatch_get_main_queue(), ^{ dispatch_async(dispatch_get_main_queue(), ^{
ASDN::MutexLocker l(__instanceLock__); ASDN::MutexLocker l(__instanceLock__);
// Bail out if not the same URL anymore // Bail out if not the same URL anymore
if (!ASObjectIsEqual(URL, _URL)) { if (!ASObjectIsEqual(URLs, _URLs)) {
return; return;
} }
NSURL *URL = [URLs lastObject];
if (_shouldCacheImage) { if (_shouldCacheImage) {
[self _locked__setImage:[UIImage imageNamed:_URL.path.lastPathComponent]]; [self _locked__setImage:[UIImage imageNamed:URL.path.lastPathComponent]];
} else { } else {
// First try to load the path directly, for efficiency assuming a developer who // First try to load the path directly, for efficiency assuming a developer who
// doesn't want caching is trying to be as minimal as possible. // doesn't want caching is trying to be as minimal as possible.
UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path]; UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:URL.path];
if (nonAnimatedImage == nil) { if (nonAnimatedImage == nil) {
// If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the // If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the
// extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage. // extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage.
NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil]; NSString *filename = [[NSBundle mainBundle] pathForResource:URL.path.lastPathComponent ofType:nil];
if (filename != nil) { if (filename != nil) {
nonAnimatedImage = [UIImage imageWithContentsOfFile:filename]; nonAnimatedImage = [UIImage imageWithContentsOfFile:filename];
} }
@ -636,7 +681,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
// If the file may be an animated gif and then created an animated image. // If the file may be an animated gif and then created an animated image.
id<ASAnimatedImageProtocol> animatedImage = nil; id<ASAnimatedImageProtocol> animatedImage = nil;
if (_downloaderFlags.downloaderImplementsAnimatedImage) { if (_downloaderFlags.downloaderImplementsAnimatedImage) {
NSData *data = [NSData dataWithContentsOfURL:_URL]; NSData *data = [NSData dataWithContentsOfURL:URL];
if (data != nil) { if (data != nil) {
animatedImage = [_downloader animatedImageWithData:data]; animatedImage = [_downloader animatedImageWithData:data];
@ -671,7 +716,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
return; return;
} }
as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs);
// Grab the lock for the rest of the block // Grab the lock for the rest of the block
ASDN::MutexLocker l(strongSelf->__instanceLock__); ASDN::MutexLocker l(strongSelf->__instanceLock__);
@ -714,26 +759,35 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
_cacheUUID = cacheUUID; _cacheUUID = cacheUUID;
__instanceLock__.unlock(); __instanceLock__.unlock();
as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL); as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ urls: %@", self, URLs);
[_cache cachedImageWithURL:URL
callbackQueue:dispatch_get_main_queue() ASImageCacherCompletion completion = ^(id <ASImageContainerProtocol> imageContainer) {
completion:^(id <ASImageContainerProtocol> imageContainer) { // If the cache UUID changed, that means this request was cancelled.
// If the cache UUID changed, that means this request was cancelled. __instanceLock__.lock();
__instanceLock__.lock(); NSUUID *currentCacheUUID = _cacheUUID;
NSUUID *currentCacheUUID = _cacheUUID; __instanceLock__.unlock();
__instanceLock__.unlock();
if (!ASObjectIsEqual(currentCacheUUID, cacheUUID)) {
if (!ASObjectIsEqual(currentCacheUUID, cacheUUID)) { return;
return; }
}
if ([imageContainer asdk_image] == nil && _downloader != nil) {
if ([imageContainer asdk_image] == nil && _downloader != nil) { [self _downloadImageWithCompletion:finished];
[self _downloadImageWithCompletion:finished]; } else {
} else { as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ urls: %@", self, [imageContainer asdk_image], URLs);
as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL); finished(imageContainer, nil, nil);
finished(imageContainer, nil, nil); }
} };
}];
if (_cacheFlags.cacheSupportsCachedURLs) {
[_cache cachedImageWithURLs:URLs
callbackQueue:dispatch_get_main_queue()
completion:completion];
} else {
[_cache cachedImageWithURL:[URLs lastObject]
callbackQueue:dispatch_get_main_queue()
completion:completion];
}
} else { } else {
[self _downloadImageWithCompletion:finished]; [self _downloadImageWithCompletion:finished];
} }

View File

@ -37,7 +37,7 @@ typedef void(^ASImageCacherCompletion)(id <ASImageContainerProtocol> _Nullable i
@param URL The URL of the image to retrieve from the cache. @param URL The URL of the image to retrieve from the cache.
@param callbackQueue The queue to call `completion` on. @param callbackQueue The queue to call `completion` on.
@param completion The block to be called when the cache has either hit or missed. @param completion The block to be called when the cache has either hit or missed.
@discussion If `URL` is nil, `completion` will be invoked immediately with a nil image. This method should not block @discussion If `URL` is nil, `completion` should be invoked immediately with a nil image. This method should not block
the calling thread as it is likely to be called from the main thread. the calling thread as it is likely to be called from the main thread.
*/ */
- (void)cachedImageWithURL:(NSURL *)URL - (void)cachedImageWithURL:(NSURL *)URL
@ -66,6 +66,19 @@ typedef void(^ASImageCacherCompletion)(id <ASImageContainerProtocol> _Nullable i
*/ */
- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL; - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL;
/**
@abstract Attempts to fetch an image with the given URLs from the cache in reverse order.
@param URLs The URLs of the image to retrieve from the cache.
@param callbackQueue The queue to call `completion` on.
@param completion The block to be called when the cache has either hit or missed.
@discussion If `URLs` is nil or empty, `completion` should be invoked immediately with a nil image. This method should not block
the calling thread as it is likely to be called from the main thread.
@see downloadImageWithURLs:callbackQueue:downloadProgress:completion:
*/
- (void)cachedImageWithURLs:(NSArray <NSURL *> *)URLs
callbackQueue:(dispatch_queue_t)callbackQueue
completion:(ASImageCacherCompletion)completion;
@end @end
/** /**
@ -154,6 +167,21 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) {
- (void)setPriority:(ASImageDownloaderPriority)priority - (void)setPriority:(ASImageDownloaderPriority)priority
withDownloadIdentifier:(id)downloadIdentifier; withDownloadIdentifier:(id)downloadIdentifier;
/**
@abstract Downloads an image from a list of URLs depending on previously observed network speed conditions.
@param URLs An array of URLs ordered by the cost of downloading them, the URL at index 0 being the lowest cost.
@param callbackQueue The queue to call `downloadProgressBlock` and `completion` on.
@param downloadProgress The block to be invoked when the download of `URL` progresses.
@param completion The block to be invoked when the download has completed, or has failed.
@discussion This method is likely to be called on the main thread, so any custom implementations should make sure to background any expensive download operations.
@result An opaque identifier to be used in canceling the download, via `cancelImageDownloadForIdentifier:`. You must
retain the identifier if you wish to use it later.
*/
- (nullable id)downloadImageWithURLs:(NSArray <NSURL *> *)URLs
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
completion:(ASImageDownloaderCompletion)completion;
@end @end
@protocol ASAnimatedImageProtocol <NSObject> @protocol ASAnimatedImageProtocol <NSObject>

View File

@ -196,6 +196,23 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
} }
} }
- (void)cachedImageWithURLs:(NSArray <NSURL *> *)URLs
callbackQueue:(dispatch_queue_t)callbackQueue
completion:(ASImageCacherCompletion)completion
{
[self cachedImageWithURL:[URLs lastObject]
callbackQueue:callbackQueue
completion:^(id<ASImageContainerProtocol> _Nullable imageFromCache) {
if (imageFromCache.asdk_image == nil && URLs.count > 1) {
[self cachedImageWithURLs:[URLs subarrayWithRange:NSMakeRange(0, URLs.count - 1)]
callbackQueue:callbackQueue
completion:completion];
} else {
completion(imageFromCache);
}
}];
}
- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL - (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL
{ {
if ([self sharedImageManagerSupportsMemoryRemoval]) { if ([self sharedImageManagerSupportsMemoryRemoval]) {
@ -210,43 +227,63 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil;
downloadProgress:(ASImageDownloaderProgress)downloadProgress downloadProgress:(ASImageDownloaderProgress)downloadProgress
completion:(ASImageDownloaderCompletion)completion; completion:(ASImageDownloaderCompletion)completion;
{ {
return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode progressDownload:^(int64_t completedBytes, int64_t totalBytes) { NSArray <NSURL *>*URLs = nil;
if (downloadProgress == nil) { return; } if (URL) {
URLs = @[URL];
}
return [self downloadImageWithURLs:URLs callbackQueue:callbackQueue downloadProgress:downloadProgress completion:completion];
}
/// If we're targeting the main queue and we're on the main thread, call immediately. - (nullable id)downloadImageWithURLs:(NSArray <NSURL *> *)URLs
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgress(completedBytes / (CGFloat)totalBytes); downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
} else { completion:(ASImageDownloaderCompletion)completion
dispatch_async(callbackQueue, ^{ {
downloadProgress(completedBytes / (CGFloat)totalBytes); PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) {
}); if (downloadProgress == nil) { return; }
}
} completion:^(PINRemoteImageManagerResult * _Nonnull result) { /// If we're targeting the main queue and we're on the main thread, call immediately.
/// If we're targeting the main queue and we're on the main thread, complete immediately. if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) {
if (ASDisplayNodeThreadIsMain() && callbackQueue == dispatch_get_main_queue()) { downloadProgress(completedBytes / (CGFloat)totalBytes);
#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
} else {
dispatch_async(callbackQueue, ^{
#if PIN_ANIMATED_AVAILABLE
if (result.alternativeRepresentation) {
completion(result.alternativeRepresentation, result.error, result.UUID);
} else { } else {
completion(result.image, result.error, result.UUID); 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()) {
#if PIN_ANIMATED_AVAILABLE
if (result.alternativeRepresentation) {
completion(result.alternativeRepresentation, result.error, result.UUID);
} else {
completion(result.image, result.error, result.UUID);
}
#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
});
}
};
return [[self sharedPINRemoteImageManager] downloadImageWithURLs:URLs
options:PINRemoteImageManagerDownloadOptionsSkipDecode
progressImage:nil
progressDownload:progressDownload
completion:imageCompletion];
} }
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier

View File

@ -45,7 +45,7 @@ Pod::Spec.new do |spec|
end end
spec.subspec 'PINRemoteImage' do |pin| spec.subspec 'PINRemoteImage' do |pin|
pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.11' pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.12'
pin.dependency 'PINRemoteImage/PINCache' pin.dependency 'PINRemoteImage/PINCache'
pin.dependency 'Texture/Core' pin.dependency 'Texture/Core'
end end