diff --git a/Source/ASNetworkImageNode.h b/Source/ASNetworkImageNode.h index 7f505c42..fa07a3d4 100644 --- a/Source/ASNetworkImageNode.h +++ b/Source/ASNetworkImageNode.h @@ -133,6 +133,13 @@ NS_ASSUME_NONNULL_BEGIN */ @property (readonly) CGFloat renderedImageQuality; +/** + * Download progress of the current image. + * When downloading a network image, this value would be updated to track download progress (value between 0 and 1) + * This is 1 if image load from cache or network successfully. + */ +@property (readonly) CGFloat downloadProgress; + @end diff --git a/Source/ASNetworkImageNode.mm b/Source/ASNetworkImageNode.mm index 044184eb..32fa9cfa 100644 --- a/Source/ASNetworkImageNode.mm +++ b/Source/ASNetworkImageNode.mm @@ -39,6 +39,7 @@ CGFloat _currentImageQuality; CGFloat _renderedImageQuality; + CGFloat _downloadProgress; // Immutable and set on init only. We don't need to lock in this case. __weak id _downloader; @@ -151,6 +152,7 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); // If our image is being set externally, the image quality is 100% if (imageWasSetExternally) { [self _setCurrentImageQuality:1.0]; + [self _setDownloadProgress:1.0]; } [self _locked__setImage:image]; @@ -202,7 +204,9 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); _networkImageNodeFlags.imageWasSetExternally = NO; [self _locked_cancelImageDownloadWithResumePossibility:NO]; - + + [self _setDownloadProgress:0.0]; + _networkImageNodeFlags.imageLoaded = NO; _URL = URL; @@ -273,6 +277,30 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); }); } +- (void)setDownloadProgress:(CGFloat)downloadProgress +{ + ASLockScopeSelf(); + _downloadProgress = downloadProgress; +} + +- (CGFloat)downloadProgress +{ + return ASLockedSelf(_downloadProgress); +} + +/** + * Always use these methods internally to update the current download progress + * We want to maintain the order that downloadProgress is set regardless of the calling thread, + * so we always have to dispatch to the main thread to ensure that we queue the operations in the correct order. + * (see comment in displayDidFinish) + */ +- (void)_setDownloadProgress:(CGFloat)downloadProgress +{ + dispatch_async(dispatch_get_main_queue(), ^{ + self.downloadProgress = downloadProgress; + }); +} + - (void)setRenderedImageQuality:(CGFloat)renderedImageQuality { ASLockScopeSelf(); @@ -363,6 +391,7 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); UIImage *result = [[_cache synchronouslyFetchedCachedImageWithURL:url] asdk_image]; if (result) { [self _setCurrentImageQuality:1.0]; + [self _setDownloadProgress:1.0]; [self _locked__setImage:result]; _networkImageNodeFlags.imageLoaded = YES; @@ -438,6 +467,17 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); #pragma mark - Progress +- (void)_updateDownloadedProgress:(CGFloat)progress + downloadIdentifier:(nullable id)downloadIdentifier +{ + ASLockScopeSelf(); + // 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 _setDownloadProgress:progress]; +} + - (void)handleProgressImage:(UIImage *)progressImage progress:(CGFloat)progress downloadIdentifier:(nullable id)downloadIdentifier { ASLockScopeSelf(); @@ -536,6 +576,7 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); [self _locked_setAnimatedImage:nil]; [self _setCurrentImageQuality:0.0]; + [self _setDownloadProgress:0.0]; [self _locked__setImage:_defaultImage]; _networkImageNodeFlags.imageLoaded = NO; @@ -593,7 +634,13 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); } dispatch_queue_t callbackQueue = [self callbackQueue]; - ASImageDownloaderProgress downloadProgress = NULL; + __weak __typeof__(self) weakSelf = self; + ASImageDownloaderProgress downloadProgress = ^(CGFloat progress){ + __typeof__(self) strongSelf = weakSelf; + if (strongSelf) { + [strongSelf _updateDownloadedProgress:progress downloadIdentifier:downloadIdentifier]; + } + }; ASImageDownloaderCompletion completion = ^(id _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier, id _Nullable userInfo) { if (finished != NULL) { finished(imageContainer, error, downloadIdentifier, userInfo); @@ -727,6 +774,7 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); self->_networkImageNodeFlags.imageLoaded = YES; [self _setCurrentImageQuality:1.0]; + [self _setDownloadProgress:1.0]; if (self->_networkImageNodeFlags.delegateDidLoadImageWithInfo) { ASUnlockScope(self); @@ -766,6 +814,7 @@ static std::atomic_bool _useMainThreadDelegateCallbacks(true); UIImage *newImage; if (imageContainer != nil) { [strongSelf _setCurrentImageQuality:1.0]; + [strongSelf _setDownloadProgress:1.0]; NSData *animatedImageData = [imageContainer asdk_animatedImageData]; if (animatedImageData && strongSelf->_networkImageNodeFlags.downloaderImplementsAnimatedImage) { id animatedImage = [strongSelf->_downloader animatedImageWithData:animatedImageData];