Implementation of Synchronous Concurrency features for AsyncDisplayKit 2.0

This provides internal features on _ASAsyncTransaction and ASDisplayNode to facilitate
implementing public API that allows clients to choose if they would prefer to block
on the completion of unfinished rendering, rather than allow a placeholder state to
become visible.

The internal features are:
-[_ASAsyncTransaction waitUntilComplete]
-[ASDisplayNode recursivelyEnsureDisplay]

Also provided are two such implementations:
-[ASCellNode setNeverShowPlaceholders:], which integrates with both Tables and Collections
-[ASViewController setNeverShowPlaceholders:], which should work with Nav and Tab controllers.

Lastly, on ASDisplayNode, a new property .shouldBypassEnsureDisplay allows individual node types
to exempt themselves from blocking the main thread on their display.

By implementing the feature at the ASCellNode level rather than ASTableView & ASCollectionView,
developers can retain fine-grained control on display characteristics.  For example, certain
cell types may be appropriate to display to the user with placeholders, whereas others may not.

Follow-up work will include unit tests, revisiting names, and the header locations of definitions.
This commit is contained in:
Scott Goodson
2015-09-27 19:14:36 -07:00
parent c824644482
commit a58844379c
34 changed files with 1109 additions and 37 deletions

View File

@@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#define UseAutomaticLayout 1
@interface AppDelegate : UIResponder <UIApplicationDelegate>
@property (strong, nonatomic) UIWindow *window;
@end

View File

@@ -0,0 +1,33 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import "AppDelegate.h"
#import "AsyncTableViewController.h"
#import "AsyncViewController.h"
@implementation AppDelegate
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
self.window.backgroundColor = [UIColor whiteColor];
UITabBarController *tabBarController = [[UITabBarController alloc] initWithNibName:nil bundle:nil];
self.window.rootViewController = tabBarController;
[tabBarController setViewControllers:@[[[AsyncTableViewController alloc] init], [[AsyncViewController alloc] init]]];
[self.window makeKeyAndVisible];
return YES;
}
@end

View File

@@ -0,0 +1,16 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
@interface AsyncTableViewController : UIViewController
@end

View File

@@ -0,0 +1,91 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <AsyncDisplayKit/AsyncDisplayKit.h>
#import <AsyncDisplayKit/ASAssert.h>
#import "AsyncTableViewController.h"
#import "RandomCoreGraphicsNode.h"
@interface AsyncTableViewController () <ASTableViewDataSource, ASTableViewDelegate>
{
ASTableView *_tableView;
}
@end
@implementation AsyncTableViewController
#pragma mark -
#pragma mark UIViewController.
- (instancetype)init
{
if (!(self = [super init]))
return nil;
_tableView = [[ASTableView alloc] initWithFrame:CGRectZero style:UITableViewStylePlain];
_tableView.separatorStyle = UITableViewCellSeparatorStyleNone;
_tableView.asyncDataSource = self;
_tableView.asyncDelegate = self;
ASRangeTuningParameters tuningParameters;
tuningParameters.leadingBufferScreenfuls = 0.5;
tuningParameters.trailingBufferScreenfuls = 1.0;
[_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypePreload];
[_tableView setTuningParameters:tuningParameters forRangeType:ASLayoutRangeTypeRender];
self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFeatured tag:0];
self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemRedo
target:self
action:@selector(reloadEverything)];
return self;
}
- (void)reloadEverything
{
[_tableView reloadData];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.view addSubview:_tableView];
}
- (void)viewWillLayoutSubviews
{
_tableView.frame = self.view.bounds;
}
- (BOOL)prefersStatusBarHidden
{
return YES;
}
#pragma mark -
#pragma mark ASTableView.
- (ASCellNode *)tableView:(ASTableView *)tableView nodeForRowAtIndexPath:(NSIndexPath *)indexPath
{
RandomCoreGraphicsNode *elementNode = [[RandomCoreGraphicsNode alloc] init];
elementNode.preferredFrameSize = CGSizeMake(320, 100);
return elementNode;
}
- (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
{
return 100;
}
@end

View File

@@ -0,0 +1,13 @@
//
// AsyncViewController.h
// Sample
//
// Created by Scott Goodson on 9/26/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import "ASViewController.h"
@interface AsyncViewController : ASViewController <UITabBarControllerDelegate>
@end

View File

@@ -0,0 +1,37 @@
//
// AsyncViewController.m
// Sample
//
// Created by Scott Goodson on 9/26/15.
// Copyright © 2015 Facebook. All rights reserved.
//
#import "AsyncViewController.h"
#import "RandomCoreGraphicsNode.h"
@implementation AsyncViewController
- (instancetype)init
{
if (!(self = [super initWithNode:[[RandomCoreGraphicsNode alloc] init]])) {
return nil;
}
self.neverShowPlaceholders = YES;
self.tabBarItem = [[UITabBarItem alloc] initWithTabBarSystemItem:UITabBarSystemItemFavorites tag:0];
return self;
}
- (void)viewWillAppear:(BOOL)animated
{
// FIXME: This is only being called on the first time the UITabBarController shows us.
[super viewWillAppear:animated];
}
- (void)viewDidDisappear:(BOOL)animated
{
[self.node recursivelyClearContents];
[super viewDidDisappear:animated];
}
@end

View File

@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>com.facebook.AsyncDisplayKit.$(PRODUCT_NAME:rfc1034identifier)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>$(PRODUCT_NAME)</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
</dict>
</plist>

View File

@@ -0,0 +1,16 @@
//
// RandomCoreGraphicsNode.h
// Sample
//
// Created by Scott Goodson on 9/5/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import <AsyncDisplayKit/AsyncDisplayKit.h>
@interface RandomCoreGraphicsNode : ASCellNode
{
ASTextNode *_textNode;
}
@end

View File

@@ -0,0 +1,93 @@
//
// RandomCoreGraphicsNode.m
// Sample
//
// Created by Scott Goodson on 9/5/15.
// Copyright (c) 2015 Facebook. All rights reserved.
//
#import "RandomCoreGraphicsNode.h"
#import <AsyncDisplayKit/ASDisplayNode+Subclasses.h>
@implementation RandomCoreGraphicsNode
+ (UIColor *)randomColor
{
CGFloat hue = ( arc4random() % 256 / 256.0 ); // 0.0 to 1.0
CGFloat saturation = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from white
CGFloat brightness = ( arc4random() % 128 / 256.0 ) + 0.5; // 0.5 to 1.0, away from black
return [UIColor colorWithHue:hue saturation:saturation brightness:brightness alpha:1];
}
+ (void)drawRect:(CGRect)bounds withParameters:(id<NSObject>)parameters isCancelled:(asdisplaynode_iscancelled_block_t)isCancelledBlock isRasterizing:(BOOL)isRasterizing
{
CGFloat locations[3];
NSMutableArray *colors = [NSMutableArray arrayWithCapacity:3];
[colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]];
locations[0] = 0.0;
[colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]];
locations[1] = 1.0;
[colors addObject:(id)[[RandomCoreGraphicsNode randomColor] CGColor]];
locations[2] = ( arc4random() % 256 / 256.0 );
CGContextRef ctx = UIGraphicsGetCurrentContext();
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGGradientRef gradient = CGGradientCreateWithColors(colorSpace, (CFArrayRef)colors, locations);
CGGradientDrawingOptions drawingOptions;
CGContextDrawLinearGradient(ctx, gradient, CGPointZero, CGPointMake(bounds.size.width, bounds.size.height), drawingOptions);
CGColorSpaceRelease(colorSpace);
}
- (NSObject *)drawParametersForAsyncLayer:(_ASDisplayLayer *)layer
{
return [self description];
}
- (NSDictionary *)textStyle
{
UIFont *font = [UIFont fontWithName:@"HelveticaNeue" size:36.0f];
NSMutableParagraphStyle *style = [[NSParagraphStyle defaultParagraphStyle] mutableCopy];
style.paragraphSpacing = 0.5 * font.lineHeight;
style.hyphenationFactor = 1.0;
return @{ NSFontAttributeName: font,
NSParagraphStyleAttributeName: style };
}
- (instancetype)init
{
if (!(self = [super init])) {
return nil;
}
_textNode = [[ASTextNode alloc] init];
_textNode.placeholderEnabled = NO;
_textNode.attributedString = [[NSAttributedString alloc] initWithString:@"Hello, ASDK!"
attributes:[self textStyle]];
[self addSubnode:_textNode];
return self;
}
- (CGSize)calculateSizeThatFits:(CGSize)constrainedSize
{
[_textNode measure:constrainedSize];
return CGSizeMake(constrainedSize.width, 100);
}
- (void)layout
{
CGSize boundsSize = self.bounds.size;
CGSize textSize = _textNode.calculatedSize;
CGRect textRect = CGRectMake(roundf((boundsSize.width - textSize.width) / 2.0),
roundf((boundsSize.height - textSize.height) / 2.0),
textSize.width,
textSize.height);
_textNode.frame = textRect;
}
@end

View File

@@ -0,0 +1,20 @@
/* This file provided by Facebook is for non-commercial testing and evaluation
* purposes only. Facebook reserves all rights not expressly granted.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
* FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
* ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#import <UIKit/UIKit.h>
#import "AppDelegate.h"
int main(int argc, char * argv[]) {
@autoreleasepool {
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
}
}