diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index e6a0d0d0a2..8bb296ceb7 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -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 = ""; }; 052EE06A1A15A0D8002C6279 /* TestResources */ = {isa = PBXFileReference; lastKnownFileType = folder; path = TestResources; sourceTree = ""; }; 053011A719B9882B00A9F2D0 /* ASRangeControllerInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASRangeControllerInternal.h; sourceTree = ""; }; + 054963471A1EA066000F8E56 /* ASBasicImageDownloader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBasicImageDownloader.h; sourceTree = ""; }; + 054963481A1EA066000F8E56 /* ASBasicImageDownloader.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASBasicImageDownloader.mm; sourceTree = ""; }; 055B9FA61A1C154B00035D6D /* ASNetworkImageNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASNetworkImageNode.h; sourceTree = ""; }; 055B9FA71A1C154B00035D6D /* ASNetworkImageNode.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASNetworkImageNode.mm; sourceTree = ""; }; 055F1A3219ABD3E3004DAFF1 /* ASTableView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTableView.h; sourceTree = ""; }; @@ -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 */, diff --git a/AsyncDisplayKit/AsyncDisplayKit.h b/AsyncDisplayKit/AsyncDisplayKit.h index 41f9a2c975..b9da525819 100644 --- a/AsyncDisplayKit/AsyncDisplayKit.h +++ b/AsyncDisplayKit/AsyncDisplayKit.h @@ -13,6 +13,7 @@ #import #import +#import #import #import diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.h b/AsyncDisplayKit/Details/ASBasicImageDownloader.h new file mode 100644 index 0000000000..12e9ed9b76 --- /dev/null +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.h @@ -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 + +/** + * @abstract Simple NSURLSession-based image downloader. + */ +@interface ASBasicImageDownloader : NSObject + +@end diff --git a/AsyncDisplayKit/Details/ASBasicImageDownloader.mm b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm new file mode 100644 index 0000000000..e86c384136 --- /dev/null +++ b/AsyncDisplayKit/Details/ASBasicImageDownloader.mm @@ -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 + +#import + +#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 () +{ + 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 diff --git a/examples/Multiplex/Sample/ViewController.m b/examples/Multiplex/Sample/ViewController.m index 96e60e9111..e96db45594 100644 --- a/examples/Multiplex/Sample/ViewController.m +++ b/examples/Multiplex/Sample/ViewController.m @@ -14,7 +14,7 @@ #import -@interface ViewController () +@interface ViewController () { 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