ASBasicImageDownloader.

This is a simplistic, NSURLSession-based downloader object that
implements ASImageDownloaderProtocol and can be used with
ASMultiplexImageNode and ASNetworkImageNode.

(Closes #115.  NSURLSession should suffice for most usecases, and this
code should provide a good jumping-off point for a more-complex
implementation.)
This commit is contained in:
Nadine Salter
2014-11-20 14:19:32 -08:00
parent 18d52949e6
commit 3c690bf9e5
5 changed files with 183 additions and 48 deletions

View File

@@ -17,6 +17,8 @@
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"; }; };
052EE06B1A15A0D8002C6279 /* TestResources in Resources */ = {isa = PBXBuildFile; fileRef = 052EE06A1A15A0D8002C6279 /* TestResources */; };
054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */ = {isa = PBXBuildFile; fileRef = 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */; };
0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */ = {isa = PBXBuildFile; fileRef = 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */; };
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, ); }; };
@@ -172,6 +174,8 @@
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>"; };
053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerInternal.h; sourceTree = "<group>"; };
054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBasicImageDownloader.h; sourceTree = "<group>"; };
054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBasicImageDownloader.mm; 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>"; };
@@ -418,6 +422,8 @@
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
058D09E5195D050800B7D73C /* _ASDisplayView.mm */,
054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */,
054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */,
058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */,
058D09E7195D050800B7D73C /* ASHighlightOverlayLayer.mm */,
058D09E8195D050800B7D73C /* ASMutableAttributedStringBuilder.h */,
@@ -568,6 +574,7 @@
0516FA3D1A15563400B4EBED /* ASLog.h in Headers */,
058D0A83195D060300B7D73C /* ASBaseDefines.h in Headers */,
058D0A84195D060300B7D73C /* ASDisplayNodeExtraIvars.h in Headers */,
054963491A1EA066000F8E56 /* ASBasicImageDownloader.h in Headers */,
05F20AA41A15733C00DCA68A /* ASImageProtocols.h in Headers */,
058D0A71195D05F800B7D73C /* _AS-objc-internal.h in Headers */,
058D0A72195D05F800B7D73C /* _ASCoreAnimationExtras.h in Headers */,
@@ -724,6 +731,7 @@
058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */,
058D0A21195D050800B7D73C /* NSMutableAttributedString+TextKitAdditions.m in Sources */,
058D0A25195D050800B7D73C /* UIView+ASConvenience.m in Sources */,
0549634A1A1EA066000F8E56 /* ASBasicImageDownloader.mm in Sources */,
058D0A14195D050800B7D73C /* ASDisplayNode.mm in Sources */,
058D0A1B195D050800B7D73C /* ASMutableAttributedStringBuilder.m in Sources */,
058D0A2B195D050800B7D73C /* ASImageNode+CGExtras.m in Sources */,

View File

@@ -13,6 +13,7 @@
#import <AsyncDisplayKit/ASImageNode.h>
#import <AsyncDisplayKit/ASTextNode.h>
#import <AsyncDisplayKit/ASBasicImageDownloader.h>
#import <AsyncDisplayKit/ASMultiplexImageNode.h>
#import <AsyncDisplayKit/ASNetworkImageNode.h>

View File

@@ -0,0 +1,16 @@
/* 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/ASImageProtocols.h>
/**
* @abstract Simple NSURLSession-based image downloader.
*/
@interface ASBasicImageDownloader : NSObject <ASImageDownloaderProtocol>
@end

View File

@@ -0,0 +1,156 @@
/* 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 "ASBasicImageDownloader.h"
#import <objc/runtime.h>
#import <UIKit/UIKit.h>
#import "ASThread.h"
#pragma mark -
/**
* Collection of properties associated with a download request.
*/
@interface ASBasicImageDownloaderMetadata : NSObject
@property (nonatomic, strong) dispatch_queue_t callbackQueue;
@property (nonatomic, strong) void (^downloadProgressBlock)(CGFloat);
@property (nonatomic, strong) void (^completionBlock)(CGImageRef, NSError *);
@end
@implementation ASBasicImageDownloaderMetadata
@end
#pragma mark -
/**
* NSURLSessionTask lacks a `userInfo` property, so add this association ourselves.
*/
@interface NSURLSessionTask (ASBasicImageDownloader)
@property (nonatomic, strong) ASBasicImageDownloaderMetadata *asyncdisplaykit_metadata;
@end
@implementation NSURLSessionTask (ASBasicImageDownloader)
static const char *kMetadataKey = NSStringFromClass(ASBasicImageDownloaderMetadata.class).UTF8String;
- (void)setAsyncdisplaykit_metadata:(ASBasicImageDownloaderMetadata *)asyncdisplaykit_metadata
{
objc_setAssociatedObject(self, kMetadataKey, asyncdisplaykit_metadata, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (ASBasicImageDownloader *)asyncdisplaykit_metadata
{
return objc_getAssociatedObject(self, kMetadataKey);
}
@end
#pragma mark -
@interface ASBasicImageDownloader () <NSURLSessionDownloadDelegate>
{
NSOperationQueue *_sessionDelegateQueue;
NSURLSession *_session;
}
@end
@implementation ASBasicImageDownloader
#pragma mark Lifecycle.
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_sessionDelegateQueue = [[NSOperationQueue alloc] init];
_session = [NSURLSession sessionWithConfiguration:[NSURLSessionConfiguration defaultSessionConfiguration]
delegate:self
delegateQueue:_sessionDelegateQueue];
return self;
}
#pragma mark ASImageDownloaderProtocol.
- (id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^)(CGFloat))downloadProgressBlock
completion:(void (^)(CGImageRef, NSError *))completion
{
// create download task
NSURLSessionTask *task = [_session downloadTaskWithURL:URL];
// associate metadata with it
ASBasicImageDownloaderMetadata *metadata = [[ASBasicImageDownloaderMetadata alloc] init];
metadata.callbackQueue = callbackQueue ?: dispatch_get_main_queue();
metadata.downloadProgressBlock = downloadProgressBlock;
metadata.completionBlock = completion;
task.asyncdisplaykit_metadata = metadata;
// start downloading
[task resume];
// return the task as an opaque cancellation token
return task;
}
- (void)cancelImageDownloadForIdentifier:(id)downloadIdentifier
{
if (!downloadIdentifier) {
return;
}
ASDisplayNodeAssert([downloadIdentifier isKindOfClass:NSURLSessionTask.class], @"unexpected downloadIdentifier");
NSURLSessionTask *task = (NSURLSessionTask *)downloadIdentifier;
[task cancel];
}
#pragma mark NSURLSessionDownloadDelegate.
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didWriteData:(int64_t)bytesWritten
totalBytesWritten:(int64_t)totalBytesWritten
totalBytesExpectedToWrite:(int64_t)totalBytesExpectedToWrite
{
ASBasicImageDownloaderMetadata *metadata = downloadTask.asyncdisplaykit_metadata;
if (metadata.downloadProgressBlock) {
metadata.downloadProgressBlock((CGFloat)totalBytesWritten / (CGFloat)totalBytesExpectedToWrite);
}
}
// invoked if the download succeeded with no error
- (void)URLSession:(NSURLSession *)session downloadTask:(NSURLSessionDownloadTask *)downloadTask
didFinishDownloadingToURL:(NSURL *)location
{
UIImage *image = [UIImage imageWithData:[NSData dataWithContentsOfURL:location]];
ASBasicImageDownloaderMetadata *metadata = downloadTask.asyncdisplaykit_metadata;
if (metadata.completionBlock) {
dispatch_async(metadata.callbackQueue, ^{
metadata.completionBlock(image.CGImage, nil);
});
}
}
// invoked unconditionally
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task
didCompleteWithError:(NSError *)error
{
ASBasicImageDownloaderMetadata *metadata = task.asyncdisplaykit_metadata;
if (metadata && error) {
dispatch_async(metadata.callbackQueue, ^{
metadata.completionBlock(NULL, error);
});
}
}
@end

View File

@@ -14,7 +14,7 @@
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface ViewController () <ASMultiplexImageNodeDataSource, ASMultiplexImageNodeDelegate, ASImageDownloaderProtocol>
@interface ViewController () <ASMultiplexImageNodeDataSource, ASMultiplexImageNodeDelegate>
{
ASMultiplexImageNode *_imageNode;
@@ -33,7 +33,7 @@
// multiplex image node!
_imageNode = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:self];
_imageNode = [[ASMultiplexImageNode alloc] initWithCache:nil downloader:[[ASBasicImageDownloader alloc] init]];
_imageNode.dataSource = self;
_imageNode.delegate = self;
@@ -128,50 +128,4 @@
}
}
#pragma mark -
#pragma mark ASImageDownloaderProtocol.
- (id)downloadImageWithURL:(NSURL *)URL
callbackQueue:(dispatch_queue_t)callbackQueue
downloadProgressBlock:(void (^)(CGFloat progress))downloadProgressBlock
completion:(void (^)(CGImageRef image, NSError *error))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] CGImage], connectionError);
}
});
};
// 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