ASNetworkImageNode.

Initial open-source release of ASNetworkImageNode, a simple counterpart
to ASMultiplexImageNode.
This commit is contained in:
Nadine Salter
2014-11-18 18:07:31 -08:00
parent 38f8e63b01
commit 1eda1834e6
3 changed files with 354 additions and 0 deletions

View File

@@ -17,6 +17,8 @@
051943151A1575670030A7D0 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; }; 051943151A1575670030A7D0 /* Photos.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 051943141A1575670030A7D0 /* Photos.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; }; 052EE0661A159FEF002C6279 /* ASMultiplexImageNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; }; 052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; };
055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */; };
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */; };
055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; settings = {ATTRIBUTES = (Public, ); }; }; 055F1A3419ABD3E3004DAFF1 /* ASTableView.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */; settings = {ATTRIBUTES = (Public, ); }; };
055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.m */; }; 055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = 055F1A3319ABD3E3004DAFF1 /* ASTableView.m */; };
055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 055F1A3819ABD413004DAFF1 /* ASRangeController.h in Headers */ = {isa = PBXBuildFile; fileRef = 055F1A3619ABD413004DAFF1 /* ASRangeController.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -170,6 +172,8 @@
052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMultiplexImageNodeTests.m; sourceTree = "<group>"; }; 052EE0651A159FEF002C6279 /* ASMultiplexImageNodeTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASMultiplexImageNodeTests.m; sourceTree = "<group>"; };
052EE06A1A15A0D8002C6279 /* TestResources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TestResources; sourceTree = "<group>"; }; 052EE06A1A15A0D8002C6279 /* TestResources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TestResources; sourceTree = "<group>"; };
053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerInternal.h; sourceTree = "<group>"; }; 053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerInternal.h; sourceTree = "<group>"; };
055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageNode.h; sourceTree = "<group>"; };
055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNetworkImageNode.mm; sourceTree = "<group>"; };
055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableView.h; sourceTree = "<group>"; }; 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableView.h; sourceTree = "<group>"; };
055F1A3319ABD3E3004DAFF1 /* ASTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableView.m; sourceTree = "<group>"; }; 055F1A3319ABD3E3004DAFF1 /* ASTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableView.m; sourceTree = "<group>"; };
055F1A3619ABD413004DAFF1 /* ASRangeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeController.h; sourceTree = "<group>"; }; 055F1A3619ABD413004DAFF1 /* ASRangeController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeController.h; sourceTree = "<group>"; };
@@ -349,6 +353,8 @@
058D09DE195D050800B7D73C /* ASImageNode.mm */, 058D09DE195D050800B7D73C /* ASImageNode.mm */,
0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */,
0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */, 0516FA3F1A1563D200B4EBED /* ASMultiplexImageNode.mm */,
055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */,
055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */,
055F1A3219ABD3E3004DAFF1 /* ASTableView.h */, 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */,
0574D5E119C110610097DC25 /* ASTableViewProtocols.h */, 0574D5E119C110610097DC25 /* ASTableViewProtocols.h */,
055F1A3319ABD3E3004DAFF1 /* ASTableView.m */, 055F1A3319ABD3E3004DAFF1 /* ASTableView.m */,
@@ -545,6 +551,7 @@
058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */, 058D0A64195D05DC00B7D73C /* ASTextNodeWordKerner.h in Headers */,
058D0A65195D05DC00B7D73C /* ASTextNodeWordKerner.m in Headers */, 058D0A65195D05DC00B7D73C /* ASTextNodeWordKerner.m in Headers */,
058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */, 058D0A66195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.h in Headers */,
055B9FA81A1C154B00035D6D /* ASNetworkImageNode.h in Headers */,
058D0A67195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Headers */, 058D0A67195D05DC00B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Headers */,
058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */, 058D0A68195D05EC00B7D73C /* _ASAsyncTransaction.h in Headers */,
058D0A69195D05EC00B7D73C /* _ASAsyncTransaction.m in Headers */, 058D0A69195D05EC00B7D73C /* _ASAsyncTransaction.m in Headers */,
@@ -708,6 +715,7 @@
058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */, 058D0A15195D050800B7D73C /* ASDisplayNodeExtras.mm in Sources */,
058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */, 058D0A1F195D050800B7D73C /* ASTextNodeTextKitHelpers.mm in Sources */,
055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */, 055F1A3519ABD3E3004DAFF1 /* ASTableView.m in Sources */,
055B9FA91A1C154B00035D6D /* ASNetworkImageNode.mm in Sources */,
058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */, 058D0A1D195D050800B7D73C /* ASTextNodeRenderer.mm in Sources */,
058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */, 058D0A2A195D050800B7D73C /* ASDisplayNode+UIViewBridge.mm in Sources */,
AC3C4A521A1139C100143C57 /* ASCollectionView.m in Sources */, AC3C4A521A1139C100143C57 /* ASCollectionView.m in Sources */,

View File

@@ -0,0 +1,91 @@
/* 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 <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASImageProtocols.h>
@protocol ASNetworkImageNodeDelegate;
/**
* ASNetworkImageNode is a simple image node that can download and display an image from the network, with support for a
* placeholder image (<defaultImage>). The currently-displayed image is always available in the inherited ASImageNode
* <image> property.
*
* @see ASMultiplexImageNode for a more powerful counterpart to this class.
*/
@interface ASNetworkImageNode : ASImageNode
/**
* The designated initializer.
*
* @param cache The object that implements a cache of images for the image node.
* @param downloader The object that implements image downloading for the image node. Must not be nil.
*
* @discussion If `cache` is nil, the receiver will not attempt to retrieve images from a cache before downloading them.
*
* @result An initialized ASNetworkImageNode.
*/
- (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASImageDownloaderProtocol>)downloader;
- (instancetype)init NS_UNAVAILABLE;
/**
* The delegate, which must conform to the <ASNetworkImageNodeDelegate> protocol.
*/
@property (atomic, assign, readwrite) id<ASNetworkImageNodeDelegate> delegate;
/**
* A placeholder image to display while the URL is loading.
*/
@property (atomic, retain, readwrite) UIImage *defaultImage;
/**
* The URL of a new image to download and display.
*
* @discussion Changing this property will reset the displayed image to a placeholder (<defaultImage>) while loading.
*/
@property (atomic, retain, readwrite) NSURL *URL;
/**
* Download and display a new image.
*
* @param reset Whether to display a placeholder (<defaultImage>) while loading the new image.
*/
- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset;
/**
* If <URL> is a local file, set this property to YES to take advantage of UIKit's image cacheing. Defaults to YES.
*/
@property (nonatomic, assign, readwrite) BOOL shouldCacheImage;
@end
#pragma mark -
@protocol ASNetworkImageNodeDelegate <NSObject>
/**
* Notification that the image node finished downloading an image.
*
* @param imageNode The sender.
* @param image The newly-loaded image.
*
* @discussion Called on a background queue.
*/
- (void)imageNode:(ASNetworkImageNode *)imageNode didLoadImage:(UIImage *)image;
@optional
/**
* Notification that the image node finished decoding an image.
*
* @param imageNode The sender.
*/
- (void)imageNodeDidFinishDecoding:(ASNetworkImageNode *)imageNode;
@end

View File

@@ -0,0 +1,255 @@
/* 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 <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
#import <AsyncDisplayKit/ASThread.h>
@interface ASNetworkImageNode ()
{
ASDN::RecursiveMutex _lock;
id<ASImageCacheProtocol> _cache;
id<ASImageDownloaderProtocol> _downloader;
// Only access any of these with _lock.
id<ASNetworkImageNodeDelegate> _delegate;
NSURL *_URL;
UIImage *_defaultImage;
NSUUID *_cacheUUID;
id _imageDownload;
BOOL _imageLoaded;
}
@end
@implementation ASNetworkImageNode
- (instancetype)initWithCache:(id<ASImageCacheProtocol>)cache downloader:(id<ASImageDownloaderProtocol>)downloader
{
if (!(self = [super init]))
return nil;
_cache = cache;
_downloader = downloader;
_shouldCacheImage = YES;
return self;
}
- (instancetype)init
{
ASDISPLAYNODE_NOT_DESIGNATED_INITIALIZER();
}
- (void)dealloc
{
[self _cancelImageDownload];
}
#pragma mark - Public methods -- must lock
- (void)setURL:(NSURL *)URL
{
[self setURL:URL resetToDefault:YES];
}
- (void)setURL:(NSURL *)URL resetToDefault:(BOOL)reset
{
ASDN::MutexLocker l(_lock);
if (URL == _URL || [URL isEqual:_URL]) {
return;
}
[self _cancelImageDownload];
_imageLoaded = NO;
_URL = URL;
if (reset || _URL == nil)
self.image = _defaultImage;
if (self.nodeLoaded && self.layer.superlayer)
[self _lazilyLoadImageIfNecessary];
}
- (NSURL *)URL
{
ASDN::MutexLocker l(_lock);
return _URL;
}
- (void)setDefaultImage:(UIImage *)defaultImage
{
ASDN::MutexLocker l(_lock);
if (defaultImage == _defaultImage || [defaultImage isEqual:_defaultImage]) {
return;
}
_defaultImage = defaultImage;
if (!_imageLoaded) {
self.image = _defaultImage;
}
}
- (UIImage *)defaultImage
{
ASDN::MutexLocker l(_lock);
return _defaultImage;
}
- (void)setDelegate:(id<ASNetworkImageNodeDelegate>)delegate
{
ASDN::MutexLocker l(_lock);
_delegate = delegate;
}
- (id<ASNetworkImageNodeDelegate>)delegate
{
ASDN::MutexLocker l(_lock);
return _delegate;
}
- (void)didExitHierarchy
{
[super didExitHierarchy];
{
ASDN::MutexLocker l(_lock);
[self _cancelImageDownload];
self.image = _defaultImage;
_imageLoaded = NO;
}
}
- (void)willEnterHierarchy
{
[super willEnterHierarchy];
{
ASDN::MutexLocker l(_lock);
[self _lazilyLoadImageIfNecessary];
}
}
#pragma mark - Private methods -- only call with lock.
- (void)_cancelImageDownload
{
if (!_imageDownload) {
return;
}
[_downloader cancelImageDownloadForIdentifier:_imageDownload];
_imageDownload = nil;
_cacheUUID = nil;
}
- (void)_downloadImageWithCompletion:(void (^)(CGImageRef))finished
{
_imageDownload = [_downloader downloadImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
downloadProgressBlock:NULL
completion:^(CGImageRef responseImage, NSError *error) {
if (finished != NULL) {
finished(responseImage);
}
}];
}
- (void)_lazilyLoadImageIfNecessary
{
if (!_imageLoaded && _URL != nil && _imageDownload == nil) {
if (_URL.isFileURL) {
{
ASDN::MutexLocker l(_lock);
dispatch_async(dispatch_get_main_queue(), ^{
_imageLoaded = YES;
if (self.shouldCacheImage) {
self.image = [UIImage imageNamed:_URL.path];
} else {
self.image = [UIImage imageWithContentsOfFile:_URL.path];
}
[_delegate imageNode:self didLoadImage:self.image];
});
}
} else {
// The delegate must be retained, as nothing prevents it from being deallocated during the delay before completionBlock is executed.
// Clients (the delegate) /should/ set our delegate property to nil in their -dealloc, but don't always do this.
__block id<ASNetworkImageNodeDelegate> delegate = _delegate;
void (^finished)(CGImageRef) = ^(CGImageRef responseImage) {
{
ASDN::MutexLocker l(_lock);
if (responseImage != NULL) {
_imageLoaded = YES;
self.image = [UIImage imageWithCGImage:responseImage];
}
_imageDownload = nil;
_cacheUUID = nil;
}
if (responseImage != NULL) {
[delegate imageNode:self didLoadImage:self.image];
}
};
if (_cache != nil) {
NSUUID *cacheUUID = [NSUUID UUID];
_cacheUUID = cacheUUID;
void (^cacheCompletion)(CGImageRef) = ^(CGImageRef image) {
// If the cache UUID changed, that means this request was cancelled.
if (![_cacheUUID isEqual:cacheUUID]) {
return;
}
if (image == NULL && _downloader != nil) {
[self _downloadImageWithCompletion:finished];
} else {
finished(image);
}
};
[_cache fetchCachedImageWithURL:_URL
callbackQueue:dispatch_get_main_queue()
completion:cacheCompletion];
} else {
[self _downloadImageWithCompletion:finished];
}
}
}
}
#pragma mark - ASDisplayNode+Subclasses
- (void)asyncdisplaykit_asyncTransactionContainerStateDidChange
{
if (self.asyncdisplaykit_asyncTransactionContainerState == ASAsyncTransactionContainerStateNoTransactions) {
if (self.layer.contents != nil && [self.delegate respondsToSelector:@selector(imageNodeDidFinishDecoding:)]) {
[self.delegate imageNodeDidFinishDecoding:self];
}
}
}
@end