Add default PINRemoteImageDownloader

This commit is contained in:
Garrett Moon
2016-02-08 16:00:19 -08:00
parent 08017071bf
commit 5a4e4dcac4
10 changed files with 457 additions and 89 deletions

View File

@@ -37,6 +37,9 @@ Pod::Spec.new do |spec|
'AsyncDisplayKit/Details/ASDealloc2MainObject.h', 'AsyncDisplayKit/Details/ASDealloc2MainObject.h',
'AsyncDisplayKit/Details/ASDealloc2MainObject.m', 'AsyncDisplayKit/Details/ASDealloc2MainObject.m',
] ]
#Subspecs
spec.subspec 'ASDealloc2MainObject' do |mrr| spec.subspec 'ASDealloc2MainObject' do |mrr|
mrr.requires_arc = false mrr.requires_arc = false
mrr.source_files = [ mrr.source_files = [
@@ -45,6 +48,15 @@ Pod::Spec.new do |spec|
'AsyncDisplayKit/Details/ASDealloc2MainObject.m', 'AsyncDisplayKit/Details/ASDealloc2MainObject.m',
] ]
end end
spec.subspec 'PINRemoteImage' do |pin|
pin.xcconfig = { 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited) PIN_REMOTE_IMAGE=1' }
pin.dependency 'PINRemoteImage'
pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject'
end
# Include optional FLAnimatedImage module
spec.default_subspec = 'PINRemoteImage'
spec.social_media_url = 'https://twitter.com/fbOpenSource' spec.social_media_url = 'https://twitter.com/fbOpenSource'
spec.library = 'c++' 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. #error ASMultiplexImageNode can be used on iOS 7, but must be linked against the iOS 8 SDK.
#endif #endif
#if PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.h"
#else
#import "ASBasicImageDownloader.h"
#endif
NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain"; NSString *const ASMultiplexImageNodeErrorDomain = @"ASMultiplexImageNodeErrorDomain";
static NSString *const kAssetsLibraryURLScheme = @"assets-library"; static NSString *const kAssetsLibraryURLScheme = @"assets-library";
@@ -72,7 +78,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
__weak NSOperation *_phImageRequestOperation; __weak NSOperation *_phImageRequestOperation;
// Networking. // Networking.
ASDN::Mutex _downloadIdentifierLock;
id _downloadIdentifier; id _downloadIdentifier;
//set on init only
BOOL _downloaderSupportsNewProtocol;
BOOL _downloaderImplementsSetProgress;
BOOL _downloaderImplementsSetPriority;
BOOL _cacherSupportsNewProtocol;
} }
//! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h. //! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h.
@@ -162,6 +175,18 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
_cache = cache; _cache = cache;
_downloader = downloader; _downloader = downloader;
NSAssert([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:)] ? YES : NO;
NSAssert(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:)] ? YES : NO;
_downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)] ? YES : NO;
_cacherSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] ? YES : NO;
self.shouldBypassEnsureDisplay = YES; self.shouldBypassEnsureDisplay = YES;
return self; return self;
@@ -169,8 +194,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
- (instancetype)init - (instancetype)init
{ {
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER(); #if PIN_REMOTE_IMAGE
return [self initWithCache:nil downloader:nil]; // satisfy compiler return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]];
#else
return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]];
#endif
} }
- (void)dealloc - (void)dealloc
@@ -238,6 +266,48 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
} }
} }
- (void)displayWillStart
{
[super displayWillStart];
[self fetchData];
if (_downloaderImplementsSetPriority) {
{
ASDN::MutexLocker l(_downloadIdentifierLock);
if (_downloadIdentifier != nil) {
[_downloader setPriority:ASImageDownloaderPriorityDisplay withDownloadIdentifier:_downloadIdentifier];
}
}
}
if (self.image == nil) {
if (_downloaderImplementsSetProgress) {
{
ASDN::MutexLocker l(_downloadIdentifierLock);
if (_downloadIdentifier != nil) {
__weak __typeof__(self) weakSelf = self;
[_downloader setProgressImageBlock:^(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 (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) {
return;
}
strongSelf.image = progressImage;
} callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier];
}
}
}
}
}
#pragma mark - Core #pragma mark - Core
- (void)setDelegate:(id <ASMultiplexImageNodeDelegate>)delegate - (void)setDelegate:(id <ASMultiplexImageNodeDelegate>)delegate
@@ -331,10 +401,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
- (void)_setDownloadIdentifier:(id)downloadIdentifier - (void)_setDownloadIdentifier:(id)downloadIdentifier
{ {
ASDN::MutexLocker l(_downloadIdentifierLock);
if (ASObjectIsEqual(downloadIdentifier, _downloadIdentifier)) if (ASObjectIsEqual(downloadIdentifier, _downloadIdentifier))
return; return;
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; if (_downloadIdentifier) {
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
}
_downloadIdentifier = downloadIdentifier; _downloadIdentifier = downloadIdentifier;
} }
@@ -622,10 +695,16 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
if (_cache) { if (_cache) {
[_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) { if (_cacherSupportsNewProtocol) {
UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil); [_cache cachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(UIImage *imageFromCache) {
completionBlock(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. // If we don't have a cache, just fail immediately.
else { else {
@@ -655,22 +734,46 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
} }
// Download! // Download!
[self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL if (_downloaderSupportsNewProtocol) {
callbackQueue:dispatch_get_main_queue() [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
downloadProgressBlock:downloadProgressBlock callbackQueue:dispatch_get_main_queue()
completion:^(CGImageRef coreGraphicsImage, NSError *error) { downloadProgress:downloadProgressBlock
// We dereference iVars directly, so we can't have weakSelf going nil on us. completion:^(UIImage *downloadedImage, NSError *error, id downloadIdentifier) {
__typeof__(self) strongSelf = weakSelf; // We dereference iVars directly, so we can't have weakSelf going nil on us.
if (!strongSelf) __typeof__(self) strongSelf = weakSelf;
return; if (!strongSelf)
return;
UIImage *downloadedImage = (coreGraphicsImage ? [UIImage imageWithCGImage:coreGraphicsImage] : nil);
completionBlock(downloadedImage, error); ASDN::MutexLocker l(_downloadIdentifierLock);
//Getting a result back for a different download identifier, download must not have been successfully canceled
// Delegateify. if (![_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) {
if (strongSelf->_delegateFlags.downloadFinish) return;
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; }
}]];
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 - #pragma mark -

View File

@@ -45,19 +45,19 @@ NS_ASSUME_NONNULL_BEGIN
/** /**
* The delegate, which must conform to the <ASNetworkImageNodeDelegate> protocol. * The delegate, which must conform to the <ASNetworkImageNodeDelegate> protocol.
*/ */
@property (atomic, weak, readwrite) id<ASNetworkImageNodeDelegate> delegate; @property (nonatomic, weak, readwrite) id<ASNetworkImageNodeDelegate> delegate;
/** /**
* A placeholder image to display while the URL is loading. * A placeholder image to display while the URL is loading.
*/ */
@property (nullable, atomic, strong, readwrite) UIImage *defaultImage; @property (nullable, nonatomic, strong, readwrite) UIImage *defaultImage;
/** /**
* The URL of a new image to download and display. * The URL of a new image to download and display.
* *
* @discussion Changing this property will reset the displayed image to a placeholder (<defaultImage>) while loading. * @discussion Changing this property will reset the displayed image to a placeholder (<defaultImage>) while loading.
*/ */
@property (nullable, atomic, strong, readwrite) NSURL *URL; @property (nullable, nonatomic, strong, readwrite) NSURL *URL;
/** /**
* Download and display a new image. * Download and display a new image.

View File

@@ -14,6 +14,10 @@
#import "ASEqualityHelpers.h" #import "ASEqualityHelpers.h"
#import "ASThread.h" #import "ASThread.h"
#if PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.h"
#endif
@interface ASNetworkImageNode () @interface ASNetworkImageNode ()
{ {
ASDN::RecursiveMutex _lock; ASDN::RecursiveMutex _lock;
@@ -27,9 +31,15 @@
UIImage *_defaultImage; UIImage *_defaultImage;
NSUUID *_cacheUUID; NSUUID *_cacheUUID;
id _imageDownload; id _downloadIdentifier;
BOOL _imageLoaded; BOOL _imageLoaded;
//set on init only
BOOL _downloaderSupportsNewProtocol;
BOOL _downloaderImplementsSetProgress;
BOOL _downloaderImplementsSetPriority;
BOOL _cacherSupportsNewProtocol;
} }
@end @end
@@ -42,6 +52,18 @@
_cache = cache; _cache = cache;
_downloader = downloader; _downloader = downloader;
NSAssert([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:)] ? YES : NO;
NSAssert([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:)] ? YES : NO;
_downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)] ? YES : NO;
_cacherSupportsNewProtocol = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)] ? YES : NO;
_shouldCacheImage = YES; _shouldCacheImage = YES;
self.shouldBypassEnsureDisplay = YES; self.shouldBypassEnsureDisplay = YES;
@@ -50,7 +72,11 @@
- (instancetype)init - (instancetype)init
{ {
#if PIN_REMOTE_IMAGE
return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]];
#else
return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]]; return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]];
#endif
} }
- (void)dealloc - (void)dealloc
@@ -129,6 +155,41 @@
[super displayWillStart]; [super displayWillStart];
[self fetchData]; [self fetchData];
if (self.image == nil) {
if (_downloaderImplementsSetPriority) {
{
ASDN::MutexLocker l(_lock);
if (_downloadIdentifier != nil) {
[_downloader setPriority:ASImageDownloaderPriorityDisplay withDownloadIdentifier:_downloadIdentifier];
}
}
}
if (_downloaderImplementsSetProgress) {
{
ASDN::MutexLocker l(_lock);
if (_downloadIdentifier != nil) {
__weak __typeof__(self) weakSelf = self;
[_downloader setProgressImageBlock:^(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 (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) {
return;
}
strongSelf.image = progressImage;
} callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:_downloadIdentifier];
}
}
}
}
} }
- (void)clearFetchedData - (void)clearFetchedData
@@ -158,31 +219,44 @@
- (void)_cancelImageDownload - (void)_cancelImageDownload
{ {
if (!_imageDownload) { if (!_downloadIdentifier) {
return; return;
} }
[_downloader cancelImageDownloadForIdentifier:_imageDownload]; if (_downloadIdentifier) {
_imageDownload = nil; [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
}
_downloadIdentifier = nil;
_cacheUUID = nil; _cacheUUID = nil;
} }
- (void)_downloadImageWithCompletion:(void (^)(CGImageRef, NSError*))finished - (void)_downloadImageWithCompletion:(void (^)(UIImage *image, NSError*, id downloadIdentifier))finished
{ {
_imageDownload = [_downloader downloadImageWithURL:_URL if (_downloaderSupportsNewProtocol) {
callbackQueue:dispatch_get_main_queue() _downloadIdentifier = [_downloader downloadImageWithURL:_URL
downloadProgressBlock:NULL callbackQueue:dispatch_get_main_queue()
completion:^(CGImageRef responseImage, NSError *error) { downloadProgress:NULL
if (finished != NULL) { completion:^(UIImage * _Nullable image, NSError * _Nullable error, id _Nullable downloadIdentifier) {
finished(responseImage, error); 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 - (void)_lazilyLoadImageIfNecessary
{ {
if (!_imageLoaded && _URL != nil && _imageDownload == nil) { if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) {
if (_URL.isFileURL) { if (_URL.isFileURL) {
{ {
ASDN::MutexLocker l(_lock); ASDN::MutexLocker l(_lock);
@@ -210,7 +284,7 @@
} }
} else { } else {
__weak __typeof__(self) weakSelf = self; __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; __typeof__(self) strongSelf = weakSelf;
if (strongSelf == nil) { if (strongSelf == nil) {
return; return;
@@ -218,13 +292,18 @@
{ {
ASDN::MutexLocker l(strongSelf->_lock); ASDN::MutexLocker l(strongSelf->_lock);
//Getting a result back for a different download identifier, download must not have been successfully canceled
if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) {
return;
}
if (responseImage != NULL) { if (responseImage != NULL) {
strongSelf->_imageLoaded = YES; strongSelf->_imageLoaded = YES;
strongSelf.image = [UIImage imageWithCGImage:responseImage]; strongSelf.image = responseImage;
} }
strongSelf->_imageDownload = nil; strongSelf->_downloadIdentifier = nil;
strongSelf->_cacheUUID = nil; strongSelf->_cacheUUID = nil;
} }
@@ -241,22 +320,32 @@
NSUUID *cacheUUID = [NSUUID UUID]; NSUUID *cacheUUID = [NSUUID UUID];
_cacheUUID = cacheUUID; _cacheUUID = cacheUUID;
void (^cacheCompletion)(CGImageRef) = ^(CGImageRef image) { void (^cacheCompletion)(UIImage *) = ^(UIImage *image) {
// If the cache UUID changed, that means this request was cancelled. // If the cache UUID changed, that means this request was cancelled.
if (![_cacheUUID isEqual:cacheUUID]) { if (![_cacheUUID isEqual:cacheUUID]) {
return; return;
} }
if (image == NULL && _downloader != nil) { if (image == NULL && _downloader != nil) {
[self _downloadImageWithCompletion:finished]; [self _downloadImageWithCompletion:finished];
} else { } else {
finished(image, NULL); finished(image, NULL, nil);
} }
}; };
[_cache fetchCachedImageWithURL:_URL if (_cacherSupportsNewProtocol) {
callbackQueue:dispatch_get_main_queue() [_cache cachedImageWithURL:_URL
completion:cacheCompletion]; callbackQueue:dispatch_get_main_queue()
completion:cacheCompletion];
} else {
void (^oldCacheCompletion)(CGImageRef) = ^(CGImageRef image) {
cacheCompletion([UIImage imageWithCGImage:image]);
};
[_cache fetchCachedImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
completion:oldCacheCompletion];
}
} else { } else {
[self _downloadImageWithCompletion:finished]; [self _downloadImageWithCompletion:finished];
} }

View File

@@ -230,7 +230,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext
- (id)downloadImageWithURL:(NSURL *)URL - (id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^)(CGFloat))downloadProgressBlock downloadProgress:(void (^)(CGFloat))downloadProgressBlock
completion:(void (^)(CGImageRef, NSError *))completion completion:(void (^)(CGImageRef, NSError *))completion
{ {
ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL];

View File

@@ -6,51 +6,51 @@
* of patent rights can be found in the PATENTS file in the same directory. * 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> #import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_BEGIN
typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache);
@protocol ASImageCacheProtocol <NSObject> @protocol ASImageCacheProtocol <NSObject>
@required @required
/** /**
@abstract Attempts to fetch an image with the given URL from the cache. @deprecated This method is deprecated @see cachedImageWithURL:callbackQueue:completion: instead
@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 - (void)fetchCachedImageWithURL:(nullable NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue callbackQueue:(nullable dispatch_queue_t)callbackQueue
completion:(void (^)(CGImageRef _Nullable imageFromCache))completion; completion:(void (^)(CGImageRef _Nullable imageFromCache))completion;
/**
@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)cachedImageWithURL:(nullable NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue
completion:(ASImageCacherCompletion)completion;
@end @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) {
ASImageDownloaderPriorityNormal = 0,
ASImageDownloaderPriorityDisplay,
};
@protocol ASImageDownloaderProtocol <NSObject> @protocol ASImageDownloaderProtocol <NSObject>
@required @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. @abstract Cancels an image download.
@@ -58,7 +58,58 @@ NS_ASSUME_NONNULL_BEGIN
`downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`.
@discussion This method has no effect if `downloadIdentifier` is nil. @discussion This method has no effect if `downloadIdentifier` is nil.
*/ */
- (void)cancelImageDownloadForIdentifier:(nullable id)downloadIdentifier; - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier;
@optional
//You must implement one of the two following methods
/**
@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;
/**
@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 @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,97 @@
//
// ASPINRemoteImageDownloader.m
// Pods
//
// Created by Garrett Moon on 2/5/16.
//
//
#ifdef PIN_REMOTE_IMAGE
#import "ASPINRemoteImageDownloader.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
{
NSString *key = [[PINRemoteImageManager sharedImageManager] cacheKeyForURL:URL processorKey:nil];
UIImage *image = [[[[PINRemoteImageManager sharedImageManager] cache] memoryCache] objectForKey:key];
dispatch_async(callbackQueue, ^{
completion([image CGImage]);
});
}
/**
@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:(dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^)(CGFloat progress))downloadProgressBlock
completion:(void (^)(CGImageRef image, NSError * error, id downloadIdentifier))completion
{
return [[PINRemoteImageManager sharedImageManager] downloadImageWithURL:URL completion:^(PINRemoteImageManagerResult *result) {
dispatch_async(callbackQueue, ^{
completion([result.image CGImage], result.error, result.UUID);
});
}];
}
/**
@abstract Cancels an image download.
@param downloadIdentifier The opaque download identifier object returned from
`downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`.
@discussion This method has no effect if `downloadIdentifier` is nil.
*/
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
{
NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
[[PINRemoteImageManager sharedImageManager] cancelTaskWithUUID:downloadIdentifier];
}
- (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier
{
NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
[[PINRemoteImageManager sharedImageManager] setProgressCallback:^(PINRemoteImageManagerResult * _Nonnull result) {
dispatch_async(callbackQueue, ^{
progressBlock(result.image, result.UUID);
});
} ofTaskWithUUID:downloadIdentifier];
}
- (void)setPriority:(ASImageDownloaderPriority)priority withDownloadIdentifier:(id)downloadIdentifier
{
NSAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID");
[[PINRemoteImageManager sharedImageManager] setPriority:PINRemoteImageManagerPriorityHigh ofTaskWithUUID:downloadIdentifier];
}
@end
#endif

View File

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

View File

@@ -20,7 +20,7 @@
} }
// multiplex image node! // 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 = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:self];
_imageNode.dataSource = self; _imageNode.dataSource = self;
_imageNode.delegate = self; _imageNode.delegate = self;