Several bug fixes for the 1.0 release of AsyncDisplayKit

Fixes:
- Usage of UIScreen in -[ASDisplayNode init]
  (the offending individual will be prosecuted)
- #38: Proper teardown of nodes owned by ASTableView / ASRangeController
- #34: Fix infinite recursion in very rare subclassing scenario
- #30: Avoid animating cell and section additions to ASTableView
- #19: Set a more reasonable default for maximum display concurrency

r=nadi
This commit is contained in:
Scott Goodson 2014-10-14 20:53:25 -07:00
parent f7d91bb877
commit c61b1c294c
4 changed files with 74 additions and 41 deletions

View File

@ -47,6 +47,22 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
return (superclassIMP != subclassIMP); return (superclassIMP != subclassIMP);
} }
CGFloat ASDisplayNodeScreenScale()
{
static CGFloat screenScale = 0.0;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
if ([NSThread isMainThread]) {
screenScale = [[UIScreen mainScreen] scale];
} else {
dispatch_sync(dispatch_get_main_queue(), ^{
screenScale = [[UIScreen mainScreen] scale];
});
}
});
return screenScale;
}
+ (void)initialize + (void)initialize
{ {
if (self == [ASDisplayNode class]) { if (self == [ASDisplayNode class]) {
@ -74,14 +90,44 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
return [_ASDisplayLayer class]; return [_ASDisplayLayer class];
} }
#pragma mark - NSObject Overrides #pragma mark - Lifecycle
// Avoid recursive loops if a subclass implements an init method that calls -initWith*Class:
- (void)_initializeInstance
{
_contentsScaleForDisplay = ASDisplayNodeScreenScale();
_displaySentinel = [[ASSentinel alloc] init];
_flags.inWindow = NO;
_flags.displaysAsynchronously = YES;
// As an optimization, it may be worth a caching system that performs these checks once per class in +initialize (see above).
_flags.implementsDisplay = [[self class] respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] || [self.class respondsToSelector:@selector(displayWithParameters:isCancelled:)];
_flags.hasClassDisplay = ([[self class] respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
_flags.hasWillDisplayAsyncLayer = ([self respondsToSelector:@selector(willDisplayAsyncLayer:)] ? 1 : 0);
_flags.hasDrawParametersForAsyncLayer = ([self respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
}
- (id)init
{
if (!(self = [super init]))
return nil;
[self _initializeInstance];
return self;
}
- (id)initWithViewClass:(Class)viewClass - (id)initWithViewClass:(Class)viewClass
{ {
if (!(self = [self init])) if (!(self = [super init]))
return nil; return nil;
ASDisplayNodeAssert([viewClass isSubclassOfClass:[UIView class]], @"should initialize with a subclass of UIView"); ASDisplayNodeAssert([viewClass isSubclassOfClass:[UIView class]], @"should initialize with a subclass of UIView");
[self _initializeInstance];
_viewClass = viewClass; _viewClass = viewClass;
_flags.isSynchronous = ![viewClass isSubclassOfClass:[_ASDisplayView class]]; _flags.isSynchronous = ![viewClass isSubclassOfClass:[_ASDisplayView class]];
@ -90,40 +136,19 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
- (id)initWithLayerClass:(Class)layerClass - (id)initWithLayerClass:(Class)layerClass
{ {
if (!(self = [self init])) if (!(self = [super init]))
return nil; return nil;
ASDisplayNodeAssert([layerClass isSubclassOfClass:[CALayer class]], @"should initialize with a subclass of CALayer"); ASDisplayNodeAssert([layerClass isSubclassOfClass:[CALayer class]], @"should initialize with a subclass of CALayer");
[self _initializeInstance];
_layerClass = layerClass; _layerClass = layerClass;
_flags.isSynchronous = ![layerClass isSubclassOfClass:[_ASDisplayLayer class]]; _flags.isSynchronous = ![layerClass isSubclassOfClass:[_ASDisplayLayer class]];
_flags.isLayerBacked = YES; _flags.isLayerBacked = YES;
return self; return self;
} }
- (id)init
{
self = [super init];
if (!self) return nil;
_contentsScaleForDisplay = [[UIScreen mainScreen] scale];
_displaySentinel = [[ASSentinel alloc] init];
_flags.inWindow = NO;
_flags.displaysAsynchronously = YES;
_flags.implementsDisplay = [[self class] respondsToSelector:@selector(drawRect:withParameters:isCancelled:isRasterizing:)] || [self.class respondsToSelector:@selector(displayWithParameters:isCancelled:)];
_flags.hasWillDisplayAsyncLayer = ([self respondsToSelector:@selector(willDisplayAsyncLayer:)] ? 1 : 0);
_flags.hasClassDisplay = ([[self class] respondsToSelector:@selector(displayWithParameters:isCancelled:)] ? 1 : 0);
_flags.hasDrawParametersForAsyncLayer = ([self respondsToSelector:@selector(drawParametersForAsyncLayer:)] ? 1 : 0);
return self;
}
- (void)dealloc - (void)dealloc
{ {
ASDisplayNodeAssertMainThread(); ASDisplayNodeAssertMainThread();
@ -142,9 +167,8 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector)
_view = nil; _view = nil;
_subnodes = nil; _subnodes = nil;
if (_flags.isLayerBacked) { if (_flags.isLayerBacked)
_layer.delegate = nil; _layer.delegate = nil;
}
_layer = nil; _layer = nil;
[self __setSupernode:nil]; [self __setSupernode:nil];

View File

@ -355,10 +355,10 @@ static BOOL _isInterceptedSelector(SEL sel)
if (newSectionCount > sectionCount) { if (newSectionCount > sectionCount) {
NSRange range = NSMakeRange(sectionCount, newSectionCount - sectionCount); NSRange range = NSMakeRange(sectionCount, newSectionCount - sectionCount);
NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range]; NSIndexSet *sections = [NSIndexSet indexSetWithIndexesInRange:range];
[super insertSections:sections withRowAnimation:UITableViewRowAnimationAutomatic]; [super insertSections:sections withRowAnimation:UITableViewRowAnimationNone];
} }
[super insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationAutomatic]; [super insertRowsAtIndexPaths:indexPaths withRowAnimation:UITableViewRowAnimationNone];
[super endUpdates]; [super endUpdates];
} }

View File

@ -65,6 +65,22 @@ typedef NS_ENUM(NSInteger, ASScrollDirection) {
return self; return self;
} }
- (void)dealloc
{
[self teardownAllNodes];
}
- (void)teardownAllNodes
{
for (ASCellNode *node in _nodes.allValues) {
[node removeFromSupernode];
[node.view removeFromSuperview];
}
[_nodes removeAllObjects];
_nodes = nil;
}
+ (dispatch_queue_t)sizingQueue + (dispatch_queue_t)sizingQueue
{ {
static dispatch_queue_t sizingQueue = NULL; static dispatch_queue_t sizingQueue = NULL;
@ -255,16 +271,7 @@ static BOOL ASRangeIsValid(NSRange range)
/* /*
* teardown * teardown
*/ */
for (ASCellNode *node in _nodes.objectEnumerator) { [self teardownAllNodes];
[node removeFromSupernode];
[node.view removeFromSuperview];
}
[_nodes removeAllObjects];
_nodes = nil;
for (UIView *view in [[ASRangeController workingView] subviews]) {
[view removeFromSuperview];
}
/* /*
* setup * setup

View File

@ -23,7 +23,9 @@
#if ASDISPLAYNODE_DELAY_DISPLAY #if ASDISPLAYNODE_DELAY_DISPLAY
static long __ASDisplayLayerMaxConcurrentDisplayCount = 1; static long __ASDisplayLayerMaxConcurrentDisplayCount = 1;
#else #else
static long __ASDisplayLayerMaxConcurrentDisplayCount = 1024; // essentially no limit until we determine a good value. // Basing this off of CPU core count would make sense, but first some experimentation should be done to understand
// if having more ready-to-run work keeps the CPU clock up (or other interesting scheduler effects).
static long __ASDisplayLayerMaxConcurrentDisplayCount = 8;
#endif #endif
static dispatch_semaphore_t __ASDisplayLayerConcurrentDisplaySemaphore; static dispatch_semaphore_t __ASDisplayLayerConcurrentDisplaySemaphore;