[ASDisplayNode] Add Event Tracing to Help Debugging (#2243)

* Add some simple event logging for ASDisplayNode

Improve the tracing

* Add header to copy files phase

* Make event header public
This commit is contained in:
Adlai Holler 2016-09-15 13:24:19 -07:00 committed by GitHub
parent 8459c1e825
commit 25de53bb13
12 changed files with 315 additions and 27 deletions

View File

@ -428,6 +428,10 @@
CC446A311D80AAE00071FD03 /* ASObjectDescriptionHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CC446A2E1D80AAE00071FD03 /* ASObjectDescriptionHelpers.m */; };
CC4981B31D1A02BE004E13CC /* ASTableViewThrashTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */; };
CC4981BD1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */; };
CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */ = {isa = PBXBuildFile; fileRef = CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */; settings = {ATTRIBUTES = (Public, ); }; };
CC4C2A781D88E3BF0039ACAB /* ASTraceEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */; };
CC4C2A791D88E3BF0039ACAB /* ASTraceEvent.m in Sources */ = {isa = PBXBuildFile; fileRef = CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */; };
CC4C2A7A1D8902350039ACAB /* ASTraceEvent.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */; };
CC54A81C1D70079800296A24 /* ASDispatch.h in Headers */ = {isa = PBXBuildFile; fileRef = CC54A81B1D70077A00296A24 /* ASDispatch.h */; };
CC54A81E1D7008B300296A24 /* ASDispatchTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CC54A81D1D7008B300296A24 /* ASDispatchTests.m */; };
CC7FD9DF1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m in Sources */ = {isa = PBXBuildFile; fileRef = CC7FD9DD1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.m */; };
@ -646,6 +650,7 @@
dstPath = "include/$(PRODUCT_NAME)";
dstSubfolderSpec = 16;
files = (
CC4C2A7A1D8902350039ACAB /* ASTraceEvent.h in CopyFiles */,
CC88F7AE1D80AF5E000D6D4E /* ASObjectDescriptionHelpers.h in CopyFiles */,
F7CE6C981D2CDB5800BE4C15 /* ASInternalHelpers.h in CopyFiles */,
F7CE6CB71D2CE2D000BE4C15 /* ASLayoutableExtensibility.h in CopyFiles */,
@ -1111,6 +1116,8 @@
CC4981B21D1A02BE004E13CC /* ASTableViewThrashTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTableViewThrashTests.m; sourceTree = "<group>"; };
CC4981BA1D1C7F65004E13CC /* NSIndexSet+ASHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSIndexSet+ASHelpers.h"; sourceTree = "<group>"; };
CC4981BB1D1C7F65004E13CC /* NSIndexSet+ASHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSIndexSet+ASHelpers.m"; sourceTree = "<group>"; };
CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTraceEvent.h; sourceTree = "<group>"; };
CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTraceEvent.m; sourceTree = "<group>"; };
CC54A81B1D70077A00296A24 /* ASDispatch.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASDispatch.h; sourceTree = "<group>"; };
CC54A81D1D7008B300296A24 /* ASDispatchTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDispatchTests.m; sourceTree = "<group>"; };
CC7FD9DC1BB5E962005CCB2B /* ASPhotosFrameworkImageRequest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASPhotosFrameworkImageRequest.h; sourceTree = "<group>"; };
@ -1428,6 +1435,8 @@
058D09E1195D050800B7D73C /* Details */ = {
isa = PBXGroup;
children = (
CC4C2A751D88E3BF0039ACAB /* ASTraceEvent.h */,
CC4C2A761D88E3BF0039ACAB /* ASTraceEvent.m */,
058D09E2195D050800B7D73C /* _ASDisplayLayer.h */,
058D09E3195D050800B7D73C /* _ASDisplayLayer.mm */,
058D09E4195D050800B7D73C /* _ASDisplayView.h */,
@ -1860,6 +1869,7 @@
34EFC7701B701CFA00AD841F /* ASStackLayoutDefines.h in Headers */,
764D83D51C8EA515009B4FB8 /* AsyncDisplayKit+Debug.h in Headers */,
E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */,
CC4C2A771D88E3BF0039ACAB /* ASTraceEvent.h in Headers */,
254C6B7B1BF94DF4003EC431 /* ASTextKitRenderer+Positioning.h in Headers */,
CC7FD9E21BB603FF005CCB2B /* ASPhotosFrameworkImageRequest.h in Headers */,
DE4843DC1C93EAC100A1F33B /* ASLayoutTransition.h in Headers */,
@ -2183,6 +2193,7 @@
257754AE1BEE44CD00737CA5 /* ASTextKitRenderer+Positioning.mm in Sources */,
ACF6ED2E1B17843500DA7C62 /* ASRatioLayoutSpec.mm in Sources */,
AC47D9461B3BB41900AAEE9D /* ASRelativeSize.mm in Sources */,
CC4C2A781D88E3BF0039ACAB /* ASTraceEvent.m in Sources */,
205F0E121B371BD7007741D0 /* ASScrollDirection.m in Sources */,
9C8898BB1C738B9800D6B02E /* ASTextKitFontSizeAdjuster.mm in Sources */,
D785F6631A74327E00291744 /* ASScrollNode.m in Sources */,
@ -2365,6 +2376,7 @@
B35062271B010EFD0018CF92 /* ASRangeController.mm in Sources */,
0442850A1BAA63FE00D16268 /* ASBatchFetching.m in Sources */,
68FC85E61CE29B9400EDD713 /* ASNavigationController.m in Sources */,
CC4C2A791D88E3BF0039ACAB /* ASTraceEvent.m in Sources */,
34EFC76F1B701CF700AD841F /* ASRatioLayoutSpec.mm in Sources */,
254C6B8B1BF94F8A003EC431 /* ASTextKitShadower.mm in Sources */,
34EFC7661B701CD200AD841F /* ASRelativeSize.mm in Sources */,

View File

@ -10,6 +10,7 @@
#import "ASDisplayNode.h"
#import "ASLayoutRangeType.h"
#import "ASTraceEvent.h"
NS_ASSUME_NONNULL_BEGIN
@ -18,6 +19,20 @@ void ASPerformBlockOnMainThread(void (^block)());
void ASPerformBlockOnBackgroundThread(void (^block)()); // DISPATCH_QUEUE_PRIORITY_DEFAULT
ASDISPLAYNODE_EXTERN_C_END
#ifndef ASDISPLAYNODE_EVENTLOG_CAPACITY
#define ASDISPLAYNODE_EVENTLOG_CAPACITY 20
#endif
#ifndef ASDISPLAYNODE_EVENTLOG_ENABLE
#define ASDISPLAYNODE_EVENTLOG_ENABLE DEBUG
#endif
#if ASDISPLAYNODE_EVENTLOG_ENABLE
#define ASDisplayNodeLogEvent(node, ...) [node _logEventWithBacktrace:[NSThread callStackSymbols] format:__VA_ARGS__]
#else
#define ASDisplayNodeLogEvent(node, ...)
#endif
/**
* Bitmask to indicate what performance measurements the cell should record.
*/
@ -110,6 +125,20 @@ extern NSString *const ASDisplayNodeLayoutGenerationNumberOfPassesKey;
*/
+ (void)setRangeModeForMemoryWarnings:(ASLayoutRangeMode)rangeMode;
#if ASDISPLAYNODE_EVENTLOG_ENABLE
/**
* The primitive event tracing method. You shouldn't call this. Use the ASDisplayNodeLogEvent macro instead.
*/
- (void)_logEventWithBacktrace:(NSArray<NSString *> *)backtrace format:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3);
/**
* @abstract The most recent trace events for this node. Max count is ASDISPLAYNODE_EVENTLOG_CAPACITY.
*/
@property (readonly, copy) NSArray *eventLog;
#endif
@end
NS_ASSUME_NONNULL_END

View File

@ -308,6 +308,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
- (void)_initializeInstance
{
[self _staticInitialize];
_eventLogHead = -1;
_contentsScaleForDisplay = ASScreenScale();
_displaySentinel = [[ASSentinel alloc] init];
@ -323,6 +324,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
_flags.canClearContentsOfLayer = YES;
_flags.canCallSetNeedsDisplayOfLayer = YES;
ASDisplayNodeLogEvent(self, @"init");
}
- (instancetype)init
@ -428,7 +430,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
{
ASDisplayNodeAssertMainThread();
// Synchronous nodes may not be able to call the hierarchy notifications, so only enforce for regular nodes.
ASDisplayNodeAssert(_flags.synchronous || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating; interfaceState: %lu, %@", (unsigned long)_interfaceState, self);
ASDisplayNodeAssert(_flags.synchronous || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating. Node: %@", self);
self.asyncLayer.asyncDelegate = nil;
_view.asyncdisplaykit_node = nil;
@ -586,6 +588,51 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
}
}
#if ASDISPLAYNODE_EVENTLOG_ENABLE
- (void)_logEventWithBacktrace:(NSArray<NSString *> *)backtrace format:(NSString *)format, ...
{
va_list args;
va_start(args, format);
ASTraceEvent *event = [[ASTraceEvent alloc] initWithObject:self
backtrace:backtrace
format:format
arguments:args];
va_end(args);
ASDN::MutexLocker l(__instanceLock__);
// Create the array if needed.
if (_eventLog == nil) {
_eventLog = [NSMutableArray arrayWithCapacity:ASDISPLAYNODE_EVENTLOG_CAPACITY];
}
// Increment the head index.
_eventLogHead = (_eventLogHead + 1) % ASDISPLAYNODE_EVENTLOG_CAPACITY;
if (_eventLogHead < _eventLog.count) {
[_eventLog replaceObjectAtIndex:_eventLogHead withObject:event];
} else {
[_eventLog insertObject:event atIndex:_eventLogHead];
}
}
- (NSArray<ASTraceEvent *> *)eventLog
{
ASDN::MutexLocker l(__instanceLock__);
NSUInteger tail = (_eventLogHead + 1);
NSUInteger count = _eventLog.count;
NSMutableArray<ASTraceEvent *> *result = [NSMutableArray array];
// Start from `tail` and go through array, wrapping around when we exceed end index.
for (NSUInteger actualIndex = 0; actualIndex < ASDISPLAYNODE_EVENTLOG_CAPACITY; actualIndex++) {
NSInteger ringIndex = (tail + actualIndex) % ASDISPLAYNODE_EVENTLOG_CAPACITY;
if (ringIndex < count) {
[result addObject:_eventLog[ringIndex]];
}
}
return result;
}
#endif
- (UIView *)view
{
ASDisplayNodeAssert(!_flags.layerBacked, @"Call to -view undefined on layer-backed nodes");
@ -1646,6 +1693,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD
_subnodes = [[NSMutableArray alloc] init];
}
ASDisplayNodeLogEvent(self, @"%@ %@", NSStringFromSelector(_cmd), subnode);
[_subnodes addObject:subnode];
// This call will apply our .hierarchyState to the new subnode.
@ -1701,6 +1749,7 @@ static bool disableNotificationsForMovingBetweenParents(ASDisplayNode *from, ASD
if (!_subnodes)
_subnodes = [[NSMutableArray alloc] init];
ASDisplayNodeLogEvent(self, @"%@: %@", NSStringFromSelector(_cmd), subnode);
[_subnodes insertObject:subnode atIndex:subnodeIndex];
[subnode __setSupernode:self];
@ -1945,6 +1994,7 @@ static NSInteger incrementIfFound(NSInteger i) {
return;
}
ASDisplayNodeLogEvent(self, @"%@: %@", NSStringFromSelector(_cmd), subnode);
[_subnodes removeObjectIdenticalTo:subnode];
[subnode __setSupernode:nil];
@ -2157,6 +2207,7 @@ static NSInteger incrementIfFound(NSInteger i) {
}
if (supernodeDidChange) {
ASDisplayNodeLogEvent(self, @"supernodeDidChange: %@, oldValue = %@", ASObjectDescriptionMakeTiny(newSupernode), ASObjectDescriptionMakeTiny(oldSupernode));
// Hierarchy state
ASHierarchyState stateToEnterOrExit = (newSupernode ? newSupernode.hierarchyState
: oldSupernode.hierarchyState);
@ -2422,11 +2473,13 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
ASLayoutableValidateLayout(layout);
#endif
}
ASDisplayNodeLogEvent(self, @"computedLayout: %@", layout);
return [layout filteredNodeLayoutTree];
} else {
// If neither -layoutSpecThatFits: nor -calculateSizeThatFits: is overridden by subclassses, preferredFrameSize should be used,
// assume that the default implementation of -calculateSizeThatFits: returns it.
CGSize size = [self calculateSizeThatFits:constrainedSize.max];
ASDisplayNodeLogEvent(self, @"calculatedSize: %@", NSStringFromCGSize(size));
return [ASLayout layoutWithLayoutable:self size:ASSizeRangeClamp(constrainedSize, size) sublayouts:nil];
}
}
@ -2565,6 +2618,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
{
ASDN::MutexLocker l(__instanceLock__);
ASDisplayNodeLogEvent(self, @"didLoad");
for (ASDisplayNodeDidLoadBlock block in _onDidLoadBlocks) {
block(self);
}
@ -2765,8 +2819,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
if (nowPreload != wasPreload) {
if (nowPreload) {
ASDisplayNodeLogEvent(self, @"didEnterPreloadState: %@", NSStringFromASInterfaceState(newState));
[self didEnterPreloadState];
} else {
ASDisplayNodeLogEvent(self, @"didExitPreloadState: %@", NSStringFromASInterfaceState(newState));
[self didExitPreloadState];
}
}
@ -2813,8 +2869,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}
if (nowDisplay) {
ASDisplayNodeLogEvent(self, @"didEnterDisplayState: %@", NSStringFromASInterfaceState(newState));
[self didEnterDisplayState];
} else {
ASDisplayNodeLogEvent(self, @"didExitDisplayState: %@", NSStringFromASInterfaceState(newState));
[self didExitDisplayState];
}
}
@ -2826,8 +2884,10 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
if (nowVisible != wasVisible) {
if (nowVisible) {
ASDisplayNodeLogEvent(self, @"didEnterVisibleState: %@", NSStringFromASInterfaceState(newState));
[self didEnterVisibleState];
} else {
ASDisplayNodeLogEvent(self, @"didExitVisibleState: %@", NSStringFromASInterfaceState(newState));
[self didExitVisibleState];
}
}
@ -2855,6 +2915,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
if (interfaceState == ASInterfaceStateNone) {
return; // This method is a no-op with a 0-bitfield argument, so don't bother recursing.
}
ASDisplayNodeLogEvent(self, @"%@ %@", NSStringFromSelector(_cmd), NSStringFromASInterfaceState(interfaceState));
ASDisplayNodePerformBlockOnEveryNode(nil, self, ^(ASDisplayNode *node) {
node.interfaceState &= (~interfaceState);
});
@ -2929,10 +2990,8 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
}
}
}
if (newState != oldState) {
LOG(@"setHierarchyState: oldState = %lu, newState = %lu", (unsigned long)oldState, (unsigned long)newState);
}
ASDisplayNodeLogEvent(self, @"setHierarchyState: oldState = %@, newState = %@", NSStringFromASHierarchyState(oldState), NSStringFromASHierarchyState(newState));
}
- (void)enterHierarchyState:(ASHierarchyState)hierarchyState
@ -2979,6 +3038,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeLogEvent(self, @"displayWillStart");
// in case current node takes longer to display than it's subnodes, treat it as a dependent node
[self _pendingNodeWillDisplay:self];
@ -2989,6 +3049,7 @@ void recursivelyTriggerDisplayForLayer(CALayer *layer, BOOL shouldBlock)
{
ASDisplayNodeAssertMainThread();
ASDisplayNodeLogEvent(self, @"displayDidFinish");
[self _pendingNodeDidDisplay:self];
[_supernode subnodeDisplayDidFinish:self];
@ -3338,15 +3399,15 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
}
if (self.layerBacked) {
CALayer *rootLayer = self.layer;
CALayer *rootLayer = _layer;
CALayer *nextLayer = rootLayer;
while ((nextLayer = rootLayer.superlayer) != nil) {
rootLayer = nextLayer;
}
return [self.layer convertRect:self.threadSafeBounds toLayer:rootLayer];
return [_layer convertRect:self.threadSafeBounds toLayer:rootLayer];
} else {
return [self.view convertRect:self.threadSafeBounds toView:nil];
return [_view convertRect:self.threadSafeBounds toView:nil];
}
}
@ -3396,6 +3457,7 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
{
if (ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(environmentTraitCollection, _environmentState.environmentTraitCollection) == NO) {
_environmentState.environmentTraitCollection = environmentTraitCollection;
ASDisplayNodeLogEvent(self, @"asyncTraitCollectionDidChange: %@", NSStringFromASEnvironmentTraitCollection(environmentTraitCollection));
[self asyncTraitCollectionDidChange];
}
}
@ -3411,7 +3473,7 @@ ASEnvironmentLayoutExtensibilityForwarding
- (void)asyncTraitCollectionDidChange
{
// Subclass override
}
#if TARGET_OS_TV

View File

@ -36,7 +36,7 @@ ASDISPLAYNODE_INLINE BOOL ASInterfaceStateIncludesMeasureLayout(ASInterfaceState
return ((interfaceState & ASInterfaceStateMeasureLayout) == ASInterfaceStateMeasureLayout);
}
ASDISPLAYNODE_INLINE NSString * _Nonnull NSStringFromASInterfaceState(ASInterfaceState interfaceState)
__unused static NSString * _Nonnull NSStringFromASInterfaceState(ASInterfaceState interfaceState)
{
NSMutableArray *states = [NSMutableArray array];
if (interfaceState == ASInterfaceStateNone) {

View File

@ -77,7 +77,7 @@ extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionMakeDefault();
extern ASEnvironmentTraitCollection ASEnvironmentTraitCollectionFromUITraitCollection(UITraitCollection *traitCollection);
extern BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnvironmentTraitCollection lhs, ASEnvironmentTraitCollection rhs);
extern NSString *NSStringFromASEnvironmentTraitCollection(ASEnvironmentTraitCollection traits);
#pragma mark - ASEnvironmentState
typedef struct ASEnvironmentState {

View File

@ -10,6 +10,7 @@
#import "ASEnvironmentInternal.h"
#import "ASAvailability.h"
#import "ASObjectDescriptionHelpers.h"
ASEnvironmentLayoutOptionsState ASEnvironmentLayoutOptionsStateMakeDefault()
{
@ -61,6 +62,57 @@ BOOL ASEnvironmentTraitCollectionIsEqualToASEnvironmentTraitCollection(ASEnviron
CGSizeEqualToSize(lhs.containerSize, rhs.containerSize);
}
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceIdiom(UIUserInterfaceIdiom idiom) {
switch (idiom) {
case UIUserInterfaceIdiomTV:
return @"TV";
case UIUserInterfaceIdiomPad:
return @"Pad";
case UIUserInterfaceIdiomPhone:
return @"Phone";
case UIUserInterfaceIdiomCarPlay:
return @"CarPlay";
default:
return @"Unspecified";
}
}
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIForceTouchCapability(UIForceTouchCapability capability) {
switch (capability) {
case UIForceTouchCapabilityAvailable:
return @"Available";
case UIForceTouchCapabilityUnavailable:
return @"Unavailable";
default:
return @"Unknown";
}
}
// Named so as not to conflict with a hidden Apple function, in case compiler decides not to inline
ASDISPLAYNODE_INLINE NSString *AS_NSStringFromUIUserInterfaceSizeClass(UIUserInterfaceSizeClass sizeClass) {
switch (sizeClass) {
case UIUserInterfaceSizeClassCompact:
return @"Compact";
case UIUserInterfaceSizeClassRegular:
return @"Regular";
default:
return @"Unspecified";
}
}
NSString *NSStringFromASEnvironmentTraitCollection(ASEnvironmentTraitCollection traits)
{
NSMutableArray<NSDictionary *> *props = [NSMutableArray array];
[props addObject:@{ @"userInterfaceIdiom": AS_NSStringFromUIUserInterfaceIdiom(traits.userInterfaceIdiom) }];
[props addObject:@{ @"containerSize": NSStringFromCGSize(traits.containerSize) }];
[props addObject:@{ @"horizontalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.horizontalSizeClass) }];
[props addObject:@{ @"verticalSizeClass": AS_NSStringFromUIUserInterfaceSizeClass(traits.verticalSizeClass) }];
[props addObject:@{ @"forceTouchCapability": AS_NSStringFromUIForceTouchCapability(traits.forceTouchCapability) }];
return ASObjectDescriptionMakeWithoutObject(props);
}
ASEnvironmentState ASEnvironmentStateMakeDefault()
{
return (ASEnvironmentState) {

View File

@ -0,0 +1,25 @@
//
// ASTraceEvent.h
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/13/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import <Foundation/Foundation.h>
@interface ASTraceEvent : NSObject
/**
* This method is dealloc safe.
*/
- (instancetype)initWithObject:(id)object
backtrace:(NSArray<NSString *> *)backtrace
format:(NSString *)format
arguments:(va_list)arguments NS_FORMAT_FUNCTION(3,0);
@property (nonatomic, readonly) NSArray<NSString *> *backtrace;
@property (nonatomic, strong, readonly) NSString *message;
@property (nonatomic, readonly) NSTimeInterval timestamp;
@end

View File

@ -0,0 +1,60 @@
//
// ASTraceEvent.m
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/13/16.
// Copyright © 2016 Facebook. All rights reserved.
//
#import "ASTraceEvent.h"
#import <QuartzCore/QuartzCore.h>
#import "ASObjectDescriptionHelpers.h"
@interface ASTraceEvent ()
@property (nonatomic, strong, readonly) NSString *objectDescription;
@property (nonatomic, strong, readonly) NSString *threadDescription;
@end
@implementation ASTraceEvent
- (instancetype)initWithObject:(id)object backtrace:(NSArray<NSString *> *)backtrace format:(NSString *)format arguments:(va_list)args
{
self = [super init];
if (self != nil) {
static NSTimeInterval refTime;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
refTime = CACurrentMediaTime();
});
// Create the format string passed to us.
_message = [[NSString alloc] initWithFormat:format arguments:args];
_objectDescription = ASObjectDescriptionMakeTiny(object);
NSThread *thread = [NSThread currentThread];
NSString *threadDescription = thread.name;
if (threadDescription.length == 0) {
if ([thread isMainThread]) {
threadDescription = @"Main";
} else {
// Want these to be 4-chars to line up with "Main". It's possible that a collision could happen
// here but it's so unbelievably likely to impact development, the risk is acceptable.
NSString *ptrString = [NSString stringWithFormat:@"%p", thread];
threadDescription = [ptrString substringFromIndex:MAX(0, ptrString.length - 4)];
}
}
_threadDescription = threadDescription;
_backtrace = backtrace;
_timestamp = CACurrentMediaTime() - refTime;
}
return self;
}
- (NSString *)description
{
return [NSString stringWithFormat:@"<%@ (%@) t=%7.3f: %@>", _objectDescription, _threadDescription, _timestamp, _message];
}
@end

View File

@ -50,16 +50,47 @@ typedef NS_OPTIONS(NSUInteger, ASHierarchyState)
ASHierarchyStateLayoutPending = 1 << 3
};
inline BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState)
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesLayoutPending(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateLayoutPending) == ASHierarchyStateLayoutPending);
}
inline BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState)
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRangeManaged(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateRangeManaged) == ASHierarchyStateRangeManaged);
}
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesRasterized(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateRasterized) == ASHierarchyStateRasterized);
}
ASDISPLAYNODE_INLINE BOOL ASHierarchyStateIncludesTransitioningSupernodes(ASHierarchyState hierarchyState)
{
return ((hierarchyState & ASHierarchyStateTransitioningSupernodes) == ASHierarchyStateTransitioningSupernodes);
}
__unused static NSString * _Nonnull NSStringFromASHierarchyState(ASHierarchyState hierarchyState)
{
NSMutableArray *states = [NSMutableArray array];
if (hierarchyState == ASHierarchyStateNormal) {
[states addObject:@"Normal"];
}
if (ASHierarchyStateIncludesRangeManaged(hierarchyState)) {
[states addObject:@"RangeManaged"];
}
if (ASHierarchyStateIncludesLayoutPending(hierarchyState)) {
[states addObject:@"LayoutPending"];
}
if (ASHierarchyStateIncludesRasterized(hierarchyState)) {
[states addObject:@"Rasterized"];
}
if (ASHierarchyStateIncludesTransitioningSupernodes(hierarchyState)) {
[states addObject:@"TransitioningSupernodes"];
}
return [NSString stringWithFormat:@"{ %@ }", [states componentsJoinedByString:@" | "]];
}
@interface ASDisplayNode ()
{
@protected

View File

@ -121,7 +121,10 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
UIEdgeInsets _hitTestSlop;
NSMutableArray *_subnodes;
NSMutableArray<ASTraceEvent *> *_eventLog;
// The index of the most recent log entry. -1 until first entry.
NSInteger _eventLogHead;
// Main thread only
BOOL _automaticallyManagesSubnodes;
_ASTransitionContext *_pendingLayoutTransitionContext;

View File

@ -36,12 +36,10 @@ NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN
/**
* Returns e.g. <MYObject: 0xFFFFFFFF; name = "Object Name"; frame = (0 0; 50 50)>
*
* Note: `object` param is autoreleasing so that this function is dealloc-safe.
* No, unsafe_unretained isn't acceptable here the optimizer may deallocate object early.
*/
/// Useful for structs etc. Returns e.g. { position = (0 0); frame = (0 0; 50 50) }
NSString *ASObjectDescriptionMakeWithoutObject(NSArray<NSDictionary *> * _Nullable propertyGroups);
/// Returns e.g. <MYObject: 0xFFFFFFFF; name = "Object Name"; frame = (0 0; 50 50)>
NSString *ASObjectDescriptionMake(__autoreleasing id object, NSArray<NSDictionary *> * _Nullable propertyGroups);
/**

View File

@ -10,7 +10,8 @@
#import <UIKit/UIKit.h>
#import "NSIndexSet+ASHelpers.h"
NSString *ASGetDescriptionValueString(id object) {
NSString *ASGetDescriptionValueString(id object)
{
if ([object isKindOfClass:[NSValue class]]) {
// Use shortened NSValue descriptions
NSValue *value = object;
@ -37,18 +38,33 @@ NSString *ASGetDescriptionValueString(id object) {
return [object description];
}
NSString *ASObjectDescriptionMake(__autoreleasing id object, NSArray<NSDictionary *> *propertyGroups) {
NSMutableString *str = [NSMutableString stringWithFormat:@"<%@: %p", [object class], object];
NSString *_ASObjectDescriptionMakePropertyList(NSArray<NSDictionary *> * _Nullable propertyGroups)
{
NSMutableArray *components = [NSMutableArray array];
for (NSDictionary *properties in propertyGroups) {
[properties enumerateKeysAndObjectsUsingBlock:^(id _Nonnull key, id _Nonnull obj, BOOL * _Nonnull stop) {
[components addObject:[NSString stringWithFormat:@"%@ = %@", key, ASGetDescriptionValueString(obj)]];
}];
}
if (components.count > 0) {
[str appendString:@"; "];
[str appendString:[components componentsJoinedByString:@"; "]];
return [components componentsJoinedByString:@"; "];
}
NSString *ASObjectDescriptionMakeWithoutObject(NSArray<NSDictionary *> * _Nullable propertyGroups)
{
return [NSString stringWithFormat:@"{ %@ }", _ASObjectDescriptionMakePropertyList(propertyGroups)];
}
NSString *ASObjectDescriptionMake(__autoreleasing id object, NSArray<NSDictionary *> *propertyGroups)
{
if (object == nil) {
return @"(null)";
}
NSMutableString *str = [NSMutableString stringWithFormat:@"<%@: %p", [object class], object];
NSString *propList = _ASObjectDescriptionMakePropertyList(propertyGroups);
if (propList.length > 0) {
[str appendFormat:@"; %@", propList];
}
[str appendString:@">"];
return str;