mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-09-13 15:48:18 +00:00
651 lines
22 KiB
Plaintext
Executable File
651 lines
22 KiB
Plaintext
Executable File
//
|
|
// ASNetworkImageNode.mm
|
|
// AsyncDisplayKit
|
|
//
|
|
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
|
|
// This source code is licensed under the BSD-style license found in the
|
|
// LICENSE file in the root directory of this source tree. An additional grant
|
|
// of patent rights can be found in the PATENTS file in the same directory.
|
|
//
|
|
|
|
#import "ASNetworkImageNode.h"
|
|
#import "ASImageNode+Private.h"
|
|
|
|
#import "ASBasicImageDownloader.h"
|
|
#import "ASDisplayNodeInternal.h"
|
|
#import "ASDisplayNodeExtras.h"
|
|
#import "ASDisplayNode+Subclasses.h"
|
|
#import "ASDisplayNode+FrameworkPrivate.h"
|
|
#import "ASEqualityHelpers.h"
|
|
#import "ASInternalHelpers.h"
|
|
#import "ASImageContainerProtocolCategories.h"
|
|
|
|
#if PIN_REMOTE_IMAGE
|
|
#import "ASPINRemoteImageDownloader.h"
|
|
#endif
|
|
|
|
static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
|
|
|
@interface ASNetworkImageNode ()
|
|
{
|
|
__weak id<ASImageCacheProtocol> _cache;
|
|
__weak id<ASImageDownloaderProtocol> _downloader;
|
|
|
|
// Only access any of these with __instanceLock__.
|
|
__weak id<ASNetworkImageNodeDelegate> _delegate;
|
|
|
|
NSURL *_URL;
|
|
UIImage *_defaultImage;
|
|
|
|
NSUUID *_cacheUUID;
|
|
id _downloadIdentifier;
|
|
// The download identifier that we have set a progress block on, if any.
|
|
id _downloadIdentifierForProgressBlock;
|
|
|
|
BOOL _imageLoaded;
|
|
BOOL _imageWasSetExternally;
|
|
CGFloat _currentImageQuality;
|
|
CGFloat _renderedImageQuality;
|
|
BOOL _shouldRenderProgressImages;
|
|
|
|
struct {
|
|
unsigned int delegateDidStartFetchingData:1;
|
|
unsigned int delegateDidFailWithError:1;
|
|
unsigned int delegateDidFinishDecoding:1;
|
|
unsigned int delegateDidLoadImage:1;
|
|
} _delegateFlags;
|
|
|
|
//set on init only
|
|
struct {
|
|
unsigned int downloaderSupportsNewProtocol:1;
|
|
unsigned int downloaderImplementsSetProgress:1;
|
|
unsigned int downloaderImplementsSetPriority:1;
|
|
unsigned int downloaderImplementsAnimatedImage:1;
|
|
} _downloaderFlags;
|
|
|
|
struct {
|
|
unsigned int cacheSupportsCachedImage:1;
|
|
unsigned int cacheSupportsClearing:1;
|
|
unsigned int cacheSupportsSynchronousFetch:1;
|
|
} _cacheFlags;
|
|
}
|
|
|
|
@end
|
|
|
|
@implementation ASNetworkImageNode
|
|
|
|
- (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASImageDownloaderProtocol>)downloader
|
|
{
|
|
if (!(self = [super init]))
|
|
return nil;
|
|
|
|
_cache = (id<ASImageCacheProtocol>)cache;
|
|
_downloader = (id<ASImageDownloaderProtocol>)downloader;
|
|
|
|
ASDisplayNodeAssert([downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)], @"downloader must respond to either downloadImageWithURL:callbackQueue:downloadProgress:completion:.");
|
|
|
|
_downloaderFlags.downloaderSupportsNewProtocol = [downloader respondsToSelector:@selector(downloadImageWithURL:callbackQueue:downloadProgress:completion:)];
|
|
|
|
ASDisplayNodeAssert(cache == nil || [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)], @"cacher must respond to either cachedImageWithURL:callbackQueue:completion:");
|
|
|
|
_downloaderFlags.downloaderImplementsSetProgress = [downloader respondsToSelector:@selector(setProgressImageBlock:callbackQueue:withDownloadIdentifier:)];
|
|
_downloaderFlags.downloaderImplementsSetPriority = [downloader respondsToSelector:@selector(setPriority:withDownloadIdentifier:)];
|
|
_downloaderFlags.downloaderImplementsAnimatedImage = [downloader respondsToSelector:@selector(animatedImageWithData:)];
|
|
|
|
_cacheFlags.cacheSupportsCachedImage = [cache respondsToSelector:@selector(cachedImageWithURL:callbackQueue:completion:)];
|
|
_cacheFlags.cacheSupportsClearing = [cache respondsToSelector:@selector(clearFetchedImageFromCacheWithURL:)];
|
|
_cacheFlags.cacheSupportsSynchronousFetch = [cache respondsToSelector:@selector(synchronouslyFetchedCachedImageWithURL:)];
|
|
|
|
_shouldCacheImage = YES;
|
|
_shouldRenderProgressImages = YES;
|
|
self.shouldBypassEnsureDisplay = YES;
|
|
|
|
return self;
|
|
}
|
|
|
|
- (instancetype)init
|
|
{
|
|
#if PIN_REMOTE_IMAGE
|
|
return [self initWithCache:[ASPINRemoteImageDownloader sharedDownloader] downloader:[ASPINRemoteImageDownloader sharedDownloader]];
|
|
#else
|
|
return [self initWithCache:nil downloader:[ASBasicImageDownloader sharedImageDownloader]];
|
|
#endif
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
[self _cancelImageDownload];
|
|
}
|
|
|
|
#pragma mark - Public methods -- must lock
|
|
|
|
/// Setter for public image property. It has the side effect to set an internal _imageWasSetExternally that prevents setting an image internally. Setting an image internally should happen with the _setImage: method
|
|
- (void)setImage:(UIImage *)image
|
|
{
|
|
__instanceLock__.lock();
|
|
_imageWasSetExternally = (image != nil);
|
|
if (_imageWasSetExternally) {
|
|
[self __cancelDownloadAndClearImage];
|
|
}
|
|
__instanceLock__.unlock();
|
|
|
|
[self __setImage:image];
|
|
}
|
|
|
|
/// Internal image setter that will first check if an image already was set externally and will return otherwise will set it
|
|
- (void)_setImage:(UIImage *)image
|
|
{
|
|
__instanceLock__.lock();
|
|
|
|
#ifdef DEBUG
|
|
if (_URL != nil) {
|
|
NSLog(@"Setting the image directly on an %@ and setting an URL is not supported. If you decide to set an image direclty this node will work the same ways as an plain ASImageNode and not consider the image loaded via URL.", NSStringFromClass([self class]));
|
|
}
|
|
#endif
|
|
|
|
if (_imageWasSetExternally) {
|
|
__instanceLock__.unlock();
|
|
return;
|
|
}
|
|
__instanceLock__.unlock();
|
|
|
|
[self __setImage:image];
|
|
}
|
|
|
|
- (void)setURL:(NSURL *)URL
|
|
{
|
|
[self setURL:URL resetToDefault:YES];
|
|
}
|
|
|
|
- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
|
|
#ifdef DEBUG
|
|
if (_imageWasSetExternally) {
|
|
NSLog(@"Setting the image directly on an %@ and setting an URL is not supported. If you decide to set an image direclty this node will work the same ways as an plain ASImageNode and not consider the image loaded via URL.", NSStringFromClass([self class]));
|
|
}
|
|
#endif
|
|
|
|
if (ASObjectIsEqual(URL, _URL) || _imageWasSetExternally) {
|
|
return;
|
|
}
|
|
|
|
[self _cancelImageDownload];
|
|
_imageLoaded = NO;
|
|
|
|
_URL = URL;
|
|
|
|
BOOL hasURL = _URL == nil;
|
|
if (reset || hasURL) {
|
|
[self _setImage:_defaultImage];
|
|
/* We want to maintain the order that currentImageQuality is set regardless of the calling thread,
|
|
so always use a dispatch_async to ensure that we queue the operations in the correct order.
|
|
(see comment in displayDidFinish) */
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
self.currentImageQuality = hasURL ? 0.0 : 1.0;
|
|
});
|
|
}
|
|
|
|
[self setNeedsPreload];
|
|
}
|
|
|
|
- (NSURL *)URL
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
return _URL;
|
|
}
|
|
|
|
- (void)setDefaultImage:(UIImage *)defaultImage
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
|
|
if (ASObjectIsEqual(defaultImage, _defaultImage)) {
|
|
return;
|
|
}
|
|
_defaultImage = defaultImage;
|
|
|
|
if (!_imageLoaded) {
|
|
BOOL hasURL = _URL == nil;
|
|
/* We want to maintain the order that currentImageQuality is set regardless of the calling thread,
|
|
so always use a dispatch_async to ensure that we queue the operations in the correct order.
|
|
(see comment in displayDidFinish) */
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
self.currentImageQuality = hasURL ? 0.0 : 1.0;
|
|
});
|
|
[self _setImage:defaultImage];
|
|
}
|
|
}
|
|
|
|
- (UIImage *)defaultImage
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
return _defaultImage;
|
|
}
|
|
|
|
- (void)setCurrentImageQuality:(CGFloat)currentImageQuality
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
_currentImageQuality = currentImageQuality;
|
|
}
|
|
|
|
- (CGFloat)currentImageQuality
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
return _currentImageQuality;
|
|
}
|
|
|
|
- (void)setRenderedImageQuality:(CGFloat)renderedImageQuality
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
_renderedImageQuality = renderedImageQuality;
|
|
}
|
|
|
|
- (CGFloat)renderedImageQuality
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
return _renderedImageQuality;
|
|
}
|
|
|
|
- (void)setDelegate:(id<ASNetworkImageNodeDelegate>)delegate
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
_delegate = delegate;
|
|
|
|
_delegateFlags.delegateDidStartFetchingData = [delegate respondsToSelector:@selector(imageNodeDidStartFetchingData:)];
|
|
_delegateFlags.delegateDidFailWithError = [delegate respondsToSelector:@selector(imageNode:didFailWithError:)];
|
|
_delegateFlags.delegateDidFinishDecoding = [delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)];
|
|
_delegateFlags.delegateDidLoadImage = [delegate respondsToSelector:@selector(imageNode:didLoadImage:)];
|
|
}
|
|
|
|
- (id<ASNetworkImageNodeDelegate>)delegate
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
return _delegate;
|
|
}
|
|
|
|
- (void)setShouldRenderProgressImages:(BOOL)shouldRenderProgressImages
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
if (shouldRenderProgressImages == _shouldRenderProgressImages) {
|
|
return;
|
|
}
|
|
_shouldRenderProgressImages = shouldRenderProgressImages;
|
|
|
|
[self _updateProgressImageBlockOnDownloaderIfNeeded];
|
|
}
|
|
|
|
- (BOOL)shouldRenderProgressImages
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
return _shouldRenderProgressImages;
|
|
}
|
|
|
|
- (BOOL)placeholderShouldPersist
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
return (self.image == nil && _URL != nil);
|
|
}
|
|
|
|
/* displayWillStart in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary
|
|
in ASMultiplexImageNode as well. */
|
|
- (void)displayWillStartAsynchronously:(BOOL)asynchronously
|
|
{
|
|
[super displayWillStartAsynchronously:asynchronously];
|
|
|
|
if (asynchronously == NO && _cacheFlags.cacheSupportsSynchronousFetch) {
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
if (_imageLoaded == NO && _URL && _downloadIdentifier == nil) {
|
|
UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:_URL] asdk_image];
|
|
if (result) {
|
|
[self _setImage:result];
|
|
_imageLoaded = YES;
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
_currentImageQuality = 1.0;
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
// TODO: Consider removing this; it predates ASInterfaceState, which now ensures that even non-range-managed nodes get a -preload call.
|
|
[self didEnterPreloadState];
|
|
|
|
if (self.image == nil && _downloaderFlags.downloaderImplementsSetPriority) {
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
if (_downloadIdentifier != nil) {
|
|
[_downloader setPriority:ASImageDownloaderPriorityImminent withDownloadIdentifier:_downloadIdentifier];
|
|
}
|
|
}
|
|
}
|
|
|
|
/* visibileStateDidChange in ASMultiplexImageNode has a very similar implementation. Changes here are likely necessary
|
|
in ASMultiplexImageNode as well. */
|
|
- (void)didEnterVisibleState
|
|
{
|
|
[super didEnterVisibleState];
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
|
|
if (_downloaderFlags.downloaderImplementsSetPriority) {
|
|
if (_downloadIdentifier != nil) {
|
|
[_downloader setPriority:ASImageDownloaderPriorityVisible withDownloadIdentifier:_downloadIdentifier];
|
|
}
|
|
}
|
|
|
|
[self _updateProgressImageBlockOnDownloaderIfNeeded];
|
|
}
|
|
|
|
- (void)didExitVisibleState
|
|
{
|
|
[super didExitVisibleState];
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
|
|
if (_downloaderFlags.downloaderImplementsSetPriority) {
|
|
if (_downloadIdentifier != nil) {
|
|
[_downloader setPriority:ASImageDownloaderPriorityPreload withDownloadIdentifier:_downloadIdentifier];
|
|
}
|
|
}
|
|
|
|
[self _updateProgressImageBlockOnDownloaderIfNeeded];
|
|
}
|
|
|
|
- (void)didExitPreloadState
|
|
{
|
|
[super didExitPreloadState];
|
|
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
// If the image was set explicitly we don't want to remove it while exiting the preload state
|
|
if (_imageWasSetExternally) {
|
|
return;
|
|
}
|
|
|
|
[self __cancelDownloadAndClearImage];
|
|
}
|
|
}
|
|
|
|
- (void)didEnterPreloadState
|
|
{
|
|
[super didEnterPreloadState];
|
|
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
// Image was set externally no need to load an image
|
|
if (_imageWasSetExternally == NO) {
|
|
[self _lazilyLoadImageIfNecessary];
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - Private methods, safe to call without lock
|
|
|
|
- (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
// Getting a result back for a different download identifier, download must not have been successfully canceled
|
|
if (ASObjectIsEqual(_downloadIdentifier, downloadIdentifier) == NO && downloadIdentifier != nil) {
|
|
return;
|
|
}
|
|
[self _setImage:progressImage];
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
// See comment in -displayDidFinish for why this must be dispatched to main
|
|
self.currentImageQuality = progress;
|
|
});
|
|
}
|
|
|
|
#pragma mark - Private methods, call with lock held
|
|
|
|
- (void)_updateProgressImageBlockOnDownloaderIfNeeded
|
|
{
|
|
// If the downloader doesn't do progress, we are done.
|
|
if (_downloaderFlags.downloaderImplementsSetProgress == NO) {
|
|
return;
|
|
}
|
|
|
|
// Read state.
|
|
BOOL shouldRender = _shouldRenderProgressImages && ASInterfaceStateIncludesVisible(_interfaceState);
|
|
id oldDownloadIDForProgressBlock = _downloadIdentifierForProgressBlock;
|
|
id newDownloadIDForProgressBlock = shouldRender ? _downloadIdentifier : nil;
|
|
|
|
// If we're already bound to the correct download, we're done.
|
|
if (ASObjectIsEqual(oldDownloadIDForProgressBlock, newDownloadIDForProgressBlock)) {
|
|
return;
|
|
}
|
|
|
|
// Unbind from the previous download.
|
|
if (oldDownloadIDForProgressBlock != nil) {
|
|
[_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:oldDownloadIDForProgressBlock];
|
|
}
|
|
|
|
// Bind to the current download.
|
|
if (newDownloadIDForProgressBlock != nil) {
|
|
__weak __typeof(self) weakSelf = self;
|
|
[_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) {
|
|
[weakSelf handleProgressImage:progressImage progress:progress downloadIdentifier:downloadIdentifier];
|
|
} callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock];
|
|
}
|
|
|
|
// Update state.
|
|
_downloadIdentifierForProgressBlock = newDownloadIDForProgressBlock;
|
|
}
|
|
|
|
- (void)__cancelDownloadAndClearImage
|
|
{
|
|
[self _cancelImageDownload];
|
|
[self _clearImage];
|
|
if (_cacheFlags.cacheSupportsClearing) {
|
|
[_cache clearFetchedImageFromCacheWithURL:_URL];
|
|
}
|
|
}
|
|
|
|
- (void)_clearImage
|
|
{
|
|
// Destruction of bigger images on the main thread can be expensive
|
|
// and can take some time, so we dispatch onto a bg queue to
|
|
// actually dealloc.
|
|
UIImage *image = self.image;
|
|
CGSize imageSize = image.size;
|
|
BOOL shouldReleaseImageOnBackgroundThread = imageSize.width > kMinReleaseImageOnBackgroundSize.width ||
|
|
imageSize.height > kMinReleaseImageOnBackgroundSize.height;
|
|
if (shouldReleaseImageOnBackgroundThread) {
|
|
ASPerformBackgroundDeallocation(image);
|
|
}
|
|
self.animatedImage = nil;
|
|
[self _setImage:_defaultImage];
|
|
_imageLoaded = NO;
|
|
// See comment in -displayDidFinish for why this must be dispatched to main
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
self.currentImageQuality = 0.0;
|
|
});
|
|
}
|
|
|
|
- (void)_cancelImageDownload
|
|
{
|
|
if (!_downloadIdentifier) {
|
|
return;
|
|
}
|
|
|
|
if (_downloadIdentifier) {
|
|
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
|
}
|
|
_downloadIdentifier = nil;
|
|
|
|
_cacheUUID = nil;
|
|
}
|
|
|
|
- (void)_downloadImageWithCompletion:(void (^)(id <ASImageContainerProtocol> imageContainer, NSError*, id downloadIdentifier))finished
|
|
{
|
|
ASPerformBlockOnBackgroundThread(^{
|
|
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
if (_downloaderFlags.downloaderSupportsNewProtocol) {
|
|
_downloadIdentifier = [_downloader downloadImageWithURL:_URL
|
|
callbackQueue:dispatch_get_main_queue()
|
|
downloadProgress:NULL
|
|
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) {
|
|
if (finished != NULL) {
|
|
finished(imageContainer, error, downloadIdentifier);
|
|
}
|
|
}];
|
|
}
|
|
|
|
[self _updateProgressImageBlockOnDownloaderIfNeeded];
|
|
|
|
});
|
|
}
|
|
|
|
- (void)_lazilyLoadImageIfNecessary
|
|
{
|
|
// FIXME: We should revisit locking in this method (e.g. to access the instance variables at the top, and holding lock while calling delegate)
|
|
if (!_imageLoaded && _URL != nil && _downloadIdentifier == nil) {
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
if (_delegateFlags.delegateDidStartFetchingData) {
|
|
[_delegate imageNodeDidStartFetchingData:self];
|
|
}
|
|
}
|
|
|
|
if (_URL.isFileURL) {
|
|
{
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
if (self.shouldCacheImage) {
|
|
[self _setImage:[UIImage imageNamed:_URL.path.lastPathComponent]];
|
|
} else {
|
|
// First try to load the path directly, for efficiency assuming a developer who
|
|
// doesn't want caching is trying to be as minimal as possible.
|
|
UIImage *nonAnimatedImage = [UIImage imageWithContentsOfFile:_URL.path];
|
|
if (nonAnimatedImage == nil) {
|
|
// If we couldn't find it, execute an -imageNamed:-like search so we can find resources even if the
|
|
// extension is not provided in the path. This allows the same path to work regardless of shouldCacheImage.
|
|
NSString *filename = [[NSBundle mainBundle] pathForResource:_URL.path.lastPathComponent ofType:nil];
|
|
if (filename != nil) {
|
|
nonAnimatedImage = [UIImage imageWithContentsOfFile:filename];
|
|
}
|
|
}
|
|
|
|
// If the file may be an animated gif and then created an animated image.
|
|
id<ASAnimatedImageProtocol> animatedImage = nil;
|
|
if (_downloaderFlags.downloaderImplementsAnimatedImage) {
|
|
NSData *data = [NSData dataWithContentsOfURL:_URL];
|
|
if (data != nil) {
|
|
animatedImage = [_downloader animatedImageWithData:data];
|
|
|
|
if ([animatedImage respondsToSelector:@selector(isDataSupported:)] && [animatedImage isDataSupported:data] == NO) {
|
|
animatedImage = nil;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (animatedImage != nil) {
|
|
self.animatedImage = animatedImage;
|
|
} else {
|
|
[self _setImage:nonAnimatedImage];
|
|
}
|
|
}
|
|
|
|
_imageLoaded = YES;
|
|
/* We want to maintain the order that currentImageQuality is set regardless of the calling thread,
|
|
so always use a dispatch_async to ensure that we queue the operations in the correct order.
|
|
(see comment in displayDidFinish) */
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
self.currentImageQuality = 1.0;
|
|
});
|
|
if (_delegateFlags.delegateDidLoadImage) {
|
|
[_delegate imageNode:self didLoadImage:self.image];
|
|
}
|
|
});
|
|
}
|
|
} else {
|
|
__weak __typeof__(self) weakSelf = self;
|
|
void (^finished)(id <ASImageContainerProtocol>, NSError *, id downloadIdentifier) = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier) {
|
|
__typeof__(self) strongSelf = weakSelf;
|
|
if (strongSelf == nil) {
|
|
return;
|
|
}
|
|
|
|
ASDN::MutexLocker l(strongSelf->__instanceLock__);
|
|
|
|
//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;
|
|
}
|
|
|
|
if (imageContainer != nil) {
|
|
strongSelf->_imageLoaded = YES;
|
|
if ([imageContainer asdk_animatedImageData] && _downloaderFlags.downloaderImplementsAnimatedImage) {
|
|
strongSelf.animatedImage = [_downloader animatedImageWithData:[imageContainer asdk_animatedImageData]];
|
|
} else {
|
|
[strongSelf _setImage:[imageContainer asdk_image]];
|
|
}
|
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
strongSelf->_currentImageQuality = 1.0;
|
|
});
|
|
}
|
|
|
|
strongSelf->_downloadIdentifier = nil;
|
|
|
|
strongSelf->_cacheUUID = nil;
|
|
|
|
if (imageContainer != nil) {
|
|
if (strongSelf->_delegateFlags.delegateDidLoadImage) {
|
|
[strongSelf->_delegate imageNode:strongSelf didLoadImage:strongSelf.image];
|
|
}
|
|
}
|
|
else if (error && strongSelf->_delegateFlags.delegateDidFailWithError) {
|
|
[strongSelf->_delegate imageNode:strongSelf didFailWithError:error];
|
|
}
|
|
};
|
|
|
|
if (_cache != nil) {
|
|
NSUUID *cacheUUID = [NSUUID UUID];
|
|
_cacheUUID = cacheUUID;
|
|
|
|
void (^cacheCompletion)(id <ASImageContainerProtocol>) = ^(id <ASImageContainerProtocol> imageContainer) {
|
|
// If the cache UUID changed, that means this request was cancelled.
|
|
if (!ASObjectIsEqual(_cacheUUID, cacheUUID)) {
|
|
return;
|
|
}
|
|
|
|
if ([imageContainer asdk_image] == nil && _downloader != nil) {
|
|
[self _downloadImageWithCompletion:finished];
|
|
} else {
|
|
finished(imageContainer, nil, nil);
|
|
}
|
|
};
|
|
|
|
if (_cacheFlags.cacheSupportsCachedImage) {
|
|
[_cache cachedImageWithURL:_URL
|
|
callbackQueue:dispatch_get_main_queue()
|
|
completion:cacheCompletion];
|
|
}
|
|
} else {
|
|
[self _downloadImageWithCompletion:finished];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#pragma mark - ASDisplayNode+Subclasses
|
|
|
|
- (void)displayDidFinish
|
|
{
|
|
[super displayDidFinish];
|
|
|
|
ASDN::MutexLocker l(__instanceLock__);
|
|
if (_delegateFlags.delegateDidFinishDecoding && self.layer.contents != nil) {
|
|
/* We store the image quality in _currentImageQuality whenever _image is set. On the following displayDidFinish, we'll know that
|
|
_currentImageQuality is the quality of the image that has just finished rendering. In order for this to be accurate, we
|
|
need to be sure we are on main thread when we set _currentImageQuality. Otherwise, it is possible for _currentImageQuality
|
|
to be modified at a point where it is too late to cancel the main thread's previous display (the final sentinel check has passed),
|
|
but before the displayDidFinish of the previous display pass is called. In this situation, displayDidFinish would be called and we
|
|
would set _renderedImageQuality to the new _currentImageQuality, but the actual quality of the rendered image should be the previous
|
|
value stored in _currentImageQuality. */
|
|
|
|
_renderedImageQuality = _currentImageQuality;
|
|
[self.delegate imageNodeDidFinishDecoding:self];
|
|
}
|
|
}
|
|
|
|
@end
|