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 = [
@@ -46,6 +49,15 @@ Pod::Spec.new do |spec|
] ]
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++'
spec.pod_target_xcconfig = { spec.pod_target_xcconfig = {

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;
if (_downloadIdentifier) {
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
}
_downloadIdentifier = downloadIdentifier; _downloadIdentifier = downloadIdentifier;
} }
@@ -622,11 +695,17 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required"); ASDisplayNodeAssertNotNil(completionBlock, @"completionBlock is required");
if (_cache) { if (_cache) {
if (_cacherSupportsNewProtocol) {
[_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) { [_cache fetchCachedImageWithURL:imageURL callbackQueue:dispatch_get_main_queue() completion:^(CGImageRef coreGraphicsImageFromCache) {
UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil); UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil);
completionBlock(imageFromCache); completionBlock(imageFromCache);
}]; }];
} }
}
// If we don't have a cache, just fail immediately. // If we don't have a cache, just fail immediately.
else { else {
completionBlock(nil); completionBlock(nil);
@@ -655,6 +734,29 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
} }
// Download! // Download!
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 (![_downloadIdentifier isEqual:downloadIdentifier] && 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 [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
callbackQueue:dispatch_get_main_queue() callbackQueue:dispatch_get_main_queue()
downloadProgressBlock:downloadProgressBlock downloadProgressBlock:downloadProgressBlock
@@ -672,6 +774,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error];
}]]; }]];
} }
}
#pragma mark - #pragma mark -
- (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier error:(NSError *)error - (void)_finishedLoadingImage:(UIImage *)image forIdentifier:(id)imageIdentifier error:(NSError *)error

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) {
_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() callbackQueue:dispatch_get_main_queue()
downloadProgressBlock:NULL downloadProgressBlock:NULL
completion:^(CGImageRef responseImage, NSError *error) { completion:^(CGImageRef responseImage, NSError *error) {
if (finished != NULL) { if (finished != NULL) {
finished(responseImage, error); 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;
@@ -219,12 +293,17 @@
{ {
ASDN::MutexLocker l(strongSelf->_lock); ASDN::MutexLocker l(strongSelf->_lock);
if (responseImage != NULL) { //Getting a result back for a different download identifier, download must not have been successfully canceled
strongSelf->_imageLoaded = YES; if (![strongSelf->_downloadIdentifier isEqual:downloadIdentifier] && downloadIdentifier != nil) {
strongSelf.image = [UIImage imageWithCGImage:responseImage]; return;
} }
strongSelf->_imageDownload = nil; if (responseImage != NULL) {
strongSelf->_imageLoaded = YES;
strongSelf.image = responseImage;
}
strongSelf->_downloadIdentifier = nil;
strongSelf->_cacheUUID = nil; strongSelf->_cacheUUID = nil;
} }
@@ -241,7 +320,7 @@
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;
@@ -250,13 +329,23 @@
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);
} }
}; };
if (_cacherSupportsNewProtocol) {
[_cache cachedImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
completion:cacheCompletion];
} else {
void (^oldCacheCompletion)(CGImageRef) = ^(CGImageRef image) {
cacheCompletion([UIImage imageWithCGImage:image]);
};
[_cache fetchCachedImageWithURL:_URL [_cache fetchCachedImageWithURL:_URL
callbackQueue:dispatch_get_main_queue() callbackQueue:dispatch_get_main_queue()
completion:cacheCompletion]; 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,14 +6,23 @@
* 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
/**
@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;
/** /**
@abstract Attempts to fetch an image with the given URL from the cache. @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 URL The URL of the image to retrieve from the cache.
@@ -24,21 +33,50 @@ NS_ASSUME_NONNULL_BEGIN
@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` 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. the calling thread as it is likely to be called from the main thread.
*/ */
- (void)fetchCachedImageWithURL:(nullable NSURL *)URL - (void)cachedImageWithURL:(nullable NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue callbackQueue:(nullable dispatch_queue_t)callbackQueue
completion:(void (^)(CGImageRef _Nullable imageFromCache))completion; 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 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;
@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. @abstract Downloads an image with the given URL.
@param URL The URL of the image to download. @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 @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on.
will be invoked on the main-queue. @param downloadProgress The block to be invoked when the download of `URL` progresses.
@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 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 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 image The image that was downloaded, if the image could be successfully downloaded; nil otherwise.
@@ -48,17 +86,30 @@ NS_ASSUME_NONNULL_BEGIN
retain the identifier if you wish to use it later. retain the identifier if you wish to use it later.
*/ */
- (nullable id)downloadImageWithURL:(NSURL *)URL - (nullable id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(nullable dispatch_queue_t)callbackQueue callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^ _Nullable)(CGFloat progress))downloadProgressBlock downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress
completion:(void (^ _Nullable)(CGImageRef _Nullable image, NSError * _Nullable error))completion; completion:(nullable ASImageDownloaderCompletion)completion;
/** /**
@abstract Cancels an image download. @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 @param downloadIdentifier The opaque download identifier object returned from
`downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`.
@discussion This method has no effect if `downloadIdentifier` is nil.
*/ */
- (void)cancelImageDownloadForIdentifier:(nullable id)downloadIdentifier; - (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;