Experiment with different strategies for image downloader priority (#1349)

Right now when an image node enters preload state, we kick off an image request with the default priority. Then when it enters display state, we change the priority to "imminent" which is mapped to the default priority as well. This means that requests from preload and display nodes have the same priority and are put to the same pool. The right behavior would be that preload requests should have a lower priority from the beginning.

Another problem is that, due to the execution order of -didEnter(Preload|Display|Visible)State calls, a node may kick off a low priority request when it enters preload state even though it knows that it's also visible. By the time -didEnterVisibleState is called, the low priority request may have already been consumed and the download/data task won't pick up the new higher priority, or some work needs to be done to move it to another queue. A better behavior would be to always use the current interface state to determine the priority. This means that visible nodes will kick off high priority requests as soon as -didEnterPreloadState is called.

The last (and smaller) issue is that a node marks its request as preload/low priority as soon as it exits visible state. I'd argue that this is too agressive. It may be reasonble for nodes in the trailing direction. Even so, we already handle this case by (almost always) have smaller trailing buffers. So this diff makes sure that nodes that exited visible state will have imminent/default priority if they remain in the display range.

All of these new behaviors are wrapped in an experiment and will be tested carefully before being rolled out.

* Add imports

* Fix build failure

* Encapsulate common logics into methods

* Address comments
This commit is contained in:
Huy Nguyen 2019-03-08 08:11:03 -08:00 committed by GitHub
parent 5e8721ebe8
commit d102ec81ee
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 301 additions and 143 deletions

View File

@ -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

View File

@ -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"
]
}
}

View File

@ -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
};

View File

@ -24,8 +24,9 @@ NSArray<NSString *> *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;
}

View File

@ -53,7 +53,13 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
@private
// Core.
id<ASImageCacheProtocol> _cache;
id<ASImageDownloaderProtocol> _downloader;
struct {
unsigned int downloaderImplementsSetProgress:1;
unsigned int downloaderImplementsSetPriority:1;
unsigned int downloaderImplementsDownloadWithPriority:1;
} _downloaderFlags;
__weak id<ASMultiplexImageNodeDelegate> _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<ASImageCacheProtocol>)cache;
_downloader = (id<ASImageDownloaderProtocol>)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 <ASImageContainerProtocol> 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 <ASImageContainerProtocol> 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];
});
}

View File

@ -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 <ASImageContainerProtocol> _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 <ASImageContainerProtocol> _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);
{

View File

@ -9,6 +9,7 @@
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
#import <Foundation/NSURLSession.h>
#import <objc/runtime.h>
#import <AsyncDisplayKit/ASBasicImageDownloaderInternal.h>
@ -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

View File

@ -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:`.
*/

View File

@ -34,6 +34,19 @@
#import <PINRemoteImage/NSData+ImageDetectors.h>
#import <PINRemoteImage/PINRemoteImageCaching.h>
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 () <PINRemoteImageManagerAlternateRepresentationProvider>
@ -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];
}

View File

@ -12,6 +12,8 @@
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASImageProtocols.h>
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

View File

@ -30,7 +30,8 @@ static ASExperimentalFeatures features[] = {
ASExperimentalDisableAccessibilityCache,
ASExperimentalSkipAccessibilityWait,
ASExperimentalNewDefaultCellLayoutMode,
ASExperimentalDispatchApply
ASExperimentalDispatchApply,
ASExperimentalImageDownloaderPriority
};
@interface ASConfigurationTests : ASTestCase <ASConfigurationDelegate>
@ -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"
];
}