diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index d9fe7520bd..9354ecdeac 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -64,6 +64,22 @@ void ASDisplayNodePerformBlockOnMainThread(void (^block)()) } } +void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()) +{ + ASDisplayNodeCAssertNotNil(block, @"block is required"); + if (!block) { + return; + } + + if (node.nodeLoaded) { + ASDisplayNodePerformBlockOnMainThread(^{ + block(); + }); + } else { + block(); + } +} + + (void)initialize { if (self == [ASDisplayNode class]) { diff --git a/AsyncDisplayKit/ASTextNode.mm b/AsyncDisplayKit/ASTextNode.mm index 0206040c07..e5d4ac2348 100644 --- a/AsyncDisplayKit/ASTextNode.mm +++ b/AsyncDisplayKit/ASTextNode.mm @@ -11,6 +11,7 @@ #import #import #import +#import #import #import #import @@ -202,7 +203,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) _constrainedSize = constrainedSizeForText; [self _invalidateRenderer]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); CGSize rendererSize = [[self _renderer] size]; // Add shadow padding back @@ -337,19 +340,21 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) // We need an entirely new renderer [self _invalidateRenderer]; - // Tell the display node superclasses that the cached layout is incorrect now - [self invalidateCalculatedLayout]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + // Tell the display node superclasses that the cached layout is incorrect now + [self invalidateCalculatedLayout]; - [self setNeedsDisplay]; + [self setNeedsDisplay]; - self.accessibilityLabel = _attributedString.string; + self.accessibilityLabel = _attributedString.string; - if (_attributedString.length == 0) { - // We're not an accessibility element by default if there is no string. - self.isAccessibilityElement = NO; - } else { - self.isAccessibilityElement = YES; - } + if (_attributedString.length == 0) { + // We're not an accessibility element by default if there is no string. + self.isAccessibilityElement = NO; + } else { + self.isAccessibilityElement = YES; + } + }); } #pragma mark - Text Layout @@ -360,7 +365,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) _exclusionPaths = exclusionPaths; [self _invalidateRenderer]; [self invalidateCalculatedLayout]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); } } @@ -897,7 +904,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) } _shadowColor = shadowColor; [self _invalidateShadower]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); } } @@ -911,7 +920,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) if (!CGSizeEqualToSize(_shadowOffset, shadowOffset)) { _shadowOffset = shadowOffset; [self _invalidateShadower]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); } } @@ -925,7 +936,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) if (_shadowOpacity != shadowOpacity) { _shadowOpacity = shadowOpacity; [self _invalidateShadower]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); } } @@ -939,7 +952,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) if (_shadowRadius != shadowRadius) { _shadowRadius = shadowRadius; [self _invalidateShadower]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); } } @@ -981,7 +996,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) if (_truncationMode != truncationMode) { _truncationMode = truncationMode; [self _invalidateRenderer]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); } } @@ -994,8 +1011,10 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) { if (_maximumLineCount != maximumLineCount) { _maximumLineCount = maximumLineCount; - [self _invalidateRenderer]; + [self _invalidateRenderer]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ [self setNeedsDisplay]; + }); } } @@ -1010,7 +1029,9 @@ ASDISPLAYNODE_INLINE CGFloat ceilPixelValue(CGFloat f) { _composedTruncationString = [self _prepareTruncationStringForDrawing:[self _composedTruncationString]]; [self _invalidateRenderer]; - [self setNeedsDisplay]; + ASDisplayNodeRespectThreadAffinityOfNode(self, ^{ + [self setNeedsDisplay]; + }); } /** diff --git a/AsyncDisplayKit/Details/ASTextNodeCoreTextAdditions.m b/AsyncDisplayKit/Details/ASTextNodeCoreTextAdditions.m index 3ea9d596f2..55efebdce8 100644 --- a/AsyncDisplayKit/Details/ASTextNodeCoreTextAdditions.m +++ b/AsyncDisplayKit/Details/ASTextNodeCoreTextAdditions.m @@ -11,6 +11,8 @@ #import #import +#import "ASAssert.h" + #pragma mark - Public BOOL ASAttributeWithNameIsUnsupportedCoreTextAttribute(NSString *attributeName) { @@ -65,8 +67,13 @@ NSDictionary *NSAttributedStringAttributesForCoreTextAttributes(NSDictionary *co CTFontRef coreTextFont = (__bridge CTFontRef)coreTextValue; NSString *fontName = (__bridge_transfer NSString *)CTFontCopyPostScriptName(coreTextFont); CGFloat fontSize = CTFontGetSize(coreTextFont); - - cleanAttributes[NSFontAttributeName] = [UIFont fontWithName:fontName size:fontSize]; + UIFont *font = [UIFont fontWithName:fontName size:fontSize]; + ASDisplayNodeCAssertNotNil(font, @"unable to load font %@ with size %f", fontName, fontSize); + if (font == nil) { + // Gracefully fail if we were unable to load the font. + font = [UIFont systemFontOfSize:fontSize]; + } + cleanAttributes[NSFontAttributeName] = font; } // kCTKernAttributeName -> NSKernAttributeName else if ([coreTextKey isEqualToString:(NSString *)kCTKernAttributeName]) { diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index b59c34d754..c3e476a441 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -20,6 +20,7 @@ BOOL ASDisplayNodeSubclassOverridesSelector(Class subclass, SEL selector); void ASDisplayNodePerformBlockOnMainThread(void (^block)()); +void ASDisplayNodeRespectThreadAffinityOfNode(ASDisplayNode *node, void (^block)()); typedef NS_OPTIONS(NSUInteger, ASDisplayNodeMethodOverrides) { ASDisplayNodeMethodOverrideNone = 0, diff --git a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm index dc683fd677..4768197ce0 100644 --- a/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm +++ b/AsyncDisplayKitTests/ASTextNodeWordKernerTests.mm @@ -80,7 +80,7 @@ CGFloat expectedWidth = [@" " sizeWithAttributes:@{ NSFontAttributeName : font }].width; CGRect boundingBox = [_layoutManagerDelegate layoutManager:_components.layoutManager boundingBoxForControlGlyphAtIndex:0 forTextContainer:_components.textContainer proposedLineFragment:CGRectZero glyphPosition:CGPointZero characterIndex:0]; - + XCTAssertEqualWithAccuracy(boundingBox.size.width, expectedWidth, FLT_EPSILON, @"Word kerning shouldn't alter the default width of %f. Encountered space width was %f", expectedWidth, boundingBox.size.width); } @@ -111,7 +111,7 @@ } NSGlyphProperty *glyphProperties = (NSGlyphProperty *)malloc(sizeof(NSGlyphProperty) * glyphCount); CGGlyph *glyphs = (CGGlyph *)malloc(sizeof(CGGlyph) * glyphCount); - NSInteger glyphsToChange = [_layoutManagerDelegate layoutManager:_components.layoutManager shouldGenerateGlyphs:glyphs properties:glyphProperties characterIndexes:characterIndexes font:NULL forGlyphRange:stringRange]; + NSInteger glyphsToChange = [_layoutManagerDelegate layoutManager:_components.layoutManager shouldGenerateGlyphs:glyphs properties:glyphProperties characterIndexes:characterIndexes font:[UIFont systemFontOfSize:12.0] forGlyphRange:stringRange]; free(characterIndexes); free(glyphProperties); free(glyphs);