From 68a8d5f4683f8f1b99be9426c4b0329af09ed183 Mon Sep 17 00:00:00 2001 From: Garrett Moon Date: Mon, 10 Apr 2017 11:18:05 -0700 Subject: [PATCH] Add download resume support (#3246) * Adds support for resuming downloads that were canceled due to exiting preload range. * Update to latest PINRemoteImage * Fix warnings * Address comments. Thanks @nguyenhuy! --- AsyncDisplayKit.podspec | 2 +- Source/ASNetworkImageNode.mm | 30 ++++++++++++--------- Source/Details/ASImageProtocols.h | 11 ++++++++ Source/Details/ASPINRemoteImageDownloader.m | 14 +++++++--- 4 files changed, 40 insertions(+), 17 deletions(-) diff --git a/AsyncDisplayKit.podspec b/AsyncDisplayKit.podspec index 7bd40f7969..e4664289f4 100644 --- a/AsyncDisplayKit.podspec +++ b/AsyncDisplayKit.podspec @@ -44,7 +44,7 @@ Pod::Spec.new do |spec| end spec.subspec 'PINRemoteImage' do |pin| - pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.8' + pin.dependency 'PINRemoteImage/iOS', '= 3.0.0-beta.9' pin.dependency 'PINRemoteImage/PINCache' pin.dependency 'AsyncDisplayKit/Core' end diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 71254dac44..ce6a424589 100755 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -58,6 +58,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; unsigned int downloaderImplementsSetProgress:1; unsigned int downloaderImplementsSetPriority:1; unsigned int downloaderImplementsAnimatedImage:1; + unsigned int downloaderImplementsCancelWithResume:1; } _downloaderFlags; // Immutable and set on init only. We don't need to lock in this case. @@ -85,6 +86,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; + _downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)]; _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; @@ -107,7 +109,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; - (void)dealloc { - [self _cancelImageDownload]; + [self _cancelImageDownloadWithResumePossibility:NO]; } #pragma mark - Public methods -- must lock @@ -127,7 +129,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; if (shouldCancelAndClear) { ASDisplayNodeAssertNil(_URL, @"Directly setting an image on an ASNetworkImageNode causes it to behave like an ASImageNode instead of an ASNetworkImageNode. If this is what you want, set the URL to nil first."); _URL = nil; - [self _locked_cancelDownloadAndClearImage]; + [self _locked_cancelDownloadAndClearImageWithResumePossibility:NO]; } [self _locked__setImage:image]; @@ -163,7 +165,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; return; } - [self _locked_cancelImageDownload]; + [self _locked_cancelImageDownloadWithResumePossibility:NO]; _imageLoaded = NO; @@ -385,7 +387,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; return; } - [self _cancelDownloadAndClearImage]; + [self _cancelDownloadAndClearImageWithResumePossibility:YES]; } - (void)didEnterPreloadState @@ -469,15 +471,15 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; } } -- (void)_cancelDownloadAndClearImage +- (void)_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume { ASDN::MutexLocker l(__instanceLock__); - [self _locked_cancelDownloadAndClearImage]; + [self _locked_cancelDownloadAndClearImageWithResumePossibility:storeResume]; } -- (void)_locked_cancelDownloadAndClearImage +- (void)_locked_cancelDownloadAndClearImageWithResumePossibility:(BOOL)storeResume { - [self _locked_cancelImageDownload]; + [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; // Destruction of bigger images on the main thread can be expensive // and can take some time, so we dispatch onto a bg queue to @@ -504,20 +506,24 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0}; } } -- (void)_cancelImageDownload +- (void)_cancelImageDownloadWithResumePossibility:(BOOL)storeResume { ASDN::MutexLocker l(__instanceLock__); - [self _locked_cancelImageDownload]; + [self _locked_cancelImageDownloadWithResumePossibility:storeResume]; } -- (void)_locked_cancelImageDownload +- (void)_locked_cancelImageDownloadWithResumePossibility:(BOOL)storeResume { if (!_downloadIdentifier) { return; } if (_downloadIdentifier) { - [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; + if (storeResume && _downloaderFlags.downloaderImplementsCancelWithResume) { + [_downloader cancelImageDownloadWithResumePossibilityForIdentifier:_downloadIdentifier]; + } else { + [_downloader cancelImageDownloadForIdentifier:_downloadIdentifier]; + } } _downloadIdentifier = nil; diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index dfd1f82c42..435cf0a221 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -109,6 +109,17 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { @optional +/** + @abstract Cancels an image download, however indicating resume data should be stored in case of redownload. + @param downloadIdentifier The opaque download identifier object returned from + `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. + @discussion This method has no effect if `downloadIdentifier` is nil. If implemented, this method + may be called instead of `cancelImageDownloadForIdentifier:` in cases where ASDK believes there's a chance + the image download will be resumed (currently when an image exits preload range). You can use this to store + any data that has already been downloaded for use in resuming the download later. + */ +- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier; + /** @abstract Return an object that conforms to ASAnimatedImageProtocol @param animatedImageData Data that represents an animated image. diff --git a/Source/Details/ASPINRemoteImageDownloader.m b/Source/Details/ASPINRemoteImageDownloader.m index b35875d9a0..c88e8218da 100644 --- a/Source/Details/ASPINRemoteImageDownloader.m +++ b/Source/Details/ASPINRemoteImageDownloader.m @@ -245,7 +245,13 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil; - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier { ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier]; + [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:NO]; +} + +- (void)cancelImageDownloadWithResumePossibilityForIdentifier:(id)downloadIdentifier +{ + ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); + [[self sharedPINRemoteImageManager] cancelTaskWithUUID:downloadIdentifier storeResumeData:YES]; } - (void)setProgressImageBlock:(ASImageDownloaderProgressImage)progressBlock callbackQueue:(dispatch_queue_t)callbackQueue withDownloadIdentifier:(id)downloadIdentifier @@ -270,15 +276,15 @@ static ASPINRemoteImageDownloader *sharedDownloader = nil; PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityMedium; switch (priority) { case ASImageDownloaderPriorityPreload: - pi_priority = PINRemoteImageManagerPriorityMedium; + pi_priority = PINRemoteImageManagerPriorityLow; break; case ASImageDownloaderPriorityImminent: - pi_priority = PINRemoteImageManagerPriorityHigh; + pi_priority = PINRemoteImageManagerPriorityDefault; break; case ASImageDownloaderPriorityVisible: - pi_priority = PINRemoteImageManagerPriorityVeryHigh; + pi_priority = PINRemoteImageManagerPriorityHigh; break; } [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier];