Skip to content

Commit d102ec8

Browse files
authored
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
1 parent 5e8721e commit d102ec8

11 files changed

Lines changed: 301 additions & 143 deletions

Podfile.lock

Lines changed: 16 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,32 @@
11
PODS:
22
- FBSnapshotTestCase/Core (2.1.4)
3-
- FLAnimatedImage (1.0.12)
43
- JGMethodSwizzler (2.0.1)
54
- OCMock (3.4.1)
6-
- PINCache (3.0.1-beta.6):
7-
- PINCache/Arc-exception-safe (= 3.0.1-beta.6)
8-
- PINCache/Core (= 3.0.1-beta.6)
9-
- PINCache/Arc-exception-safe (3.0.1-beta.6):
5+
- PINCache (3.0.1-beta.7):
6+
- PINCache/Arc-exception-safe (= 3.0.1-beta.7)
7+
- PINCache/Core (= 3.0.1-beta.7)
8+
- PINCache/Arc-exception-safe (3.0.1-beta.7):
109
- PINCache/Core
11-
- PINCache/Core (3.0.1-beta.6):
12-
- PINOperation (~> 1.1.0)
10+
- PINCache/Core (3.0.1-beta.7):
11+
- PINOperation (~> 1.1.1)
1312
- PINOperation (1.1.1)
14-
- PINRemoteImage (3.0.0-beta.13):
15-
- PINRemoteImage/FLAnimatedImage (= 3.0.0-beta.13)
16-
- PINRemoteImage/PINCache (= 3.0.0-beta.13)
17-
- PINRemoteImage/Core (3.0.0-beta.13):
13+
- PINRemoteImage (3.0.0-beta.14):
14+
- PINRemoteImage/PINCache (= 3.0.0-beta.14)
15+
- PINRemoteImage/Core (3.0.0-beta.14):
1816
- PINOperation
19-
- PINRemoteImage/FLAnimatedImage (3.0.0-beta.13):
20-
- FLAnimatedImage (>= 1.0)
21-
- PINRemoteImage/Core
22-
- PINRemoteImage/PINCache (3.0.0-beta.13):
23-
- PINCache (= 3.0.1-beta.6)
17+
- PINRemoteImage/PINCache (3.0.0-beta.14):
18+
- PINCache (= 3.0.1-beta.7)
2419
- PINRemoteImage/Core
2520

2621
DEPENDENCIES:
2722
- FBSnapshotTestCase/Core (~> 2.1)
2823
- JGMethodSwizzler (from `https://github.com/JonasGessner/JGMethodSwizzler`, branch `master`)
2924
- OCMock (= 3.4.1)
30-
- PINRemoteImage (= 3.0.0-beta.13)
25+
- PINRemoteImage (= 3.0.0-beta.14)
3126

3227
SPEC REPOS:
3328
https://github.com/cocoapods/specs.git:
3429
- FBSnapshotTestCase
35-
- FLAnimatedImage
3630
- OCMock
3731
- PINCache
3832
- PINOperation
@@ -50,13 +44,12 @@ CHECKOUT OPTIONS:
5044

5145
SPEC CHECKSUMS:
5246
FBSnapshotTestCase: 094f9f314decbabe373b87cc339bea235a63e07a
53-
FLAnimatedImage: 4a0b56255d9b05f18b6dd7ee06871be5d3b89e31
5447
JGMethodSwizzler: 7328146117fffa8a4038c42eb7cd3d4c75006f97
5548
OCMock: 2cd0716969bab32a2283ff3a46fd26a8c8b4c5e3
56-
PINCache: d195fdba255283f7e9900a55e3cced377f431f9b
49+
PINCache: 7cb9ae068c8f655717f7c644ef1dff9fd573e979
5750
PINOperation: a6219e6fc9db9c269eb7a7b871ac193bcf400aac
58-
PINRemoteImage: d6d51c5d2adda55f1ce30c96e850b6c4ebd2856a
51+
PINRemoteImage: 81bbff853acc71c6de9e106e9e489a791b8bbb08
5952

60-
PODFILE CHECKSUM: 42715d61f73cc22cc116bf80d7b268cb1f9e4742
53+
PODFILE CHECKSUM: 445046ac151568c694ff286684322273f0b597d6
6154

62-
COCOAPODS: 1.5.3
55+
COCOAPODS: 1.6.0

Schemas/configuration.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"exp_disable_a11y_cache",
2626
"exp_skip_a11y_wait",
2727
"exp_new_default_cell_layout_mode",
28-
"exp_dispatch_apply"
28+
"exp_dispatch_apply",
29+
"exp_image_downloader_priority"
2930
]
3031
}
3132
}

Source/ASExperimentalFeatures.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) {
3131
ASExperimentalSkipAccessibilityWait = 1 << 10, // exp_skip_a11y_wait
3232
ASExperimentalNewDefaultCellLayoutMode = 1 << 11, // exp_new_default_cell_layout_mode
3333
ASExperimentalDispatchApply = 1 << 12, // exp_dispatch_apply
34+
ASExperimentalImageDownloaderPriority = 1 << 13, // exp_image_downloader_priority
3435
ASExperimentalFeatureAll = 0xFFFFFFFF
3536
};
3637

Source/ASExperimentalFeatures.mm

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,9 @@
2424
@"exp_disable_a11y_cache",
2525
@"exp_skip_a11y_wait",
2626
@"exp_new_default_cell_layout_mode",
27-
@"exp_dispatch_apply"]));
28-
27+
@"exp_dispatch_apply",
28+
@"exp_image_downloader_priority"]));
29+
2930
if (flags == ASExperimentalFeatureAll) {
3031
return allNames;
3132
}

Source/ASMultiplexImageNode.mm

Lines changed: 104 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,13 @@ @interface ASMultiplexImageNode ()
5353
@private
5454
// Core.
5555
id<ASImageCacheProtocol> _cache;
56+
5657
id<ASImageDownloaderProtocol> _downloader;
58+
struct {
59+
unsigned int downloaderImplementsSetProgress:1;
60+
unsigned int downloaderImplementsSetPriority:1;
61+
unsigned int downloaderImplementsDownloadWithPriority:1;
62+
} _downloaderFlags;
5763

5864
__weak id<ASMultiplexImageNodeDelegate> _delegate;
5965
struct {
@@ -89,8 +95,6 @@ @interface ASMultiplexImageNode ()
8995
BOOL _shouldRenderProgressImages;
9096

9197
//set on init only
92-
BOOL _downloaderImplementsSetProgress;
93-
BOOL _downloaderImplementsSetPriority;
9498
BOOL _cacheSupportsClearing;
9599
}
96100

@@ -171,13 +175,14 @@ - (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASI
171175
_cache = (id<ASImageCacheProtocol>)cache;
172176
_downloader = (id<ASImageDownloaderProtocol>)downloader;
173177

174-
_downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)];
175-
_downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];
178+
_downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)];
179+
_downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];
180+
_downloaderFlags.downloaderImplementsDownloadWithPriority = [downloader respondsToSelector:@selector(downloadImageWithURL:priority:callbackQueue:downloadProgress:completion:)];
176181

177182
_cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)];
178183

179184
_shouldRenderProgressImages = YES;
180-
185+
181186
self.shouldBypassEnsureDisplay = YES;
182187

183188
return self;
@@ -271,49 +276,34 @@ - (BOOL)placeholderShouldPersist
271276
- (void)displayWillStartAsynchronously:(BOOL)asynchronously
272277
{
273278
[super displayWillStartAsynchronously:asynchronously];
274-
275279
[self didEnterPreloadState];
276-
277-
if (_downloaderImplementsSetPriority) {
278-
{
279-
ASDN::MutexLocker l(_downloadIdentifierLock);
280-
if (_downloadIdentifier != nil) {
281-
[_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier];
282-
}
283-
}
284-
}
280+
[self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityImminent];
285281
}
286282

287283
/* didEnterVisibleState / didExitVisibleState in ASNetworkImageNode has a very similar implementation. Changes here are likely necessary
288284
in ASNetworkImageNode as well. */
289285
- (void)didEnterVisibleState
290286
{
291287
[super didEnterVisibleState];
292-
293-
if (_downloaderImplementsSetPriority) {
294-
ASDN::MutexLocker l(_downloadIdentifierLock);
295-
if (_downloadIdentifier != nil) {
296-
[_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier];
297-
}
298-
}
299-
288+
[self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityVisible];
300289
[self _updateProgressImageBlockOnDownloaderIfNeeded];
301290
}
302291

303292
- (void)didExitVisibleState
304293
{
305294
[super didExitVisibleState];
306-
307-
if (_downloaderImplementsSetPriority) {
308-
ASDN::MutexLocker l(_downloadIdentifierLock);
309-
if (_downloadIdentifier != nil) {
310-
[_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier];
311-
}
312-
}
313-
295+
[self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload];
314296
[self _updateProgressImageBlockOnDownloaderIfNeeded];
315297
}
316298

299+
- (void)didExitDisplayState
300+
{
301+
[super didExitDisplayState];
302+
if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) {
303+
[self _updatePriorityOnDownloaderIfNeededWithDefaultPriority:ASImageDownloaderPriorityPreload];
304+
}
305+
}
306+
317307
#pragma mark - Core
318308

319309
- (void)setImage:(UIImage *)image
@@ -449,7 +439,6 @@ - (void)_setDownloadIdentifier:(id)downloadIdentifier
449439
_downloadIdentifier = downloadIdentifier;
450440
}
451441

452-
453442
#pragma mark - Image Loading Machinery
454443

455444
- (void)_loadImageIdentifiers
@@ -493,19 +482,37 @@ - (UIImage *)_bestImmediatelyAvailableImageFromDataSource:(id *)imageIdentifierO
493482

494483
#pragma mark -
495484

496-
/**
497-
@note: This should be called without _downloadIdentifierLock held. We will lock
498-
super to read our interface state and it's best to avoid acquiring both locks.
499-
*/
485+
- (void)_updatePriorityOnDownloaderIfNeededWithDefaultPriority:(ASImageDownloaderPriority)defaultPriority
486+
{
487+
ASAssertUnlocked(_downloadIdentifierLock);
488+
489+
if (_downloaderFlags.downloaderImplementsSetPriority) {
490+
// Read our interface state before locking so that we don't lock super while holding our lock.
491+
ASInterfaceState interfaceState = self.interfaceState;
492+
ASDN::MutexLocker l(_downloadIdentifierLock);
493+
494+
if (_downloadIdentifier != nil) {
495+
ASImageDownloaderPriority priority = defaultPriority;
496+
if (ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) {
497+
priority = ASImageDownloaderPriorityWithInterfaceState(interfaceState);
498+
}
499+
500+
[_downloader setPriority:priority withDownloadIdentifier:_downloadIdentifier];
501+
}
502+
}
503+
}
504+
500505
- (void)_updateProgressImageBlockOnDownloaderIfNeeded
501506
{
507+
ASAssertUnlocked(_downloadIdentifierLock);
508+
502509
BOOL shouldRenderProgressImages = self.shouldRenderProgressImages;
503510

504511
// Read our interface state before locking so that we don't lock super while holding our lock.
505512
ASInterfaceState interfaceState = self.interfaceState;
506513
ASDN::MutexLocker l(_downloadIdentifierLock);
507514

508-
if (!_downloaderImplementsSetProgress || _downloadIdentifier == nil) {
515+
if (!_downloaderFlags.downloaderImplementsSetProgress || _downloadIdentifier == nil) {
509516
return;
510517
}
511518

@@ -825,7 +832,7 @@ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL c
825832
[_delegate multiplexImageNode:self didStartDownloadOfImageWithIdentifier:imageIdentifier];
826833

827834
__weak __typeof__(self) weakSelf = self;
828-
void (^downloadProgressBlock)(CGFloat) = nil;
835+
ASImageDownloaderProgress downloadProgressBlock = NULL;
829836
if (_delegateFlags.downloadProgress) {
830837
downloadProgressBlock = ^(CGFloat progress) {
831838
__typeof__(self) strongSelf = weakSelf;
@@ -835,30 +842,67 @@ - (void)_downloadImageWithIdentifier:(id)imageIdentifier URL:(NSURL *)imageURL c
835842
};
836843
}
837844

845+
ASImageDownloaderCompletion completion = ^(id <ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier, id userInfo) {
846+
// We dereference iVars directly, so we can't have weakSelf going nil on us.
847+
__typeof__(self) strongSelf = weakSelf;
848+
if (!strongSelf)
849+
return;
850+
851+
ASDN::MutexLocker l(strongSelf->_downloadIdentifierLock);
852+
//Getting a result back for a different download identifier, download must not have been successfully canceled
853+
if (ASObjectIsEqual(strongSelf->_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
854+
return;
855+
}
856+
857+
completionBlock([imageContainer asdk_image], error);
858+
859+
// Delegateify.
860+
if (strongSelf->_delegateFlags.downloadFinish)
861+
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error];
862+
};
863+
838864
// Download!
839865
ASPerformBlockOnBackgroundThread(^{
840-
[self _setDownloadIdentifier:[_downloader downloadImageWithURL:imageURL
841-
callbackQueue:dispatch_get_main_queue()
842-
downloadProgress:downloadProgressBlock
843-
completion:^(id <ASImageContainerProtocol> imageContainer, NSError *error, id downloadIdentifier, id userInfo) {
844-
// We dereference iVars directly, so we can't have weakSelf going nil on us.
845-
__typeof__(self) strongSelf = weakSelf;
846-
if (!strongSelf)
847-
return;
848-
849-
ASDN::MutexLocker l(_downloadIdentifierLock);
850-
//Getting a result back for a different download identifier, download must not have been successfully canceled
851-
if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
852-
return;
853-
}
854-
855-
completionBlock([imageContainer asdk_image], error);
856-
857-
// Delegateify.
858-
if (strongSelf->_delegateFlags.downloadFinish)
859-
[strongSelf->_delegate multiplexImageNode:weakSelf didFinishDownloadingImageWithIdentifier:imageIdentifier error:error];
860-
}]];
861-
[self _updateProgressImageBlockOnDownloaderIfNeeded];
866+
__typeof__(self) strongSelf = weakSelf;
867+
if (!strongSelf)
868+
return;
869+
870+
dispatch_queue_t callbackQueue = dispatch_get_main_queue();
871+
872+
id downloadIdentifier;
873+
if (strongSelf->_downloaderFlags.downloaderImplementsDownloadWithPriority
874+
&& ASActivateExperimentalFeature(ASExperimentalImageDownloaderPriority)) {
875+
876+
/*
877+
Decide a priority based on the current interface state of this node.
878+
It can happen that this method was called when the node entered preload state
879+
but the interface state, at this point, tells us that the node is (going to be) visible,
880+
If that's the case, we jump to a higher priority directly.
881+
*/
882+
ASImageDownloaderPriority priority = ASImageDownloaderPriorityWithInterfaceState(strongSelf.interfaceState);
883+
downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL
884+
priority:priority
885+
callbackQueue:callbackQueue
886+
downloadProgress:downloadProgressBlock
887+
completion:completion];
888+
} else {
889+
/*
890+
Kick off a download with default priority.
891+
The actual "default" value is decided by the downloader.
892+
ASBasicImageDownloader and ASPINRemoteImageDownloader both use ASImageDownloaderPriorityImminent
893+
which is mapped to NSURLSessionTaskPriorityDefault.
894+
895+
This means that preload and display nodes use the same priority
896+
and their requests are put into the same pool.
897+
*/
898+
downloadIdentifier = [strongSelf->_downloader downloadImageWithURL:imageURL
899+
callbackQueue:callbackQueue
900+
downloadProgress:downloadProgressBlock
901+
completion:completion];
902+
}
903+
904+
[strongSelf _setDownloadIdentifier:downloadIdentifier];
905+
[strongSelf _updateProgressImageBlockOnDownloaderIfNeeded];
862906
});
863907
}
864908

0 commit comments

Comments
 (0)