-
Notifications
You must be signed in to change notification settings - Fork 1.3k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Experiment with different strategies for image downloader priority #1349
Changes from all commits
0454fd6
1df0049
3848174
6a1f368
d7413c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -53,7 +53,13 @@ @interface ASMultiplexImageNode () | |
@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 @@ @interface ASMultiplexImageNode () | |
BOOL _shouldRenderProgressImages; | ||
|
||
//set on init only | ||
BOOL _downloaderImplementsSetProgress; | ||
BOOL _downloaderImplementsSetPriority; | ||
BOOL _cacheSupportsClearing; | ||
} | ||
|
||
|
@@ -171,13 +175,14 @@ - (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASI | |
_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,49 +276,34 @@ - (BOOL)placeholderShouldPersist | |
- (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 | ||
in ASNetworkImageNode as well. */ | ||
- (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 @@ - (void)_setDownloadIdentifier:(id)downloadIdentifier | |
_downloadIdentifier = downloadIdentifier; | ||
} | ||
|
||
|
||
#pragma mark - Image Loading Machinery | ||
|
||
- (void)_loadImageIdentifiers | ||
|
@@ -493,19 +482,37 @@ - (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierO | |
|
||
#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 @@ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL c | |
[_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 @@ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL c | |
}; | ||
} | ||
|
||
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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we call this we a priority also so that it do not need to be called differently in if-else? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd like to let the concrete downloader to decide what the default priority is. Right now both |
||
callbackQueue:callbackQueue | ||
downloadProgress:downloadProgressBlock | ||
completion:completion]; | ||
} | ||
|
||
[strongSelf _setDownloadIdentifier:downloadIdentifier]; | ||
[strongSelf _updateProgressImageBlockOnDownloaderIfNeeded]; | ||
}); | ||
} | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just reminder that in thrash case (without coalescing) a view may exit and enter immediately. It seems ok in this case though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good call. I think the right fix is coalescing in such case to avoid jumping between interface states unnecessarily.