Merge pull request #1209 from garrettmoon/addPINRemoteImageDownloader

[ASImageNode] Add PINRemoteImage as the default image downloader for both Network & Multiplex nodes.
This commit is contained in:
appleguy
2016-02-10 15:11:33 -08:00
9 changed files with 541 additions and 117 deletions

View File

@@ -37,6 +37,9 @@ Pod::Spec.new do |spec|
'AsyncDisplayKit/Details/ASDealloc2MainObject.h',
'AsyncDisplayKit/Details/ASDealloc2MainObject.m',
]
#Subspecs
spec.subspec 'ASDealloc2MainObject' do |mrr|
mrr.requires_arc = false
mrr.source_files = [
@@ -45,6 +48,15 @@ Pod::Spec.new do |spec|
'AsyncDisplayKit/Details/ASDealloc2MainObject.m',
]
end
spec.subspec 'PINRemoteImage' do |pin|
pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' }
pin.dependency 'PINRemoteImage/iOS', '>= 2'
pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject'
end
# Include optional FLAnimatedImage module
spec.default_subspec = 'PINRemoteImage'
spec.social_media_url = 'https://twitter.com/fbOpenSource'
spec.library = 'c++'

View File

@@ -26,6 +26,12 @@
#error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK.
#endif
#if PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.h"
#else
#import "ASBasicImageDownloader.h"
#endif
NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain";
static NSString *const kAssetsLibraryURLScheme = @"assets-library";
@@ -42,8 +48,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
{
@private
// Core.
id<ASImageCacheProtocol> _cache;
id<ASImageDownloaderProtocol> _downloader;
id<ASImageCacheProtocol, ASImageCacheProtocolDeprecated> _cache;
id<ASImageDownloaderProtocol, ASImageDownloaderProtocolDeprecated> _downloader;
__weak id<ASMultiplexImageNodeDelegate> _delegate;
struct {
@@ -72,7 +78,15 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
__weak NSOperation *_phImageRequestOperation;
// Networking.
ASDN::Mutex _downloadIdentifierLock;
id _downloadIdentifier;
//set on init only
BOOL _downloaderSupportsNewProtocol;
BOOL _downloaderImplementsSetProgress;
BOOL _downloaderImplementsSetPriority;
BOOL _cacheSupportsNewProtocol;
BOOL _cacheSupportsClearing;
}
//! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h.
@@ -143,13 +157,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
*/
- (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL completion:(void (^)(UIImage *image, NSError *error))completionBlock;
/**
@abstract Returns a Boolean value indicating whether the downloaded image should be removed when clearing fetched data
@discussion Downloaded image data should only be cleared out if a cache is present
@return YES if an image cache is available; otherwise, NO.
*/
- (BOOL)_shouldClearFetchedImageData;
@end
@implementation ASMultiplexImageNode
@@ -160,8 +167,21 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
if (!(self = [super init]))
return nil;
_cache = cache;
_downloader = downloader;
_cache = (id<ASImageCacheProtocol, ASImageCacheProtocolDeprecated>)cache;
_downloader = (id<ASImageDownloaderProtocol, ASImageDownloaderProtocolDeprecated>)downloader;
ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:.");
_downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)];
ASDisplayNodeAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:");
_downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)];
_downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];
_cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)];
_cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)];
self.shouldBypassEnsureDisplay = YES;
return self;
@@ -169,8 +189,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
- (instancetype)init
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
return [self initWithCache:nil downloader:nil]; // satisfy compiler
#if PIN_REMOTE_IMAGE
return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]];
#else
return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]];
#endif
}
- (void)dealloc
@@ -192,16 +215,17 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
{
[super clearFetchedData];
if ([self _shouldClearFetchedImageData]) {
[_phImageRequestOperation cancel];
[_phImageRequestOperation cancel];
[self _setDownloadIdentifier:nil];
// setting this to nil makes the node fetch images the next time its display starts
_loadedImageIdentifier = nil;
self.image = nil;
[self _setDownloadIdentifier:nil];
if (_cacheSupportsClearing) {
[_cache clearFetchedImageFromCacheWithURL:[_dataSource multiplexImageNode:self URLForImageIdentifier:self.loadedImageIdentifier]];
}
// setting this to nil makes the node fetch images the next time its display starts
_loadedImageIdentifier = nil;
self.image = nil;
}
- (void)fetchData
@@ -238,6 +262,69 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}
}
/* displayWillStart in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary
in ASNetworkImageNode as well. */
- (void)displayWillStart
{
[super displayWillStart];
[self fetchData];
if (_downloaderImplementsSetPriority) {
{
ASDN::MutexLocker l(_downloadIdentifierLock);
if (_downloadIdentifier != nil) {
[_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier];
}
}
}
}
/* visibilityDidChange in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary
in ASNetworkImageNode as well. */
- (void)visibilityDidChange:(BOOL)isVisible
{
[super visibilityDidChange:isVisible];
if (_downloaderImplementsSetPriority) {
ASDN::MutexLocker l(_downloadIdentifierLock);
if (_downloadIdentifier != nil) {
if (isVisible) {
[_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier];
} else {
[_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier];
}
}
}
if (_downloaderImplementsSetProgress) {
ASDN::MutexLocker l(_downloadIdentifierLock);
if (_downloadIdentifier != nil) {
__weak __typeof__(self) weakSelf = self;
ASImageDownloaderProgressImage progress = nil;
if (isVisible) {
progress = ^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
ASDN::MutexLocker l(strongSelf->_downloadIdentifierLock);
//Getting a result back for a different download identifier, download must not have been successfully canceled
if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
return;
}
strongSelf.image = progressImage;
};
}
[_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier];
}
}
}
#pragma mark - Core
- (void)setDelegate:(id <ASMultiplexImageNodeDelegate>)delegate
@@ -331,10 +418,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
- (void)_setDownloadIdentifier:(id)downloadIdentifier
{
ASDN::MutexLocker l(_downloadIdentifierLock);
if (ASObjectIsEqual(downloadIdentifier, _downloadIdentifier))
return;
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
if (_downloadIdentifier) {
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
}
_downloadIdentifier = downloadIdentifier;
}
@@ -622,10 +712,16 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
if (_cache) {
[_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) {
UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil);
completionBlock(imageFromCache);
}];
if (_cacheSupportsNewProtocol) {
[_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(UIImage *imageFromCache) {
completionBlock(imageFromCache);
}];
} else {
[_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) {
UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil);
completionBlock(imageFromCache);
}];
}
}
// If we don't have a cache, just fail immediately.
else {
@@ -655,22 +751,46 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
}
// Download!
[self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
callbackQueue:dispatch_get_main_queue()
downloadProgressBlock:downloadProgressBlock
completion:^(CGImageRef coreGraphicsImage, NSError *error) {
// We dereference iVars directly, so we can't have weakSelf going nil on us.
__typeof__(self) strongSelf = weakSelf;
if (!strongSelf)
return;
UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil);
completionBlock(downloadedImage, error);
// Delegateify.
if (strongSelf->_delegateFlags.downloadFinish)
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error];
}]];
if (_downloaderSupportsNewProtocol) {
[self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
callbackQueue:dispatch_get_main_queue()
downloadProgress:downloadProgressBlock
completion:^(UIImage *downloadedImage, NSError *error, id downloadIdentifier) {
// We dereference iVars directly, so we can't have weakSelf going nil on us.
__typeof__(self) strongSelf = weakSelf;
if (!strongSelf)
return;
ASDN::MutexLocker l(_downloadIdentifierLock);
//Getting a result back for a different download identifier, download must not have been successfully canceled
if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
return;
}
completionBlock(downloadedImage, error);
// Delegateify.
if (strongSelf->_delegateFlags.downloadFinish)
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error];
}]];
} else {
[self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
callbackQueue:dispatch_get_main_queue()
downloadProgressBlock:downloadProgressBlock
completion:^(CGImageRef coreGraphicsImage, NSError *error) {
// We dereference iVars directly, so we can't have weakSelf going nil on us.
__typeof__(self) strongSelf = weakSelf;
if (!strongSelf)
return;
UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil);
completionBlock(downloadedImage, error);
// Delegateify.
if (strongSelf->_delegateFlags.downloadFinish)
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error];
}]];
}
}
#pragma mark -
@@ -708,10 +828,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
[self _loadNextImage];
}
- (BOOL)_shouldClearFetchedImageData {
return _cache != nil;
}
@end
#if TARGET_OS_IOS
@implementation NSURL (ASPhotosFrameworkURLs)

View File

@@ -14,11 +14,15 @@
#import "ASEqualityHelpers.h"
#import "ASThread.h"
#if PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.h"
#endif
@interface ASNetworkImageNode ()
{
ASDN::RecursiveMutex _lock;
__weak id<ASImageCacheProtocol> _cache;
__weak id<ASImageDownloaderProtocol> _downloader;
__weak id<ASImageCacheProtocol, ASImageCacheProtocolDeprecated> _cache;
__weak id<ASImageDownloaderProtocol, ASImageDownloaderProtocolDeprecated> _downloader;
// Only access any of these with _lock.
__weak id<ASNetworkImageNodeDelegate> _delegate;
@@ -27,9 +31,16 @@
UIImage *_defaultImage;
NSUUID *_cacheUUID;
id _imageDownload;
id _downloadIdentifier;
BOOL _imageLoaded;
//set on init only
BOOL _downloaderSupportsNewProtocol;
BOOL _downloaderImplementsSetProgress;
BOOL _downloaderImplementsSetPriority;
BOOL _cacheSupportsNewProtocol;
BOOL _cacheSupportsClearing;
}
@end
@@ -40,8 +51,21 @@
if (!(self = [super init]))
return nil;
_cache = cache;
_downloader = downloader;
_cache = (id<ASImageCacheProtocol, ASImageCacheProtocolDeprecated>)cache;
_downloader = (id<ASImageDownloaderProtocol, ASImageDownloaderProtocolDeprecated>)downloader;
ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)] || [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion: or downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:.");
_downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)];
ASDisplayNodeAssert([cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] || [cache respondsToSelector:@selector(fetchCachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion: or fetchCachedImageWithURL:callbackQueue:completion:");
_downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)];
_downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];
_cacheSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)];
_cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)];
_shouldCacheImage = YES;
self.shouldBypassEnsureDisplay = YES;
@@ -50,7 +74,11 @@
- (instancetype)init
{
#if PIN_REMOTE_IMAGE
return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]];
#else
return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]];
#endif
}
- (void)dealloc
@@ -124,11 +152,62 @@
return _delegate;
}
/* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary
in ASMultiplexImageNode as well. */
- (void)displayWillStart
{
[super displayWillStart];
[self fetchData];
if (self.image == nil && _downloaderImplementsSetPriority) {
ASDN::MutexLocker l(_lock);
if (_downloadIdentifier != nil) {
[_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier];
}
}
}
/* visibilityDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary
in ASMultiplexImageNode as well. */
- (void)visibilityDidChange:(BOOL)isVisible
{
if (_downloaderImplementsSetPriority) {
ASDN::MutexLocker l(_lock);
if (_downloadIdentifier != nil) {
if (isVisible) {
[_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier];
} else {
[_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier];
}
}
}
if (_downloaderImplementsSetProgress) {
ASDN::MutexLocker l(_lock);
if (_downloadIdentifier != nil) {
__weak __typeof__(self) weakSelf = self;
ASImageDownloaderProgressImage progress = nil;
if (isVisible) {
progress = ^(UIImage * _Nonnull progressImage, id _Nullable downloadIdentifier) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
}
ASDN::MutexLocker l(_lock);
//Getting a result back for a different download identifier, download must not have been successfully canceled
if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
return;
}
strongSelf.image = progressImage;
};
}
[_downloader setProgressImageBlock:progress callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier];
}
}
}
- (void)clearFetchedData
@@ -141,6 +220,9 @@
[self _cancelImageDownload];
self.image = _defaultImage;
_imageLoaded = NO;
if (_cacheSupportsClearing) {
[_cache clearFetchedImageFromCacheWithURL:_URL];
}
}
}
@@ -158,31 +240,44 @@
- (void)_cancelImageDownload
{
if (!_imageDownload) {
if (!_downloadIdentifier) {
return;
}
[_downloader cancelImageDownloadForIdentifier:_imageDownload];
_imageDownload = nil;
if (_downloadIdentifier) {
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
}
_downloadIdentifier = nil;
_cacheUUID = nil;
}
- (void)_downloadImageWithCompletion:(void (^)(CGImageRef, NSError*))finished
- (void)_downloadImageWithCompletion:(void (^)(UIImage *image, NSError*, id downloadIdentifier))finished
{
_imageDownload = [_downloader downloadImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
downloadProgressBlock:NULL
completion:^(CGImageRef responseImage, NSError *error) {
if (finished != NULL) {
finished(responseImage, error);
}
}];
if (_downloaderSupportsNewProtocol) {
_downloadIdentifier = [_downloader downloadImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
downloadProgress:NULL
completion:^(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) {
if (finished != NULL) {
finished(image, error, downloadIdentifier);
}
}];
} else {
_downloadIdentifier = [_downloader downloadImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
downloadProgressBlock:NULL
completion:^(CGImageRef responseImage, NSError *error) {
if (finished != NULL) {
finished([UIImage imageWithCGImage:responseImage], error, nil);
}
}];
}
}
- (void)_lazilyLoadImageIfNecessary
{
if (!_imageLoaded && _URL != nil && _imageDownload == nil) {
if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) {
if (_URL.isFileURL) {
{
ASDN::MutexLocker l(_lock);
@@ -210,7 +305,7 @@
}
} else {
__weak __typeof__(self) weakSelf = self;
void (^finished)(CGImageRef, NSError *) = ^(CGImageRef responseImage, NSError *error) {
void (^finished)(UIImage *, NSError *, id downloadIdentifier) = ^(UIImage *responseImage, NSError *error, id downloadIdentifier) {
__typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) {
return;
@@ -218,13 +313,18 @@
{
ASDN::MutexLocker l(strongSelf->_lock);
//Getting a result back for a different download identifier, download must not have been successfully canceled
if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
return;
}
if (responseImage != NULL) {
strongSelf->_imageLoaded = YES;
strongSelf.image = [UIImage imageWithCGImage:responseImage];
strongSelf.image = responseImage;
}
strongSelf->_imageDownload = nil;
strongSelf->_downloadIdentifier = nil;
strongSelf->_cacheUUID = nil;
}
@@ -241,22 +341,30 @@
NSUUID *cacheUUID = [NSUUID UUID];
_cacheUUID = cacheUUID;
void (^cacheCompletion)(CGImageRef) = ^(CGImageRef image) {
void (^cacheCompletion)(UIImage *) = ^(UIImage *image) {
// If the cache UUID changed, that means this request was cancelled.
if (![_cacheUUID isEqual:cacheUUID]) {
return;
}
if (image == NULL && _downloader != nil) {
[self _downloadImageWithCompletion:finished];
} else {
finished(image, NULL);
finished(image, NULL, nil);
}
};
[_cache fetchCachedImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
completion:cacheCompletion];
if (_cacheSupportsNewProtocol) {
[_cache cachedImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
completion:cacheCompletion];
} else {
[_cache fetchCachedImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
completion:^(CGImageRef image) {
cacheCompletion([UIImage imageWithCGImage:image]);
}];
}
} else {
[self _downloadImageWithCompletion:finished];
}

View File

@@ -13,7 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
/**
* @abstract Simple NSURLSession-based image downloader.
*/
@interface ASBasicImageDownloader : NSObject <ASImageDownloaderProtocol>
@interface ASBasicImageDownloader : NSObject <ASImageDownloaderProtocolDeprecated, ASImageDownloaderProtocol>
+ (instancetype)sharedImageDownloader;

View File

@@ -6,51 +6,53 @@
* of patent rights can be found in the PATENTS file in the same directory.
*/
#import <CoreGraphics/CoreGraphics.h>
#import <UIKit/UIKit.h>
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache);
@protocol ASImageCacheProtocol <NSObject>
@required
@optional
/**
@abstract Attempts to fetch an image with the given URL from the cache.
@param URL The URL of the image to retrieve from the cache.
@param callbackQueue The queue to call `completion` on. If this value is nil, @{ref completion} will be invoked on the
main-queue.
@param completion The block to be called when the cache has either hit or missed.
@param imageFromCache The image that was retrieved from the cache, if the image could be retrieved; nil otherwise.
@discussion If `URL` is nil, `completion` will 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.
@abstract Attempts to fetch an image with the given URL from the cache.
@param URL The URL of the image to retrieve from the cache.
@param callbackQueue The queue to call `completion` on. If this value is nil, @{ref completion} will be invoked on the
main-queue.
@param completion The block to be called when the cache has either hit or missed.
@param imageFromCache The image that was retrieved from the cache, if the image could be retrieved; nil otherwise.
@discussion If `URL` is nil, `completion` will 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.
*/
- (void)fetchCachedImageWithURL:(nullable NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue
completion:(void (^)(CGImageRef _Nullable imageFromCache))completion;
- (void)cachedImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
completion:(ASImageCacherCompletion)completion;
/**
@abstract Called during clearFetchedData. Allows the cache to optionally trim items.
@note Depending on your caches implementation you may *not* wish to respond to this method. It is however useful
if you have a memory and disk cache in which case you'll likely want to clear out the memory cache.
*/
- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL;
@end
typedef void(^ASImageDownloaderCompletion)(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier);
typedef void(^ASImageDownloaderProgress)(CGFloat progress);
typedef void(^ASImageDownloaderProgressImage)(UIImage *progressImage, id _Nullable downloadIdentifier);
typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) {
ASImageDownloaderPriorityPreload = 0,
ASImageDownloaderPriorityImminent,
ASImageDownloaderPriorityVisible
};
@protocol ASImageDownloaderProtocol <NSObject>
@required
/**
@abstract Downloads an image with the given URL.
@param URL The URL of the image to download.
@param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. If this value is nil, both blocks
will be invoked on the main-queue.
@param downloadProgressBlock The block to be invoked when the download of `URL` progresses.
@param progress The progress of the download, in the range of (0.0, 1.0), inclusive.
@param completion The block to be invoked when the download has completed, or has failed.
@param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise.
@param error An error describing why the download of `URL` failed, if the download failed; nil otherwise.
@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)downloadImageWithURL:(NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock
completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion;
/**
@abstract Cancels an image download.
@@ -58,7 +60,75 @@ NS_ASSUME_NONNULL_BEGIN
`downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`.
@discussion This method has no effect if `downloadIdentifier` is nil.
*/
- (void)cancelImageDownloadForIdentifier:(nullable id)downloadIdentifier;
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier;
@optional
//You must implement the following method OR the deprecated method at the bottom
/**
@abstract Downloads an image with the given URL.
@param URL The URL of the image to download.
@param callbackQueue The queue to call `downloadProgressBlock` and `completion` on.
@param downloadProgress The block to be invoked when the download of `URL` progresses.
@param progress The progress of the download, in the range of (0.0, 1.0), inclusive.
@param completion The block to be invoked when the download has completed, or has failed.
@param image The image that was downloaded, if the image could be successfully downloaded; nil otherwise.
@param error An error describing why the download of `URL` failed, if the download failed; nil otherwise.
@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)downloadImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
completion:(nullable ASImageDownloaderCompletion)completion;
/**
@abstract Sets block to be called when a progress image is available.
@param progressBlock The block to be invoked when the download has a progressive render of an image available.
@param callbackQueue The queue to call `progressBlock` on.
@param downloadIdentifier The opaque download identifier object returned from
`downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`.
*/
- (void)setProgressImageBlock:(nullable ASImageDownloaderProgressImage)progressBlock
callbackQueue:(dispatch_queue_t)callbackQueue
withDownloadIdentifier:(id)downloadIdentifier;
/**
@abstract Called to indicate what priority an image should be downloaded at.
@param priority The priority at which the image should be downloaded.
@param downloadIdentifier The opaque download identifier object returned from
`downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`.
*/
- (void)setPriority:(ASImageDownloaderPriority)priority
withDownloadIdentifier:(id)downloadIdentifier;
@end
@protocol ASImageDownloaderProtocolDeprecated <ASImageDownloaderProtocol>
@optional
/**
@deprecated This method is deprecated @see downloadImageWithURL:callbackQueue:downloadProgress:completion: instead
*/
- (nullable id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock
completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion;
@end
@protocol ASImageCacheProtocolDeprecated <ASImageCacheProtocol>
@optional
/**
@deprecated This method is deprecated @see cachedImageWithURL:callbackQueue:completion: instead
*/
- (void)fetchCachedImageWithURL:(nullable NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue
completion:(void (^)(CGImageRef _Nullable imageFromCache))completion;
@end

View File

@@ -0,0 +1,16 @@
//
// ASPINRemoteImageDownloader.h
// Pods
//
// Created by Garrett Moon on 2/5/16.
//
//
#import <Foundation/Foundation.h>
#import "ASImageProtocols.h"
@interface ASPINRemoteImageDownloader : NSObject <ASImageCacheProtocol, ASImageDownloaderProtocol>
+ (instancetype)sharedDownloader;
@end

View File

@@ -0,0 +1,102 @@
//
// ASPINRemoteImageDownloader.m
// Pods
//
// Created by Garrett Moon on 2/5/16.
//
//
#ifdef PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.h"
#import "ASAssert.h"
#import <PINRemoteImage/PINRemoteImageManager.h>
#import <PINCache/PINCache.h>
@implementation ASPINRemoteImageDownloader
+ (instancetype)sharedDownloader
{
static ASPINRemoteImageDownloader *sharedDownloader = nil;
static dispatch_once_t once = 0;
dispatch_once(&once, ^{
sharedDownloader = [[ASPINRemoteImageDownloader alloc] init];
});
return sharedDownloader;
}
#pragma mark ASImageProtocols
- (void)fetchCachedImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
completion:(void (^)(CGImageRef imageFromCache))completion
{
//We do not check the cache here and instead check it in downloadImageWithURL to avoid checking the cache twice.
dispatch_async(callbackQueue, ^{
completion(nil);
});
}
- (void)clearFetchedImageFromCacheWithURL:(NSURL *)URL
{
NSString *key = [[PINRemoteImageManager sharedImageManager] cacheKeyForURL:URL processorKey:nil];
[[[[PINRemoteImageManager sharedImageManager] cache] memoryCache] removeObjectForKey:key];
}
- (nullable id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgress:(void (^)(CGFloat progress))downloadProgressBlock
completion:(void (^)(UIImage *image, NSError * error, id downloadIdentifier))completion
{
return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode completion:^(PINRemoteImageManagerResult *result) {
dispatch_async(callbackQueue, ^{
completion(result.image, result.error, result.UUID);
});
}];
}
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
{
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
[[PINRemoteImageManager sharedImageManager] cancelTaskWithUUID:downloadIdentifier];
}
- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier
{
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
if (progressBlock) {
[[PINRemoteImageManager sharedImageManager] setProgressCallback:^(PINRemoteImageManagerResult * _Nonnull result) {
dispatch_async(callbackQueue, ^{
progressBlock(result.image, result.UUID);
});
} ofTaskWithUUID:downloadIdentifier];
} else {
[[PINRemoteImageManager sharedImageManager] setProgressCallback:nil ofTaskWithUUID:downloadIdentifier];
}
}
- (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier
{
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityMedium;
switch (priority) {
case ASImageDownloaderPriorityPreload:
pi_priority = PINRemoteImageManagerPriorityMedium;
break;
case ASImageDownloaderPriorityImminent:
pi_priority = PINRemoteImageManagerPriorityHigh;
break;
case ASImageDownloaderPriorityVisible:
pi_priority = PINRemoteImageManagerPriorityVeryHigh;
break;
}
[[PINRemoteImageManager sharedImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier];
}
@end
#endif

View File

@@ -145,14 +145,14 @@ static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)())
NSArray *URL;
[inv getArgument:&URL atIndex:2];
void (^completionBlock)(CGImageRef);
ASImageCacherCompletion completionBlock;
[inv getArgument:&completionBlock atIndex:4];
// Call the completion block with our test image and URL.
NSURL *testImageURL = [self _testImageURL];
XCTAssertEqualObjects(URL, testImageURL, @"Fetching URL other than test image");
completionBlock([self _testImage].CGImage);
}] fetchCachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]];
completionBlock([self _testImage]);
}] cachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]];
imageNode.imageIdentifiers = @[imageIdentifier];
// Kick off loading.
@@ -302,25 +302,25 @@ static BOOL ASRunRunLoopUntilBlockIsTrue(BOOL (^block)())
// Mock a cache miss.
id mockCache = [OCMockObject mockForProtocol:@protocol(ASImageCacheProtocol)];
[[[mockCache stub] andDo:^(NSInvocation *inv) {
void (^completion)(CGImageRef imageFromCache);
ASImageCacherCompletion completion;
[inv getArgument:&completion atIndex:4];
completion(nil);
}] fetchCachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]];
}] cachedImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] completion:[OCMArg any]];
// Mock a 50%-progress URL download.
id mockDownloader = [OCMockObject mockForProtocol:@protocol(ASImageDownloaderProtocol)];
const CGFloat mockedProgress = 0.5;
[[[mockDownloader stub] andDo:^(NSInvocation *inv) {
// Simulate progress.
void (^progressBlock)(CGFloat progress);
ASImageDownloaderProgress progressBlock;
[inv getArgument:&progressBlock atIndex:4];
progressBlock(mockedProgress);
// Simulate completion.
void (^completionBlock)(CGImageRef image, NSError *error);
ASImageDownloaderCompletion completionBlock;
[inv getArgument:&completionBlock atIndex:5];
completionBlock([self _testImage].CGImage, nil);
}] downloadImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] downloadProgressBlock:[OCMArg any] completion:[OCMArg any]];
completionBlock([self _testImage], nil, nil);
}] downloadImageWithURL:[OCMArg any] callbackQueue:[OCMArg any] downloadProgress:[OCMArg any] completion:[OCMArg any]];
ASMultiplexImageNode *imageNode = [[ASMultiplexImageNode alloc] initWithCache:mockCache downloader:mockDownloader];
NSNumber *imageIdentifier = @1;

View File

@@ -20,7 +20,7 @@
}
// multiplex image node!
// NB: we're using a custom downloader with an artificial delay for this demo, but ASBasicImageDownloader works too!
// NB: we're using a custom downloader with an artificial delay for this demo, but ASPINRemoteImageDownloader works too!
_imageNode = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:self];
_imageNode.dataSource = self;
_imageNode.delegate = self;