mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-01 10:23:15 +00:00
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:
parent
5e8721ebe8
commit
d102ec81ee
39
Podfile.lock
39
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
|
||||
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
};
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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];
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
|
||||
{
|
||||
|
@ -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
|
||||
|
@ -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:`.
|
||||
*/
|
||||
|
@ -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];
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
];
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user