diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index f93552b3c0..9b47f55c84 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -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' + pin.dependency 'AsyncDisplayKit/ASDealloc2MainObject' + end + + # Include optional FLAnimatedImage module + spec.default_subspec = 'PINRemoteImage' spec.social_media_url = 'https://twitter.com/fbOpenSource' spec.library = 'c++' diff --git a/AsyncDisplayKit/ASMultiplexImageNode.mm b/AsyncDisplayKit/ASMultiplexImageNode.mm index ff35ed3aec..526d1be1ef 100644 --- a/AsyncDisplayKit/ASMultiplexImageNode.mm +++ b/AsyncDisplayKit/ASMultiplexImageNode.mm @@ -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"; @@ -72,7 +78,14 @@ 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 _cacherSupportsNewProtocol; } //! @abstract Read-write redeclaration of property declared in ASMultiplexImageNode.h. @@ -162,6 +175,18 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = cache; _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; return self; @@ -169,8 +194,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 @@ -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 - (void)setDelegate:(id )delegate @@ -331,10 +401,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 +695,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 (_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) { + UIImage *imageFromCache = (coreGraphicsImageFromCache ? [UIImage imageWithCGImage:coreGraphicsImageFromCache] : nil); + completionBlock(imageFromCache); + }]; + } } // If we don't have a cache, just fail immediately. else { @@ -655,22 +734,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 (![_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 + 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 - diff --git a/AsyncDisplayKit/ASNetworkImageNode.h b/AsyncDisplayKit/ASNetworkImageNode.h index 82513a8c55..be08fa2158 100644 --- a/AsyncDisplayKit/ASNetworkImageNode.h +++ b/AsyncDisplayKit/ASNetworkImageNode.h @@ -45,19 +45,19 @@ NS_ASSUME_NONNULL_BEGIN /** * The delegate, which must conform to the protocol. */ -@property (atomic, weak, readwrite) id delegate; +@property (nonatomic, weak, readwrite) id delegate; /** * 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. * * @discussion Changing this property will reset the displayed image to a placeholder () while loading. */ -@property (nullable, atomic, strong, readwrite) NSURL *URL; +@property (nullable, nonatomic, strong, readwrite) NSURL *URL; /** * Download and display a new image. diff --git a/AsyncDisplayKit/ASNetworkImageNode.mm b/AsyncDisplayKit/ASNetworkImageNode.mm index 5c5c8f37be..ee350ef0e8 100755 --- a/AsyncDisplayKit/ASNetworkImageNode.mm +++ b/AsyncDisplayKit/ASNetworkImageNode.mm @@ -14,6 +14,10 @@ #import "ASEqualityHelpers.h" #import "ASThread.h" +#if PIN_REMOTE_IMAGE +#import "ASPINRemoteImageDownloader.h" +#endif + @interface ASNetworkImageNode () { ASDN::RecursiveMutex _lock; @@ -27,9 +31,15 @@ UIImage *_defaultImage; NSUUID *_cacheUUID; - id _imageDownload; + id _downloadIdentifier; BOOL _imageLoaded; + + //set on init only + BOOL _downloaderSupportsNewProtocol; + BOOL _downloaderImplementsSetProgress; + BOOL _downloaderImplementsSetPriority; + BOOL _cacherSupportsNewProtocol; } @end @@ -42,6 +52,18 @@ _cache = cache; _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; self.shouldBypassEnsureDisplay = YES; @@ -50,7 +72,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 @@ -129,6 +155,41 @@ [super displayWillStart]; [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 @@ -158,31 +219,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 +284,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 +292,18 @@ { 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) { strongSelf->_imageLoaded = YES; - strongSelf.image = [UIImage imageWithCGImage:responseImage]; + strongSelf.image = responseImage; } - strongSelf->_imageDownload = nil; + strongSelf->_downloadIdentifier = nil; strongSelf->_cacheUUID = nil; } @@ -241,22 +320,32 @@ 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 (_cacherSupportsNewProtocol) { + [_cache cachedImageWithURL:_URL + 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 { [self _downloadImageWithCompletion:finished]; } diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm index 73301919cc..983d5bc8bb 100644 --- a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -230,7 +230,7 @@ static const char *kContextKey = NSStringFromClass(ASBasicImageDownloaderContext - (id)downloadImageWithURL:(NSURL *)URL callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgressBlock:(void (^)(CGFloat))downloadProgressBlock + downloadProgress:(void (^)(CGFloat))downloadProgressBlock completion:(void (^)(CGImageRef, NSError *))completion { ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; diff --git a/AsyncDisplayKit/Details/ASImageProtocols.h b/AsyncDisplayKit/Details/ASImageProtocols.h index ba04eaaa75..405fdeb378 100644 --- a/AsyncDisplayKit/Details/ASImageProtocols.h +++ b/AsyncDisplayKit/Details/ASImageProtocols.h @@ -6,51 +6,51 @@ * of patent rights can be found in the PATENTS file in the same directory. */ -#import +#import #import NS_ASSUME_NONNULL_BEGIN +typedef void(^ASImageCacherCompletion)(UIImage * _Nullable imageFromCache); + @protocol ASImageCacheProtocol @required /** - @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. + @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. + @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 +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 @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 +58,58 @@ 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 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 diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h new file mode 100644 index 0000000000..1a7850b2fa --- /dev/null +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.h @@ -0,0 +1,16 @@ +// +// ASPINRemoteImageDownloader.h +// Pods +// +// Created by Garrett Moon on 2/5/16. +// +// + +#import +#import "ASImageProtocols.h" + +@interface ASPINRemoteImageDownloader : NSObject + ++ (instancetype)sharedDownloader; + +@end diff --git a/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m new file mode 100644 index 0000000000..e74baa16b6 --- /dev/null +++ b/AsyncDisplayKit/Details/ASPINRemoteImageDownloader.m @@ -0,0 +1,97 @@ +// +// ASPINRemoteImageDownloader.m +// Pods +// +// Created by Garrett Moon on 2/5/16. +// +// + +#ifdef PIN_REMOTE_IMAGE +#import "ASPINRemoteImageDownloader.h" +#import +#import + +@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 \ No newline at end of file diff --git a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m index 069b545d00..5a1d967787 100644 --- a/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m +++ b/AsyncDisplayKitTests/ASMultiplexImageNodeTests.m @@ -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; diff --git a/examples/Multiplex/Sample/ScreenNode.m b/examples/Multiplex/Sample/ScreenNode.m index b73368a03f..02df45922e 100644 --- a/examples/Multiplex/Sample/ScreenNode.m +++ b/examples/Multiplex/Sample/ScreenNode.m @@ -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;