diff --git a/Podfile.lock b/Podfile.lock index a0caeb263e..df3c6fcb88 100644 --- a/Podfile.lock +++ b/Podfile.lock @@ -1,38 +1,32 @@ PODS: - FBSnapshotTestCase/Core (2.1.4) - - FLAnimatedImage (1.0.12) - JGMethodSwizzler (2.0.1) - OCMock (3.4.1) - - PINCache (3.0.1-beta.6): - - PINCache/Arc-exception-safe (= 3.0.1-beta.6) - - PINCache/Core (= 3.0.1-beta.6) - - PINCache/Arc-exception-safe (3.0.1-beta.6): + - PINCache (3.0.1-beta.7): + - PINCache/Arc-exception-safe (= 3.0.1-beta.7) + - PINCache/Core (= 3.0.1-beta.7) + - PINCache/Arc-exception-safe (3.0.1-beta.7): - PINCache/Core - - PINCache/Core (3.0.1-beta.6): - - PINOperation (~> 1.1.0) + - PINCache/Core (3.0.1-beta.7): + - PINOperation (~> 1.1.1) - PINOperation (1.1.1) - - PINRemoteImage (3.0.0-beta.13): - - PINRemoteImage/FLAnimatedImage (= 3.0.0-beta.13) - - PINRemoteImage/PINCache (= 3.0.0-beta.13) - - PINRemoteImage/Core (3.0.0-beta.13): + - PINRemoteImage (3.0.0-beta.14): + - PINRemoteImage/PINCache (= 3.0.0-beta.14) + - PINRemoteImage/Core (3.0.0-beta.14): - PINOperation - - PINRemoteImage/FLAnimatedImage (3.0.0-beta.13): - - FLAnimatedImage (>= 1.0) - - PINRemoteImage/Core - - PINRemoteImage/PINCache (3.0.0-beta.13): - - PINCache (= 3.0.1-beta.6) + - PINRemoteImage/PINCache (3.0.0-beta.14): + - PINCache (= 3.0.1-beta.7) - PINRemoteImage/Core DEPENDENCIES: - FBSnapshotTestCase/Core (~> 2.1) - JGMethodSwizzler (from `https://github.com/JonasGessner/JGMethodSwizzler`, branch `master`) - OCMock (= 3.4.1) - - PINRemoteImage (= 3.0.0-beta.13) + - PINRemoteImage (= 3.0.0-beta.14) SPEC REPOS: https://github.com/cocoapods/specs.git: - FBSnapshotTestCase - - FLAnimatedImage - OCMock - PINCache - PINOperation @@ -50,13 +44,12 @@ CHECKOUT OPTIONS: SPEC CHECKSUMS: FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a - FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31 JGMethodSwizzler: 7328146117fffa8a4038c42eb7cd3d4c75006f97 OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3 - PINCache: d195fdba255283f7e9900a55e3cced377f431f9b + PINCache: 7cb9ae068c8f655717f7c644ef1dff9fd573e979 PINOperation: a6219e6fc9db9c269eb7a7b871ac193bcf400aac - PINRemoteImage: d6d51c5d2adda55f1ce30c96e850b6c4ebd2856a + PINRemoteImage: 81bbff853acc71c6de9e106e9e489a791b8bbb08 -PODFILE CHECKSUM: 42715d61f73cc22cc116bf80d7b268cb1f9e4742 +PODFILE CHECKSUM: 445046ac151568c694ff286684322273f0b597d6 -COCOAPODS: 1.5.3 +COCOAPODS: 1.6.0 diff --git a/Schemas/configuration.json b/Schemas/configuration.json index 2b95d767f0..64a4c790b2 100644 --- a/Schemas/configuration.json +++ b/Schemas/configuration.json @@ -25,7 +25,8 @@ "exp_disable_a11y_cache", "exp_skip_a11y_wait", "exp_new_default_cell_layout_mode", - "exp_dispatch_apply" + "exp_dispatch_apply", + "exp_image_downloader_priority" ] } } diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h index f78b4332c8..f3e6b295bd 100644 --- a/Source/ASExperimentalFeatures.h +++ b/Source/ASExperimentalFeatures.h @@ -31,6 +31,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { ASExperimentalSkipAccessibilityWait = 1 << 10, // exp_skip_a11y_wait ASExperimentalNewDefaultCellLayoutMode = 1 << 11, // exp_new_default_cell_layout_mode ASExperimentalDispatchApply = 1 << 12, // exp_dispatch_apply + ASExperimentalImageDownloaderPriority = 1 << 13, // exp_image_downloader_priority ASExperimentalFeatureAll = 0xFFFFFFFF }; diff --git a/Source/ASExperimentalFeatures.mm b/Source/ASExperimentalFeatures.mm index 0a8972fe34..d1f9a79667 100644 --- a/Source/ASExperimentalFeatures.mm +++ b/Source/ASExperimentalFeatures.mm @@ -24,8 +24,9 @@ NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags @"exp_disable_a11y_cache", @"exp_skip_a11y_wait", @"exp_new_default_cell_layout_mode", - @"exp_dispatch_apply"])); - + @"exp_dispatch_apply", + @"exp_image_downloader_priority"])); + if (flags == ASExperimentalFeatureAll) { return allNames; } diff --git a/Source/ASMultiplexImageNode.mm b/Source/ASMultiplexImageNode.mm index 2adc646f91..bd61dc02c4 100644 --- a/Source/ASMultiplexImageNode.mm +++ b/Source/ASMultiplexImageNode.mm @@ -53,7 +53,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent @private // Core. id _cache; + id _downloader; + struct { + unsigned int downloaderImplementsSetProgress:1; + unsigned int downloaderImplementsSetPriority:1; + unsigned int downloaderImplementsDownloadWithPriority:1; + } _downloaderFlags; __weak id _delegate; struct { @@ -89,8 +95,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent BOOL _shouldRenderProgressImages; //set on init only - BOOL _downloaderImplementsSetProgress; - BOOL _downloaderImplementsSetPriority; BOOL _cacheSupportsClearing; } @@ -171,13 +175,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _cache = (id)cache; _downloader = (id)downloader; - _downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; - _downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; + _downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)]; + _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; + _downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)]; _cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; _shouldRenderProgressImages = YES; - + self.shouldBypassEnsureDisplay = YES; return self; @@ -271,17 +276,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent - (void)displayWillStartAsynchronously:(BOOL)asynchronously { [super displayWillStartAsynchronously:asynchronously]; - [self didEnterPreloadState]; - - if (_downloaderImplementsSetPriority) { - { - ASDN::MutexLocker l(_downloadIdentifierLock); - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier]; - } - } - } + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityImminent]; } /* didEnterVisibleState / didExitVisibleState in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary @@ -289,31 +285,25 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent - (void)didEnterVisibleState { [super didEnterVisibleState]; - - if (_downloaderImplementsSetPriority) { - ASDN::MutexLocker l(_downloadIdentifierLock); - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier]; - } - } - + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityVisible]; [self _updateProgressImageBlockOnDownloaderIfNeeded]; } - (void)didExitVisibleState { [super didExitVisibleState]; - - if (_downloaderImplementsSetPriority) { - ASDN::MutexLocker l(_downloadIdentifierLock); - if (_downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier]; - } - } - + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; [self _updateProgressImageBlockOnDownloaderIfNeeded]; } +- (void)didExitDisplayState +{ + [super didExitDisplayState]; + if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; + } +} + #pragma mark - Core - (void)setImage:(UIImage *)image @@ -449,7 +439,6 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent _downloadIdentifier = downloadIdentifier; } - #pragma mark - Image Loading Machinery - (void)_loadImageIdentifiers @@ -493,19 +482,37 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent #pragma mark - -/** - @note: This should be called without _downloadIdentifierLock held. We will lock - super to read our interface state and it's best to avoid acquiring both locks. - */ +- (void)_updatePriorityOnDownloaderIfNeededWithDefaultPriority:(ASImageDownloaderPriority)defaultPriority +{ + ASAssertUnlocked(_downloadIdentifierLock); + + if (_downloaderFlags.downloaderImplementsSetPriority) { + // Read our interface state before locking so that we don't lock super while holding our lock. + ASInterfaceState interfaceState = self.interfaceState; + ASDN::MutexLocker l(_downloadIdentifierLock); + + if (_downloadIdentifier != nil) { + ASImageDownloaderPriority priority = defaultPriority; + if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { + priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); + } + + [_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier]; + } + } +} + - (void)_updateProgressImageBlockOnDownloaderIfNeeded { + ASAssertUnlocked(_downloadIdentifierLock); + BOOL shouldRenderProgressImages = self.shouldRenderProgressImages; // Read our interface state before locking so that we don't lock super while holding our lock. ASInterfaceState interfaceState = self.interfaceState; ASDN::MutexLocker l(_downloadIdentifierLock); - if (!_downloaderImplementsSetProgress || _downloadIdentifier == nil) { + if (!_downloaderFlags.downloaderImplementsSetProgress || _downloadIdentifier == nil) { return; } @@ -825,7 +832,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent [_delegate multiplexImageNode:self didStartDownloadOfImageWithIdentifier:imageIdentifier]; __weak __typeof__(self) weakSelf = self; - void (^downloadProgressBlock)(CGFloat) = nil; + ASImageDownloaderProgress downloadProgressBlock = NULL; if (_delegateFlags.downloadProgress) { downloadProgressBlock = ^(CGFloat progress) { __typeof__(self) strongSelf = weakSelf; @@ -835,30 +842,67 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent }; } + ASImageDownloaderCompletion completion = ^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { + // We dereference iVars directly, so we can't have weakSelf going nil on us. + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) + return; + + ASDN::MutexLocker l(strongSelf->_downloadIdentifierLock); + //Getting a result back for a different download identifier, download must not have been successfully canceled + if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { + return; + } + + completionBlock([imageContainer asdk_image], error); + + // Delegateify. + if (strongSelf->_delegateFlags.downloadFinish) + [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; + }; + // Download! ASPerformBlockOnBackgroundThread(^{ - [self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL - callbackQueue:dispatch_get_main_queue() - downloadProgress:downloadProgressBlock - completion:^(id imageContainer, NSError *error, id downloadIdentifier, id userInfo) { - // We dereference iVars directly, so we can't have weakSelf going nil on us. - __typeof__(self) strongSelf = weakSelf; - if (!strongSelf) - return; - - ASDN::MutexLocker l(_downloadIdentifierLock); - //Getting a result back for a different download identifier, download must not have been successfully canceled - if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) { - return; - } - - completionBlock([imageContainer asdk_image], error); - - // Delegateify. - if (strongSelf->_delegateFlags.downloadFinish) - [strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error]; - }]]; - [self _updateProgressImageBlockOnDownloaderIfNeeded]; + __typeof__(self) strongSelf = weakSelf; + if (!strongSelf) + return; + + dispatch_queue_t callbackQueue = dispatch_get_main_queue(); + + id downloadIdentifier; + if (strongSelf->_downloaderFlags.downloaderImplementsDownloadWithPriority + && ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { + + /* + Decide a priority based on the current interface state of this node. + It can happen that this method was called when the node entered preload state + but the interface state, at this point, tells us that the node is (going to be) visible, + If that's the case, we jump to a higher priority directly. + */ + ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(strongSelf.interfaceState); + downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL + priority:priority + callbackQueue:callbackQueue + downloadProgress:downloadProgressBlock + completion:completion]; + } else { + /* + Kick off a download with default priority. + The actual "default" value is decided by the downloader. + ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent + which is mapped to NSURLSessionTaskPriorityDefault. + + This means that preload and display nodes use the same priority + and their requests are put into the same pool. + */ + downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL + callbackQueue:callbackQueue + downloadProgress:downloadProgressBlock + completion:completion]; + } + + [strongSelf _setDownloadIdentifier:downloadIdentifier]; + [strongSelf _updateProgressImageBlockOnDownloaderIfNeeded]; }); } diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index f196844594..e7910cd780 100644 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -68,6 +68,7 @@ unsigned int downloaderImplementsSetPriority:1; unsigned int downloaderImplementsAnimatedImage:1; unsigned int downloaderImplementsCancelWithResume:1; + unsigned int downloaderImplementsDownloadWithPriority:1; } _downloaderFlags; // Immutable and set on init only. We don't need to lock in this case. @@ -98,6 +99,7 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); _downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)]; _downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)]; _downloaderFlags.downloaderImplementsCancelWithResume = [downloader respondsToSelector:@selector(cancelImageDownloadWithResumePossibilityForIdentifier:)]; + _downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)]; _cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)]; _cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)]; @@ -369,11 +371,8 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); } } - if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) { - id downloadIdentifier = ASLockedSelf(_downloadIdentifier); - if (downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:downloadIdentifier]; - } + if (self.image == nil) { + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityImminent]; } } @@ -382,35 +381,25 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); - (void)didEnterVisibleState { [super didEnterVisibleState]; - - id downloadIdentifier = ({ - ASLockScopeSelf(); - _downloaderFlags.downloaderImplementsSetPriority ? _downloadIdentifier : nil; - }); - - if (downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:downloadIdentifier]; - } - + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityVisible]; [self _updateProgressImageBlockOnDownloaderIfNeeded]; } - (void)didExitVisibleState { [super didExitVisibleState]; - - id downloadIdentifier = ({ - ASLockScopeSelf(); - _downloaderFlags.downloaderImplementsSetPriority ? _downloadIdentifier : nil; - }); - - if (downloadIdentifier != nil) { - [_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:downloadIdentifier]; - } - + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; [self _updateProgressImageBlockOnDownloaderIfNeeded]; } +- (void)didExitDisplayState +{ + [super didExitDisplayState]; + if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { + [self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload]; + } +} + - (void)didExitPreloadState { [super didExitPreloadState]; @@ -457,6 +446,22 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); [self _locked__setImage:progressImage]; } +- (void)_updatePriorityOnDownloaderIfNeededWithDefaultPriority:(ASImageDownloaderPriority)defaultPriority +{ + if (_downloaderFlags.downloaderImplementsSetPriority) { + ASLockScopeSelf(); + + if (_downloadIdentifier != nil) { + ASImageDownloaderPriority priority = defaultPriority; + if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { + priority = ASImageDownloaderPriorityWithInterfaceState(_interfaceState); + } + + [_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier]; + } + } +} + - (void)_updateProgressImageBlockOnDownloaderIfNeeded { // If the downloader doesn't do progress, we are done. @@ -574,24 +579,55 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); NSURL *url; id downloadIdentifier; BOOL cancelAndReattempt = NO; - + ASInterfaceState interfaceState; + // Below, to avoid performance issues, we're calling downloadImageWithURL without holding the lock. This is a bit ugly because // We need to reobtain the lock after and ensure that the task we've kicked off still matches our URL. If not, we need to cancel // it and try again. { ASLockScopeSelf(); url = self->_URL; + interfaceState = self->_interfaceState; } + dispatch_queue_t callbackQueue = [self callbackQueue]; + ASImageDownloaderProgress downloadProgress = NULL; + ASImageDownloaderCompletion completion = ^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { + if (finished != NULL) { + finished(imageContainer, error, downloadIdentifier, userInfo); + } + }; - downloadIdentifier = [self->_downloader downloadImageWithURL:url - callbackQueue:[self callbackQueue] - downloadProgress:NULL - completion:^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { - if (finished != NULL) { - finished(imageContainer, error, downloadIdentifier, userInfo); - } - }]; + if (self->_downloaderFlags.downloaderImplementsDownloadWithPriority + && ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) { + /* + Decide a priority based on the current interface state of this node. + It can happen that this method was called when the node entered preload state + but the interface state, at this point, tells us that the node is (going to be) visible. + If that's the case, we jump to a higher priority directly. + */ + ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState); + + downloadIdentifier = [self->_downloader downloadImageWithURL:url + priority:priority + callbackQueue:callbackQueue + downloadProgress:downloadProgress + completion:completion]; + } else { + /* + Kick off a download with default priority. + The actual "default" value is decided by the downloader. + ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent + which is mapped to NSURLSessionTaskPriorityDefault. + + This means that preload and display nodes use the same priority + and their requests are put into the same pool. + */ + downloadIdentifier = [self->_downloader downloadImageWithURL:url + callbackQueue:callbackQueue + downloadProgress:downloadProgress + completion:completion]; + } as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url); { diff --git a/Source/Details/ASBasicImageDownloader.mm b/Source/Details/ASBasicImageDownloader.mm index ae92c53508..6c4010f573 100644 --- a/Source/Details/ASBasicImageDownloader.mm +++ b/Source/Details/ASBasicImageDownloader.mm @@ -9,6 +9,7 @@ #import +#import #import #import @@ -25,6 +26,19 @@ NSString * const kASBasicImageDownloaderContextCallbackQueue = @"kASBasicImageDo NSString * const kASBasicImageDownloaderContextProgressBlock = @"kASBasicImageDownloaderContextProgressBlock"; NSString * const kASBasicImageDownloaderContextCompletionBlock = @"kASBasicImageDownloaderContextCompletionBlock"; +static inline float NSURLSessionTaskPriorityWithImageDownloaderPriority(ASImageDownloaderPriority priority) { + switch (priority) { + case ASImageDownloaderPriorityPreload: + return NSURLSessionTaskPriorityLow; + + case ASImageDownloaderPriorityImminent: + return NSURLSessionTaskPriorityDefault; + + case ASImageDownloaderPriorityVisible: + return NSURLSessionTaskPriorityHigh; + } +} + @interface ASBasicImageDownloaderContext () { BOOL _invalid; @@ -238,10 +252,23 @@ static const void *ContextKey() { #pragma mark ASImageDownloaderProtocol. -- (id)downloadImageWithURL:(NSURL *)URL - callbackQueue:(dispatch_queue_t)callbackQueue - downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress - completion:(ASImageDownloaderCompletion)completion +- (nullable id)downloadImageWithURL:(NSURL *)URL + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion +{ + return [self downloadImageWithURL:URL + priority:ASImageDownloaderPriorityImminent // maps to default priority + callbackQueue:callbackQueue + downloadProgress:downloadProgress + completion:completion]; +} + +- (nullable id)downloadImageWithURL:(NSURL *)URL + priority:(ASImageDownloaderPriority)priority + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion { ASBasicImageDownloaderContext *context = [ASBasicImageDownloaderContext contextForURL:URL]; @@ -266,6 +293,7 @@ static const void *ContextKey() { NSURLSessionDownloadTask *task = (NSURLSessionDownloadTask *)[context createSessionTaskIfNecessaryWithBlock:^(){return [_session downloadTaskWithURL:URL];}]; if (task) { + task.priority = NSURLSessionTaskPriorityWithImageDownloaderPriority(priority); task.originalRequest.asyncdisplaykit_context = context; // start downloading diff --git a/Source/Details/ASImageProtocols.h b/Source/Details/ASImageProtocols.h index 10ad85db35..fae3b9f5b4 100644 --- a/Source/Details/ASImageProtocols.h +++ b/Source/Details/ASImageProtocols.h @@ -102,17 +102,35 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { /** @abstract Cancels an image download. @param downloadIdentifier The opaque download identifier object returned from - `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. + `downloadImageWithURL:callbackQueue:downloadProgress:completion:`. @discussion This method has no effect if `downloadIdentifier` is nil. */ - (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier; @optional +/** + @abstract Downloads an image with the given URL. + @param URL The URL of the image to download. + @param priority The priority at which the image should be downloaded. + @param callbackQueue The queue to call `downloadProgressBlock` and `completion` on. + @param downloadProgress The block to be invoked when the download of `URL` progresses. + @param completion The block to be invoked when the download has completed, or has failed. + @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. + @note If this method is implemented, it will be called instead of the required method (`downloadImageWithURL:callbackQueue:downloadProgress:completion:`). + @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 + priority:(ASImageDownloaderPriority)priority + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(nullable ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion; + /** @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:`. + `downloadImageWithURL:callbackQueue:downloadProgress: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 @@ -130,7 +148,7 @@ typedef NS_ENUM(NSUInteger, ASImageDownloaderPriority) { /** @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 callbackQueue The queue to call `progressImageBlock` on. @param downloadIdentifier The opaque download identifier object returned from `downloadImageWithURL:callbackQueue:downloadProgressBlock:completion:`. */ diff --git a/Source/Details/ASPINRemoteImageDownloader.mm b/Source/Details/ASPINRemoteImageDownloader.mm index 0276946f98..3daf06e336 100644 --- a/Source/Details/ASPINRemoteImageDownloader.mm +++ b/Source/Details/ASPINRemoteImageDownloader.mm @@ -34,6 +34,19 @@ #import #import +static inline PINRemoteImageManagerPriority PINRemoteImageManagerPriorityWithASImageDownloaderPriority(ASImageDownloaderPriority priority) { + switch (priority) { + case ASImageDownloaderPriorityPreload: + return PINRemoteImageManagerPriorityLow; + + case ASImageDownloaderPriorityImminent: + return PINRemoteImageManagerPriorityDefault; + + case ASImageDownloaderPriorityVisible: + return PINRemoteImageManagerPriorityHigh; + } +} + #if PIN_ANIMATED_AVAILABLE @interface ASPINRemoteImageDownloader () @@ -245,6 +258,21 @@ static dispatch_once_t shared_init_predicate; downloadProgress:(ASImageDownloaderProgress)downloadProgress completion:(ASImageDownloaderCompletion)completion; { + return [self downloadImageWithURL:URL + priority:ASImageDownloaderPriorityImminent // maps to default priority + callbackQueue:callbackQueue + downloadProgress:downloadProgress + completion:completion]; +} + +- (nullable id)downloadImageWithURL:(NSURL *)URL + priority:(ASImageDownloaderPriority)priority + callbackQueue:(dispatch_queue_t)callbackQueue + downloadProgress:(ASImageDownloaderProgress)downloadProgress + completion:(ASImageDownloaderCompletion)completion +{ + PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); + PINRemoteImageManagerProgressDownload progressDownload = ^(int64_t completedBytes, int64_t totalBytes) { if (downloadProgress == nil) { return; } @@ -274,6 +302,7 @@ static dispatch_once_t shared_init_predicate; // check the cache as part of this download. return [[self sharedPINRemoteImageManager] downloadImageWithURL:URL options:PINRemoteImageManagerDownloadOptionsSkipDecode | PINRemoteImageManagerDownloadOptionsIgnoreCache + priority:pi_priority progressImage:nil progressDownload:progressDownload completion:imageCompletion]; @@ -310,20 +339,7 @@ static dispatch_once_t shared_init_predicate; { ASDisplayNodeAssert([downloadIdentifier isKindOfClass:[NSUUID class]], @"downloadIdentifier must be NSUUID"); - PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityDefault; - switch (priority) { - case ASImageDownloaderPriorityPreload: - pi_priority = PINRemoteImageManagerPriorityLow; - break; - - case ASImageDownloaderPriorityImminent: - pi_priority = PINRemoteImageManagerPriorityDefault; - break; - - case ASImageDownloaderPriorityVisible: - pi_priority = PINRemoteImageManagerPriorityHigh; - break; - } + PINRemoteImageManagerPriority pi_priority = PINRemoteImageManagerPriorityWithASImageDownloaderPriority(priority); [[self sharedPINRemoteImageManager] setPriority:pi_priority ofTaskWithUUID:downloadIdentifier]; } diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index e6d3811c79..8bc53fa0e6 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -12,6 +12,8 @@ #import #import +#import +#import NS_ASSUME_NONNULL_BEGIN @@ -95,6 +97,22 @@ ASDISPLAYNODE_INLINE UIEdgeInsets ASConcatInsets(UIEdgeInsets insetsA, UIEdgeIns return insetsA; } +ASDISPLAYNODE_INLINE AS_WARN_UNUSED_RESULT ASImageDownloaderPriority ASImageDownloaderPriorityWithInterfaceState(ASInterfaceState interfaceState) { + if (ASInterfaceStateIncludesVisible(interfaceState)) { + return ASImageDownloaderPriorityVisible; + } + + if (ASInterfaceStateIncludesDisplay(interfaceState)) { + return ASImageDownloaderPriorityImminent; + } + + if (ASInterfaceStateIncludesPreload(interfaceState)) { + return ASImageDownloaderPriorityPreload; + } + + return ASImageDownloaderPriorityPreload; +} + @interface NSIndexPath (ASInverseComparison) - (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath; @end diff --git a/Tests/ASConfigurationTests.mm b/Tests/ASConfigurationTests.mm index aff3091761..a5f37c11c3 100644 --- a/Tests/ASConfigurationTests.mm +++ b/Tests/ASConfigurationTests.mm @@ -30,7 +30,8 @@ static ASExperimentalFeatures features[] = { ASExperimentalDisableAccessibilityCache, ASExperimentalSkipAccessibilityWait, ASExperimentalNewDefaultCellLayoutMode, - ASExperimentalDispatchApply + ASExperimentalDispatchApply, + ASExperimentalImageDownloaderPriority }; @interface ASConfigurationTests : ASTestCase @@ -55,7 +56,8 @@ static ASExperimentalFeatures features[] = { @"exp_disable_a11y_cache", @"exp_skip_a11y_wait", @"exp_new_default_cell_layout_mode", - @"exp_dispatch_apply" + @"exp_dispatch_apply", + @"exp_image_downloader_priority" ]; }