Files
Christos Gkekas 17d4d13463 Exposes a new option in ASImageDownloaderProtocol to retry image downloads (#1948)
* Exposes a new option in ASImageDownloaderProtocol to retry image downloads

At the moment the ASBasicImageDownloader does not automatically retry image downloads if the remote
host is unreachable. On the contrary the ASPINRemoteImageDownloader automatically retries. Retrying is
something that ultimately clients need to be able to control, for example to fail fast to an alternative image
rather than keep retrying for more than one minute while not displaying any image. This change exposes
a new option in the ASImageDownloaderProtocol to retry image downloads. It also uses this new option
in both ASNetworkImageNode and also ASMultiplexImageNode, setting it to YES to preserve the current
behaviour.

* Fixes a failing test in ASMultiplexImageNodeTests

* Fixes ScreenNode.m

ScreenNode.m is implementing ASImageDownloaderProtocol and needs to
be fixed to reflect changes in the latter.
2021-02-19 08:23:01 -08:00

161 lines
5.4 KiB
Objective-C

//
// ScreenNode.m
// Texture
//
// Copyright (c) Facebook, Inc. and its affiliates. All rights reserved.
// Changes after 4/13/2017 are: Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import "ScreenNode.h"
@interface ScreenNode() <ASMultiplexImageNodeDataSource, ASMultiplexImageNodeDelegate, ASImageDownloaderProtocol>
@end
@implementation ScreenNode
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
// multiplex image node!
// NB: we're using a custom downloader with an artificial delay for this demo, but ASPINRemoteImageDownloader works too!
_imageNode = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:self];
_imageNode.dataSource = self;
_imageNode.delegate = self;
// placeholder colour
_imageNode.backgroundColor = ASDisplayNodeDefaultPlaceholderColor();
// load low-quality images before high-quality images
_imageNode.downloadsIntermediateImages = YES;
// simple status label. Synchronous to avoid flicker / placeholder state when updating.
_buttonNode = [[ASButtonNode alloc] init];
[_buttonNode addTarget:self action:@selector(reload) forControlEvents:ASControlNodeEventTouchUpInside];
_buttonNode.titleNode.displaysAsynchronously = NO;
[self addSubnode:_imageNode];
[self addSubnode:_buttonNode];
return self;
}
- (void)start
{
[self setText:@"loading…"];
_buttonNode.userInteractionEnabled = NO;
_imageNode.imageIdentifiers = @[ @"best", @"medium", @"worst" ]; // go!
}
- (void)reload
{
[self start];
[_imageNode reloadImageIdentifierSources];
}
- (void)setText:(NSString *)text
{
NSDictionary *attributes = @{NSFontAttributeName: [UIFont fontWithName:@"HelveticaNeue-Light" size:22.0f]};
NSAttributedString *string = [[NSAttributedString alloc] initWithString:text
attributes:attributes];
[_buttonNode setAttributedTitle:string forState:UIControlStateNormal];
[self setNeedsLayout];
}
- (ASLayoutSpec *)layoutSpecThatFits:(ASSizeRange)constrainedSize
{
ASRatioLayoutSpec *imagePlaceholder = [ASRatioLayoutSpec ratioLayoutSpecWithRatio:1 child:_imageNode];
ASStackLayoutSpec *verticalStack = [[ASStackLayoutSpec alloc] init];
verticalStack.direction = ASStackLayoutDirectionVertical;
verticalStack.spacing = 10;
verticalStack.justifyContent = ASStackLayoutJustifyContentCenter;
verticalStack.alignItems = ASStackLayoutAlignItemsCenter;
verticalStack.children = @[imagePlaceholder, _buttonNode];
return [ASInsetLayoutSpec insetLayoutSpecWithInsets:UIEdgeInsetsMake(10, 10, 10, 10) child:verticalStack];
}
#pragma mark -
#pragma mark ASMultiplexImageNode data source & delegate.
- (NSURL *)multiplexImageNode:(ASMultiplexImageNode *)imageNode URLForImageIdentifier:(id)imageIdentifier
{
if ([imageIdentifier isEqualToString:@"worst"]) {
return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples_extra/Multiplex/worst.png"];
}
if ([imageIdentifier isEqualToString:@"medium"]) {
return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples_extra/Multiplex/medium.png"];
}
if ([imageIdentifier isEqualToString:@"best"]) {
return [NSURL URLWithString:@"https://raw.githubusercontent.com/facebook/AsyncDisplayKit/master/examples_extra/Multiplex/best.png"];
}
// unexpected identifier
return nil;
}
- (void)multiplexImageNode:(ASMultiplexImageNode *)imageNode didFinishDownloadingImageWithIdentifier:(id)imageIdentifier error:(NSError *)error
{
[self setText:[NSString stringWithFormat:@"loaded '%@'", imageIdentifier]];
if ([imageIdentifier isEqualToString:@"best"]) {
[self setText:[_buttonNode.titleNode.attributedText.string stringByAppendingString:@". tap to reload"]];
_buttonNode.userInteractionEnabled = YES;
}
}
#pragma mark -
#pragma mark ASImageDownloaderProtocol.
- (nullable id)downloadImageWithURL:(NSURL *)URL
shouldRetry:(BOOL)shouldRetry
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgress:(nullable ASImageDownloaderProgress)downloadProgressBlock
completion:(ASImageDownloaderCompletion)completion
{
// if no callback queue is supplied, run on the main thread
if (callbackQueue == nil) {
callbackQueue = dispatch_get_main_queue();
}
// call completion blocks
void (^handler)(NSURLResponse *, NSData *, NSError *) = ^(NSURLResponse *response, NSData *data, NSError *connectionError) {
// add an artificial delay
usleep(1.0 * USEC_PER_SEC);
// ASMultiplexImageNode callbacks
dispatch_async(callbackQueue, ^{
if (downloadProgressBlock) {
downloadProgressBlock(1.0f);
}
if (completion) {
completion([UIImage imageWithData:data], connectionError, nil, nil);
}
});
};
// let NSURLConnection do the heavy lifting
NSURLRequest *request = [NSURLRequest requestWithURL:URL];
[NSURLConnection sendAsynchronousRequest:request
queue:[[NSOperationQueue alloc] init]
completionHandler:handler];
// return nil, don't support cancellation
return nil;
}
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
{
// no-op, don't support cancellation
}
@end