From fb6d1830a059973b7ea304a8d7cf028fa8106f1e Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Wed, 9 Nov 2016 00:44:49 +0000 Subject: [PATCH] Beter table/collection update history (#2562) - Introduce thread-safe ASEventLog - ASCollectionNode and ASTableNode share their event log with their ASDataController. The controller uses it to log change set submitting and finishing events. - ASCollectionNode and ASTableNode print their data source and delegate in their debug description. --- AsyncDisplayKit.xcodeproj/project.pbxproj | 12 ++ AsyncDisplayKit/ASCollectionNode.mm | 13 +- AsyncDisplayKit/ASCollectionView.mm | 6 +- AsyncDisplayKit/ASDisplayNode+Beta.h | 41 ++---- AsyncDisplayKit/ASDisplayNode.mm | 58 ++------ AsyncDisplayKit/ASTableNode.mm | 13 +- AsyncDisplayKit/ASTableView.mm | 10 +- AsyncDisplayKit/ASTableViewInternal.h | 4 +- .../Details/ASChangeSetDataController.mm | 14 +- .../Details/ASCollectionDataController.h | 2 +- .../Details/ASCollectionDataController.mm | 4 +- .../Details/ASCollectionInternal.h | 2 +- AsyncDisplayKit/Details/ASDataController.h | 16 +- AsyncDisplayKit/Details/ASDataController.mm | 9 +- .../Details/ASObjectDescriptionHelpers.h | 6 +- .../Details/ASObjectDescriptionHelpers.m | 2 +- .../Private/ASDisplayNodeInternal.h | 9 +- AsyncDisplayKit/Private/ASEventLog.h | 25 ++++ AsyncDisplayKit/Private/ASEventLog.mm | 80 ++++++++++ .../Private/_ASHierarchyChangeSet.h | 9 +- .../Private/_ASHierarchyChangeSet.mm | 139 ++++++++++++++++-- AsyncDisplayKitTests/ASTableViewTests.m | 3 +- 22 files changed, 366 insertions(+), 111 deletions(-) create mode 100644 AsyncDisplayKit/Private/ASEventLog.h create mode 100644 AsyncDisplayKit/Private/ASEventLog.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 3c2e255c6c..a35e941c05 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -486,6 +486,10 @@ E52405B31C8FEF03004DC8E7 /* ASLayoutTransition.mm in Sources */ = {isa = PBXBuildFile; fileRef = E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */; }; E55D86321CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; E55D86331CA8A14000A0C26F /* ASLayoutElement.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */; }; + E55FFC8F1DCCA50700060AC4 /* ASEventLog.h in Headers */ = {isa = PBXBuildFile; fileRef = E55FFC8E1DCCA50700060AC4 /* ASEventLog.h */; settings = {ATTRIBUTES = (Public, ); }; }; + E55FFC911DCCA5A600060AC4 /* ASEventLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55FFC901DCCA5A600060AC4 /* ASEventLog.mm */; }; + E55FFC921DCCA5A600060AC4 /* ASEventLog.mm in Sources */ = {isa = PBXBuildFile; fileRef = E55FFC901DCCA5A600060AC4 /* ASEventLog.mm */; }; + E55FFC931DCCB0A800060AC4 /* ASEventLog.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = E55FFC8E1DCCA50700060AC4 /* ASEventLog.h */; }; E5711A2C1C840C81009619D4 /* ASIndexedNodeContext.h in Headers */ = {isa = PBXBuildFile; fileRef = E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */; }; E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */; }; E5711A301C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */; }; @@ -658,6 +662,7 @@ dstPath = "include/$(PRODUCT_NAME)"; dstSubfolderSpec = 16; files = ( + E55FFC931DCCB0A800060AC4 /* ASEventLog.h in CopyFiles */, 693117CE1DC7C72700DE4784 /* ASDisplayNode+Deprecated.h in CopyFiles */, 69F381A51DA4630D00CF2278 /* NSArray+Diffing.h in CopyFiles */, CC4C2A7A1D8902350039ACAB /* ASTraceEvent.h in CopyFiles */, @@ -1168,6 +1173,8 @@ E52405B21C8FEF03004DC8E7 /* ASLayoutTransition.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASLayoutTransition.mm; sourceTree = ""; }; E52405B41C8FEF16004DC8E7 /* ASLayoutTransition.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASLayoutTransition.h; sourceTree = ""; }; E55D86311CA8A14000A0C26F /* ASLayoutElement.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = ASLayoutElement.mm; path = AsyncDisplayKit/Layout/ASLayoutElement.mm; sourceTree = ""; }; + E55FFC8E1DCCA50700060AC4 /* ASEventLog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASEventLog.h; sourceTree = ""; }; + E55FFC901DCCA5A600060AC4 /* ASEventLog.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASEventLog.mm; sourceTree = ""; }; E5711A2A1C840C81009619D4 /* ASIndexedNodeContext.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIndexedNodeContext.h; sourceTree = ""; }; E5711A2D1C840C96009619D4 /* ASIndexedNodeContext.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASIndexedNodeContext.mm; sourceTree = ""; }; EFA731F0396842FF8AB635EE /* libPods-AsyncDisplayKitTests.a */ = {isa = PBXFileReference; explicitFileType = archive.ar; includeInIndex = 0; path = "libPods-AsyncDisplayKitTests.a"; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1583,6 +1590,8 @@ 6959433C1D70815300B0EE1F /* ASDisplayNodeLayout.mm */, 69E100691CA89CB600D88C1B /* ASEnvironmentInternal.h */, 69E1006A1CA89CB600D88C1B /* ASEnvironmentInternal.mm */, + E55FFC8E1DCCA50700060AC4 /* ASEventLog.h */, + E55FFC901DCCA5A600060AC4 /* ASEventLog.mm */, 68B8A4DB1CBD911D007E4543 /* ASImageNode+AnimatedImagePrivate.h */, 058D0A0D195D050800B7D73C /* ASImageNode+CGExtras.h */, 058D0A0E195D050800B7D73C /* ASImageNode+CGExtras.m */, @@ -1765,6 +1774,7 @@ isa = PBXHeadersBuildPhase; buildActionMask = 2147483647; files = ( + E55FFC8F1DCCA50700060AC4 /* ASEventLog.h in Headers */, 683489281D70DE3400327501 /* ASDisplayNode+Deprecated.h in Headers */, 6907C2581DC4ECFE00374C66 /* ASObjectDescriptionHelpers.h in Headers */, 69E0E8A71D356C9400627613 /* ASEqualityHelpers.h in Headers */, @@ -2235,6 +2245,7 @@ E5711A2E1C840C96009619D4 /* ASIndexedNodeContext.mm in Sources */, 9C8221971BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 251B8EF81BBB3D690087C538 /* ASCollectionDataController.mm in Sources */, + E55FFC911DCCA5A600060AC4 /* ASEventLog.mm in Sources */, ACF6ED301B17843500DA7C62 /* ASStackLayoutSpec.mm in Sources */, 257754BE1BEE458E00737CA5 /* ASTextKitComponents.m in Sources */, 257754A91BEE44CD00737CA5 /* ASTextKitContext.mm in Sources */, @@ -2422,6 +2433,7 @@ 254C6B851BF94F8A003EC431 /* ASTextKitAttributes.mm in Sources */, 509E68601B3AED8E009B9150 /* ASScrollDirection.m in Sources */, B35062091B010EFD0018CF92 /* ASScrollNode.m in Sources */, + E55FFC921DCCA5A600060AC4 /* ASEventLog.mm in Sources */, 9C8221981BA237B80037F19A /* ASStackBaselinePositionedLayout.mm in Sources */, 8BDA5FC81CDBDF95007D13B2 /* ASVideoPlayerNode.mm in Sources */, 34EFC7721B701D0300AD841F /* ASStackLayoutSpec.mm in Sources */, diff --git a/AsyncDisplayKit/ASCollectionNode.mm b/AsyncDisplayKit/ASCollectionNode.mm index 5d0dfe66cd..5ecac71efc 100644 --- a/AsyncDisplayKit/ASCollectionNode.mm +++ b/AsyncDisplayKit/ASCollectionNode.mm @@ -13,6 +13,7 @@ #import "ASCollectionInternal.h" #import "ASCollectionViewLayoutFacilitatorProtocol.h" #import "ASCollectionNode.h" +#import "ASDisplayNodeInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASEnvironmentInternal.h" #import "ASInternalHelpers.h" @@ -121,7 +122,7 @@ - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator { ASDisplayNodeViewBlock collectionViewBlock = ^UIView *{ - return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator]; + return [[ASCollectionView alloc] _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:layoutFacilitator eventLog:ASDisplayNodeGetEventLog(self)]; }; if (self = [super initWithViewBlock:collectionViewBlock]) { @@ -561,4 +562,14 @@ ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) +#pragma mark - Debugging (Private) + +- (NSMutableArray *)propertiesForDebugDescription +{ + NSMutableArray *result = [super propertiesForDebugDescription]; + [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }]; + [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }]; + return result; +} + @end diff --git a/AsyncDisplayKit/ASCollectionView.mm b/AsyncDisplayKit/ASCollectionView.mm index 5835ed5a43..c59a492ad5 100644 --- a/AsyncDisplayKit/ASCollectionView.mm +++ b/AsyncDisplayKit/ASCollectionView.mm @@ -244,10 +244,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; - (instancetype)initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout { - return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil]; + return [self _initWithFrame:frame collectionViewLayout:layout layoutFacilitator:nil eventLog:nil]; } -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id)layoutFacilitator eventLog:(ASEventLog *)eventLog { if (!(self = [super initWithFrame:frame collectionViewLayout:layout])) return nil; @@ -259,7 +259,7 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell"; _rangeController.delegate = self; _rangeController.layoutController = _layoutController; - _dataController = [[ASCollectionDataController alloc] initWithDataSource:self]; + _dataController = [[ASCollectionDataController alloc] initWithDataSource:self eventLog:eventLog]; _dataController.delegate = _rangeController; _dataController.environmentDelegate = self; diff --git a/AsyncDisplayKit/ASDisplayNode+Beta.h b/AsyncDisplayKit/ASDisplayNode+Beta.h index e68d8827ee..0cdb997903 100644 --- a/AsyncDisplayKit/ASDisplayNode+Beta.h +++ b/AsyncDisplayKit/ASDisplayNode+Beta.h @@ -10,7 +10,7 @@ #import "ASDisplayNode.h" #import "ASLayoutRangeType.h" -#import "ASTraceEvent.h" +#import "ASEventLog.h" NS_ASSUME_NONNULL_BEGIN @@ -19,20 +19,18 @@ 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__] +#if ASEVENTLOG_ENABLE +#define ASDisplayNodeLogEvent(node, ...) [node.eventLog logEventWithBacktrace:[NSThread callStackSymbols] format:__VA_ARGS__] #else #define ASDisplayNodeLogEvent(node, ...) #endif +#if ASEVENTLOG_ENABLE +#define ASDisplayNodeGetEventLog(node) node.eventLog +#else +#define ASDisplayNodeGetEventLog(node) nil +#endif + /** * Bitmask to indicate what performance measurements the cell should record. */ @@ -94,6 +92,13 @@ typedef struct { */ @property (nonatomic, assign, readonly) ASDisplayNodePerformanceMeasurements performanceMeasurements; +#if ASEVENTLOG_ENABLE +/* + * @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDisplayNodeLogEvent macro instead. + */ +@property (nonatomic, strong, readonly) ASEventLog *eventLog; +#endif + /** * @abstract Currently used by ASNetworkImageNode and ASMultiplexImageNode to allow their placeholders to stay if they are loading an image from the network. * Otherwise, a display pass is scheduled and completes, but does not actually draw anything - and ASDisplayNode considers the element finished. @@ -118,20 +123,6 @@ typedef struct { */ + (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 *)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 diff --git a/AsyncDisplayKit/ASDisplayNode.mm b/AsyncDisplayKit/ASDisplayNode.mm index e528cff70d..725c3a4857 100644 --- a/AsyncDisplayKit/ASDisplayNode.mm +++ b/AsyncDisplayKit/ASDisplayNode.mm @@ -300,7 +300,11 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) - (void)_initializeInstance { [self _staticInitialize]; - _eventLogHead = -1; + +#if ASEVENTLOG_ENABLE + _eventLog = [[ASEventLog alloc] init]; +#endif + _contentsScaleForDisplay = ASScreenScale(); _environmentState = ASEnvironmentStateMakeDefault(); @@ -575,51 +579,6 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c) } } -#if ASDISPLAYNODE_EVENTLOG_ENABLE -- (void)_logEventWithBacktrace:(NSArray *)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 *)eventLog -{ - ASDN::MutexLocker l(__instanceLock__); - NSUInteger tail = (_eventLogHead + 1); - NSUInteger count = _eventLog.count; - - NSMutableArray *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"); @@ -3344,6 +3303,13 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority"; #pragma mark Debugging (Private) +#if ASEVENTLOG_ENABLE +- (ASEventLog *)eventLog +{ + return _eventLog; +} +#endif + - (NSMutableArray *)propertiesForDescription { NSMutableArray *result = [NSMutableArray array]; diff --git a/AsyncDisplayKit/ASTableNode.mm b/AsyncDisplayKit/ASTableNode.mm index 64696edd96..3ffe388e1b 100644 --- a/AsyncDisplayKit/ASTableNode.mm +++ b/AsyncDisplayKit/ASTableNode.mm @@ -13,6 +13,7 @@ #import "ASTableNode.h" #import "ASTableViewInternal.h" #import "ASEnvironmentInternal.h" +#import "ASDisplayNodeInternal.h" #import "ASDisplayNode+Subclasses.h" #import "ASInternalHelpers.h" #import "ASCellNode+Internal.h" @@ -79,7 +80,7 @@ - (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass { ASDisplayNodeViewBlock tableViewBlock = ^UIView *{ - return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass]; + return [[ASTableView alloc] _initWithFrame:frame style:style dataControllerClass:dataControllerClass eventLog:ASDisplayNodeGetEventLog(self)]; }; if (self = [super initWithViewBlock:tableViewBlock]) { @@ -574,4 +575,14 @@ ASEnvironmentCollectionTableSetEnvironmentState(_environmentStateLock) [self.view waitUntilAllUpdatesAreCommitted]; } +#pragma mark - Debugging (Private) + +- (NSMutableArray *)propertiesForDebugDescription +{ + NSMutableArray *result = [super propertiesForDebugDescription]; + [result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }]; + [result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }]; + return result; +} + @end diff --git a/AsyncDisplayKit/ASTableView.mm b/AsyncDisplayKit/ASTableView.mm index 04d335c1c6..a3b75fb5b4 100644 --- a/AsyncDisplayKit/ASTableView.mm +++ b/AsyncDisplayKit/ASTableView.mm @@ -212,7 +212,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; #pragma mark - #pragma mark Lifecycle -- (void)configureWithDataControllerClass:(Class)dataControllerClass +- (void)configureWithDataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog { _layoutController = [[ASFlowLayoutController alloc] initWithScrollOption:ASFlowLayoutDirectionVertical]; @@ -221,7 +221,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; _rangeController.dataSource = self; _rangeController.delegate = self; - _dataController = [[dataControllerClass alloc] initWithDataSource:self]; + _dataController = [[dataControllerClass alloc] initWithDataSource:self eventLog:eventLog]; _dataController.delegate = _rangeController; _dataController.environmentDelegate = self; @@ -248,10 +248,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; - (instancetype)initWithFrame:(CGRect)frame style:(UITableViewStyle)style { - return [self _initWithFrame:frame style:style dataControllerClass:nil]; + return [self _initWithFrame:frame style:style dataControllerClass:nil eventLog:nil]; } -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog { if (!(self = [super initWithFrame:frame style:style])) { return nil; @@ -261,7 +261,7 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell"; dataControllerClass = [[self class] dataControllerClass]; } - [self configureWithDataControllerClass:dataControllerClass]; + [self configureWithDataControllerClass:dataControllerClass eventLog:eventLog]; if (!AS_AT_LEAST_IOS9) { _retainedLayer = self.layer; diff --git a/AsyncDisplayKit/ASTableViewInternal.h b/AsyncDisplayKit/ASTableViewInternal.h index bc3a6145d6..2dc5533090 100644 --- a/AsyncDisplayKit/ASTableViewInternal.h +++ b/AsyncDisplayKit/ASTableViewInternal.h @@ -31,8 +31,10 @@ * @param style A constant that specifies the style of the table view. See UITableViewStyle for descriptions of valid constants. * * @param dataControllerClass A controller class injected to and used to create a data controller for the table view. + * + * @param eventLog An event log passed through to the data controller. */ -- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass; +- (instancetype)_initWithFrame:(CGRect)frame style:(UITableViewStyle)style dataControllerClass:(Class)dataControllerClass eventLog:(ASEventLog *)eventLog; /// Set YES and we'll log every time we call [super insertRows…] etc @property (nonatomic) BOOL test_enableSuperUpdateCallLogging; diff --git a/AsyncDisplayKit/Details/ASChangeSetDataController.mm b/AsyncDisplayKit/Details/ASChangeSetDataController.mm index 5dd0d57bab..10ae0f28d0 100644 --- a/AsyncDisplayKit/Details/ASChangeSetDataController.mm +++ b/AsyncDisplayKit/Details/ASChangeSetDataController.mm @@ -47,7 +47,7 @@ [_changeSet addCompletionHandler:completion]; if (_changeSetBatchUpdateCounter == 0) { - void (^batchCompletion)(BOOL finished) = _changeSet.completionHandler; + void (^batchCompletion)(BOOL) = _changeSet.completionHandler; /** * If the initial reloadData has not been called, just bail because we don't have @@ -66,6 +66,8 @@ [self invalidateDataSourceItemCounts]; [_changeSet markCompletedWithNewItemCounts:[self itemCountsFromDataSource]]; + ASDataControllerLogEvent(self, @"triggeredUpdate: %@", _changeSet); + [super beginUpdates]; for (_ASHierarchyItemChange *change in [_changeSet itemChangesOfType:_ASHierarchyChangeTypeDelete]) { @@ -84,6 +86,16 @@ [super insertRowsAtIndexPaths:change.indexPaths withAnimationOptions:change.animationOptions]; } +#if ASEVENTLOG_ENABLE + NSString *changeSetDescription = ASObjectDescriptionMakeTiny(_changeSet); + batchCompletion = ^(BOOL finished) { + if (batchCompletion != nil) { + batchCompletion(finished); + } + ASDataControllerLogEvent(self, @"finishedUpdate: %@", changeSetDescription); + }; +#endif + [super endUpdatesAnimated:animated completion:batchCompletion]; _changeSet = nil; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.h b/AsyncDisplayKit/Details/ASCollectionDataController.h index 5fdebab104..74c08ec413 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.h +++ b/AsyncDisplayKit/Details/ASCollectionDataController.h @@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ASCollectionDataController : ASChangeSetDataController -- (instancetype)initWithDataSource:(id)dataSource NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; - (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath; diff --git a/AsyncDisplayKit/Details/ASCollectionDataController.mm b/AsyncDisplayKit/Details/ASCollectionDataController.mm index 444d8fc7aa..bdbdd9801f 100644 --- a/AsyncDisplayKit/Details/ASCollectionDataController.mm +++ b/AsyncDisplayKit/Details/ASCollectionDataController.mm @@ -36,9 +36,9 @@ NSMutableDictionary *> *_pendingNodeContexts; } -- (instancetype)initWithDataSource:(id)dataSource +- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog { - self = [super initWithDataSource:dataSource]; + self = [super initWithDataSource:dataSource eventLog:eventLog]; if (self != nil) { _pendingNodeContexts = [NSMutableDictionary dictionary]; _dataSourceImplementsSupplementaryNodeBlockOfKindAtIndexPath = [dataSource respondsToSelector:@selector(dataController:supplementaryNodeBlockOfKind:atIndexPath:)]; diff --git a/AsyncDisplayKit/Details/ASCollectionInternal.h b/AsyncDisplayKit/Details/ASCollectionInternal.h index d471ebe5d8..814f51d3b9 100644 --- a/AsyncDisplayKit/Details/ASCollectionInternal.h +++ b/AsyncDisplayKit/Details/ASCollectionInternal.h @@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @class ASRangeController; @interface ASCollectionView () -- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator; +- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id)layoutFacilitator eventLog:(ASEventLog*)eventLog; @property (nonatomic, weak, readwrite) ASCollectionNode *collectionNode; @property (nonatomic, strong, readonly) ASDataController *dataController; diff --git a/AsyncDisplayKit/Details/ASDataController.h b/AsyncDisplayKit/Details/ASDataController.h index 8c2c1606f0..5cd7134cdf 100644 --- a/AsyncDisplayKit/Details/ASDataController.h +++ b/AsyncDisplayKit/Details/ASDataController.h @@ -14,9 +14,16 @@ #import #import #import +#import NS_ASSUME_NONNULL_BEGIN +#if ASEVENTLOG_ENABLE +#define ASDataControllerLogEvent(dataController, ...) [dataController.eventLog logEventWithBacktrace:[NSThread callStackSymbols] format:__VA_ARGS__] +#else +#define ASDataControllerLogEvent(dataController, ...) +#endif + @class ASCellNode; @class ASDataController; @protocol ASEnvironment; @@ -109,7 +116,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; @protocol ASFlowLayoutControllerDataSource; @interface ASDataController : ASDealloc2MainObject -- (instancetype)initWithDataSource:(id)dataSource NS_DESIGNATED_INITIALIZER; +- (instancetype)initWithDataSource:(id)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER; /** Data source for fetching data info. @@ -135,6 +142,13 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind; */ @property (nonatomic, readonly) BOOL initialReloadDataHasBeenCalled; +#if ASEVENTLOG_ENABLE +/* + * @abstract The primitive event tracing object. You shouldn't directly use it to log event. Use the ASDataControllerLogEvent macro instead. + */ +@property (nonatomic, strong, readonly) ASEventLog *eventLog; +#endif + /** @name Data Updating */ - (void)beginUpdates; diff --git a/AsyncDisplayKit/Details/ASDataController.mm b/AsyncDisplayKit/Details/ASDataController.mm index 75be3cb8b9..711ab2d7bd 100644 --- a/AsyncDisplayKit/Details/ASDataController.mm +++ b/AsyncDisplayKit/Details/ASDataController.mm @@ -71,7 +71,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; #pragma mark - Lifecycle -- (instancetype)initWithDataSource:(id)dataSource +- (instancetype)initWithDataSource:(id)dataSource eventLog:(ASEventLog *)eventLog { if (!(self = [super init])) { return nil; @@ -80,6 +80,10 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; _dataSource = dataSource; +#if ASEVENTLOG_ENABLE + _eventLog = eventLog; +#endif + _nodeContexts = [NSMutableDictionary dictionary]; _completedNodes = [NSMutableDictionary dictionary]; _editingNodes = [NSMutableDictionary dictionary]; @@ -102,7 +106,8 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind"; { ASDisplayNodeFailAssert(@"Failed to call designated initializer."); id fakeDataSource = nil; - return [self initWithDataSource:fakeDataSource]; + ASEventLog *eventLog = nil; + return [self initWithDataSource:fakeDataSource eventLog:eventLog]; } - (void)setDelegate:(id)delegate diff --git a/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.h b/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.h index 5c3899ef80..c969a2682f 100644 --- a/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.h +++ b/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.h @@ -1,5 +1,5 @@ // -// ASObjectDescriptions.h +// ASObjectDescriptionHelpers.h // AsyncDisplayKit // // Created by Adlai Holler on 9/7/16. @@ -26,7 +26,7 @@ NS_ASSUME_NONNULL_BEGIN /** * Your base class should conform to this and override `-description` * to call `[self propertiesForDescription]` and use `ASObjectDescriptionMake` - * to return a string. Subclasses of this base class just need to override + * to return a string. Subclasses of this base class just need to override * `propertiesForDescription`, call super, and modify the result as needed. */ @protocol ASDescriptionProvider @@ -36,6 +36,8 @@ NS_ASSUME_NONNULL_BEGIN ASDISPLAYNODE_EXTERN_C_BEGIN +NSString *ASGetDescriptionValueString(id object); + /// Useful for structs etc. Returns e.g. { position = (0 0); frame = (0 0; 50 50) } NSString *ASObjectDescriptionMakeWithoutObject(NSArray * _Nullable propertyGroups); diff --git a/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.m b/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.m index baaed3137d..cfdc7324fe 100644 --- a/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.m +++ b/AsyncDisplayKit/Details/ASObjectDescriptionHelpers.m @@ -1,5 +1,5 @@ // -// ASObjectDescriptions.m +// ASObjectDescriptionHelpers.m // AsyncDisplayKit // // Created by Adlai Holler on 9/7/16. diff --git a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h index 630c36c145..8aa400cb54 100644 --- a/AsyncDisplayKit/Private/ASDisplayNodeInternal.h +++ b/AsyncDisplayKit/Private/ASDisplayNodeInternal.h @@ -122,10 +122,11 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo UIEdgeInsets _hitTestSlop; NSMutableArray *_subnodes; - NSMutableArray *_eventLog; - // The index of the most recent log entry. -1 until first entry. - NSInteger _eventLogHead; - + +#if ASEVENTLOG_ENABLE + ASEventLog *_eventLog; +#endif + // Main thread only BOOL _automaticallyManagesSubnodes; _ASTransitionContext *_pendingLayoutTransitionContext; diff --git a/AsyncDisplayKit/Private/ASEventLog.h b/AsyncDisplayKit/Private/ASEventLog.h new file mode 100644 index 0000000000..380a6d51c4 --- /dev/null +++ b/AsyncDisplayKit/Private/ASEventLog.h @@ -0,0 +1,25 @@ +// +// ASEventLog.h +// AsyncDisplayKit +// +// Created by Huy Nguyen on 4/11/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. +// + +#ifndef ASEVENTLOG_CAPACITY +#define ASEVENTLOG_CAPACITY 20 +#endif + +#ifndef ASEVENTLOG_ENABLE +#define ASEVENTLOG_ENABLE 1 +#endif + +@interface ASEventLog : NSObject + +- (void)logEventWithBacktrace:(NSArray *)backtrace format:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3); + +@end diff --git a/AsyncDisplayKit/Private/ASEventLog.mm b/AsyncDisplayKit/Private/ASEventLog.mm new file mode 100644 index 0000000000..b94ea1b494 --- /dev/null +++ b/AsyncDisplayKit/Private/ASEventLog.mm @@ -0,0 +1,80 @@ +// +// ASEventLog.m +// AsyncDisplayKit +// +// Created by Huy Nguyen on 4/11/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 "ASEventLog.h" +#import "ASThread.h" +#import "ASTraceEvent.h" + +@interface ASEventLog () +{ + ASDN::RecursiveMutex __instanceLock__; + // The index of the most recent log entry. -1 until first entry. + NSInteger _eventLogHead; + // The most recent trace events. Max count is ASEVENTLOG_CAPACITY. + NSMutableArray *_eventLog; +} +@end + +@implementation ASEventLog + +- (instancetype)init +{ + if ((self = [super init])) { + _eventLogHead = -1; + } + return self; +} + +- (void)logEventWithBacktrace:(NSArray *)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:ASEVENTLOG_CAPACITY]; + } + + // Increment the head index. + _eventLogHead = (_eventLogHead + 1) % ASEVENTLOG_CAPACITY; + if (_eventLogHead < _eventLog.count) { + [_eventLog replaceObjectAtIndex:_eventLogHead withObject:event]; + } else { + [_eventLog insertObject:event atIndex:_eventLogHead]; + } +} + +- (NSArray *)eventLog +{ + ASDN::MutexLocker l(__instanceLock__); + NSUInteger tail = (_eventLogHead + 1); + NSUInteger count = _eventLog.count; + + NSMutableArray *result = [NSMutableArray array]; + + // Start from `tail` and go through array, wrapping around when we exceed end index. + for (NSUInteger actualIndex = 0; actualIndex < ASEVENTLOG_CAPACITY; actualIndex++) { + NSInteger ringIndex = (tail + actualIndex) % ASEVENTLOG_CAPACITY; + if (ringIndex < count) { + [result addObject:_eventLog[ringIndex]]; + } + } + return result; +} + +@end diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h index 00a7b7d48d..f2994c2ab2 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.h @@ -12,6 +12,7 @@ #import #import +#import "ASObjectDescriptionHelpers.h" NS_ASSUME_NONNULL_BEGIN @@ -58,7 +59,7 @@ BOOL ASHierarchyChangeTypeIsFinal(_ASHierarchyChangeType changeType); NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); -@interface _ASHierarchySectionChange : NSObject +@interface _ASHierarchySectionChange : NSObject // FIXME: Generalize this to `changeMetadata` dict? @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; @@ -73,7 +74,7 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); - (_ASHierarchySectionChange *)changeByFinalizingType; @end -@interface _ASHierarchyItemChange : NSObject +@interface _ASHierarchyItemChange : NSObject @property (nonatomic, readonly) ASDataControllerAnimationOptions animationOptions; /// Index paths are sorted descending for changeType .Delete, ascending otherwise @@ -81,7 +82,7 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); @property (nonatomic, readonly) _ASHierarchyChangeType changeType; -+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofType:(_ASHierarchyChangeType)changeType; ++ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes; /** * If this is a .OriginalInsert or .OriginalDelete change, this returns a copied change @@ -90,7 +91,7 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType); - (_ASHierarchyItemChange *)changeByFinalizingType; @end -@interface _ASHierarchyChangeSet : NSObject +@interface _ASHierarchyChangeSet : NSObject - (instancetype)initWithOldData:(std::vector)oldItemCounts NS_DESIGNATED_INITIALIZER; diff --git a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm index 6f8739e71b..16351fd1a9 100644 --- a/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm +++ b/AsyncDisplayKit/Private/_ASHierarchyChangeSet.mm @@ -15,7 +15,7 @@ #import "NSIndexSet+ASHelpers.h" #import "ASAssert.h" #import "ASDisplayNode+Beta.h" - +#import "ASObjectDescriptionHelpers.h" #import // NOTE: We log before throwing so they don't have to let it bubble up to see the error. @@ -66,6 +66,8 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) /// Returns all the indexes from all the `indexSet`s of the given `_ASHierarchySectionChange` objects. + (NSMutableIndexSet *)allIndexesInSectionChanges:(NSArray *)changes; + ++ (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes; @end @interface _ASHierarchyItemChange () @@ -76,9 +78,13 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) Assumes: `changes` all have the same changeType */ + (void)sortAndCoalesceItemChanges:(NSMutableArray<_ASHierarchyItemChange *> *)changes ignoringChangesInSections:(NSIndexSet *)sections; + ++ (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes; + ++ (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType; @end -@interface _ASHierarchyChangeSet () +@interface _ASHierarchyChangeSet () @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *insertItemChanges; @property (nonatomic, strong, readonly) NSMutableArray<_ASHierarchyItemChange *> *originalInsertItemChanges; @@ -328,8 +334,11 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) [_insertItemChanges addObject:[originalInsertItemChange changeByFinalizingType]]; } - NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges ofType:_ASHierarchyChangeTypeInsert]; - NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges ofType:_ASHierarchyChangeTypeDelete]; + [_ASHierarchyItemChange ensureItemChanges:_insertItemChanges ofSameType:_ASHierarchyChangeTypeInsert]; + NSDictionary *insertedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_insertItemChanges]; + + [_ASHierarchyItemChange ensureItemChanges:_deleteItemChanges ofSameType:_ASHierarchyChangeTypeDelete]; + NSDictionary *deletedIndexPathsMap = [_ASHierarchyItemChange sectionToIndexSetMapFromChanges:_deleteItemChanges]; for (_ASHierarchyItemChange *change in _reloadItemChanges) { NSAssert(change.changeType == _ASHierarchyChangeTypeReload, @"It must be a reload change to be in here"); @@ -484,11 +493,46 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) } } +#pragma mark - Debugging (Private) + - (NSString *)description { - return [NSString stringWithFormat:@"<%@ %p: deletedSections=%@, insertedSections=%@, deletedItems=%@, insertedItems=%@>", NSStringFromClass(self.class), self, _deletedSections, _insertedSections, _deleteItemChanges, _insertItemChanges]; + return ASObjectDescriptionMake(self, [self propertiesForDescription]); } +- (NSString *)debugDescription +{ + return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); +} + +- (NSMutableArray *)propertiesForDescription +{ + NSMutableArray *result = [NSMutableArray array]; + if (_reloadSectionChanges.count > 0) { + [result addObject:@{ @"reloadSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_reloadSectionChanges] }]; + } + if (_reloadItemChanges.count > 0) { + [result addObject:@{ @"reloadItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_reloadItemChanges] }]; + } + if (_originalDeleteSectionChanges.count > 0) { + [result addObject:@{ @"deleteSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalDeleteSectionChanges] }]; + } + if (_originalDeleteItemChanges.count > 0) { + [result addObject:@{ @"deleteItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalDeleteItemChanges] }]; + } + if (_originalInsertSectionChanges.count > 0) { + [result addObject:@{ @"insertSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_originalInsertSectionChanges] }]; + } + if (_originalInsertItemChanges.count > 0) { + [result addObject:@{ @"insertItems" : [_ASHierarchyItemChange smallDescriptionForItemChanges:_originalInsertItemChanges] }]; + } + return result; +} + +- (NSMutableArray *)propertiesForDebugDescription +{ + return [self propertiesForDescription]; +} @end @@ -600,9 +644,46 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) return indexes; } +#pragma mark - Debugging (Private) + ++ (NSString *)smallDescriptionForSectionChanges:(NSArray<_ASHierarchySectionChange *> *)changes +{ + NSMutableIndexSet *unionIndexSet = [NSMutableIndexSet indexSet]; + for (_ASHierarchySectionChange *change in changes) { + [unionIndexSet addIndexes:change.indexSet]; + } + return [unionIndexSet as_smallDescription]; +} + - (NSString *)description { - return [NSString stringWithFormat:@"<%@: anim=%lu, type=%@, indexes=%@>", NSStringFromClass(self.class), (unsigned long)_animationOptions, NSStringFromASHierarchyChangeType(_changeType), [self.indexSet as_smallDescription]]; + return ASObjectDescriptionMake(self, [self propertiesForDescription]); +} + +- (NSString *)debugDescription +{ + return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); +} + +- (NSString *)smallDescription +{ + return [self.indexSet as_smallDescription]; +} + +- (NSMutableArray *)propertiesForDescription +{ + NSMutableArray *result = [NSMutableArray array]; + [result addObject:@{ @"indexes" : [self.indexSet as_smallDescription] }]; + return result; +} + +- (NSMutableArray *)propertiesForDebugDescription +{ + NSMutableArray *result = [NSMutableArray array]; + [result addObject:@{ @"anim" : @(_animationOptions) }]; + [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }]; + [result addObject:@{ @"indexes" : self.indexSet }]; + return result; } @end @@ -629,11 +710,10 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) // Create a mapping out of changes indexPaths to a {@section : [indexSet]} fashion // e.g. changes: (0 - 0), (0 - 1), (2 - 5) // will become: {@0 : [0, 1], @2 : [5]} -+ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray *)changes ofType:(_ASHierarchyChangeType)changeType ++ (NSDictionary *)sectionToIndexSetMapFromChanges:(NSArray<_ASHierarchyItemChange *> *)changes { NSMutableDictionary *sectionToIndexSetMap = [NSMutableDictionary dictionary]; for (_ASHierarchyItemChange *change in changes) { - NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); for (NSIndexPath *indexPath in change.indexPaths) { NSNumber *sectionKey = @(indexPath.section); NSMutableIndexSet *indexSet = sectionToIndexSetMap[sectionKey]; @@ -648,6 +728,13 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) return sectionToIndexSetMap; } ++ (void)ensureItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes ofSameType:(_ASHierarchyChangeType)changeType +{ + for (_ASHierarchyItemChange *change in changes) { + NSAssert(change.changeType == changeType, @"The map we created must all be of the same changeType as of now"); + } +} + - (_ASHierarchyItemChange *)changeByFinalizingType { _ASHierarchyChangeType newType; @@ -725,9 +812,43 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType) [changes setArray:result]; } +#pragma mark - Debugging (Private) + ++ (NSString *)smallDescriptionForItemChanges:(NSArray<_ASHierarchyItemChange *> *)changes +{ + NSDictionary *map = [self sectionToIndexSetMapFromChanges:changes]; + NSMutableString *str = [NSMutableString stringWithString:@"{ "]; + [map enumerateKeysAndObjectsUsingBlock:^(NSNumber * _Nonnull section, NSIndexSet * _Nonnull indexSet, BOOL * _Nonnull stop) { + [str appendFormat:@"@%lu : %@ ", section.integerValue, [indexSet as_smallDescription]]; + }]; + [str appendString:@"}"]; + return str; +} + - (NSString *)description { - return [NSString stringWithFormat:@"<%@: anim=%lu, type=%@, indexPaths=%@>", NSStringFromClass(self.class), (unsigned long)_animationOptions, NSStringFromASHierarchyChangeType(_changeType), self.indexPaths]; + return ASObjectDescriptionMake(self, [self propertiesForDescription]); +} + +- (NSString *)debugDescription +{ + return ASObjectDescriptionMake(self, [self propertiesForDebugDescription]); +} + +- (NSMutableArray *)propertiesForDescription +{ + NSMutableArray *result = [NSMutableArray array]; + [result addObject:@{ @"indexPaths" : self.indexPaths }]; + return result; +} + +- (NSMutableArray *)propertiesForDebugDescription +{ + NSMutableArray *result = [NSMutableArray array]; + [result addObject:@{ @"anim" : @(_animationOptions) }]; + [result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }]; + [result addObject:@{ @"indexPaths" : self.indexPaths }]; + return result; } @end diff --git a/AsyncDisplayKitTests/ASTableViewTests.m b/AsyncDisplayKitTests/ASTableViewTests.m index f0c50bda3a..510306547b 100644 --- a/AsyncDisplayKitTests/ASTableViewTests.m +++ b/AsyncDisplayKitTests/ASTableViewTests.m @@ -53,7 +53,8 @@ - (instancetype)__initWithFrame:(CGRect)frame style:(UITableViewStyle)style { - return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class]]; + + return [super _initWithFrame:frame style:style dataControllerClass:[ASTestDataController class] eventLog:[[ASEventLog alloc] init]]; } - (ASTestDataController *)testDataController