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.
This commit is contained in:
Huy Nguyen
2016-11-09 00:44:49 +00:00
committed by Adlai Holler
parent 06f5754b37
commit fb6d1830a0
22 changed files with 366 additions and 111 deletions

View File

@@ -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<ASCollectionViewLayoutFacilitatorProtocol>)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<NSDictionary *> *)propertiesForDebugDescription
{
NSMutableArray<NSDictionary *> *result = [super propertiesForDebugDescription];
[result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }];
[result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }];
return result;
}
@end

View File

@@ -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<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(id<ASCollectionViewLayoutFacilitatorProtocol>)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;

View File

@@ -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<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

@@ -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<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");
@@ -3344,6 +3303,13 @@ static const char *ASDisplayNodeDrawingPriorityKey = "ASDrawingPriority";
#pragma mark Debugging (Private)
#if ASEVENTLOG_ENABLE
- (ASEventLog *)eventLog
{
return _eventLog;
}
#endif
- (NSMutableArray<NSDictionary *> *)propertiesForDescription
{
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];

View File

@@ -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<NSDictionary *> *)propertiesForDebugDescription
{
NSMutableArray<NSDictionary *> *result = [super propertiesForDebugDescription];
[result addObject:@{ @"dataSource" : ASObjectDescriptionMakeTiny(self.dataSource) }];
[result addObject:@{ @"delegate" : ASObjectDescriptionMakeTiny(self.delegate) }];
return result;
}
@end

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;

View File

@@ -43,7 +43,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface ASCollectionDataController : ASChangeSetDataController
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;
- (ASCellNode *)supplementaryNodeOfKind:(NSString *)kind atIndexPath:(NSIndexPath *)indexPath;

View File

@@ -36,9 +36,9 @@
NSMutableDictionary<NSString *, NSMutableArray<ASIndexedNodeContext *> *> *_pendingNodeContexts;
}
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)dataSource
- (instancetype)initWithDataSource:(id<ASCollectionDataControllerSource>)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:)];

View File

@@ -20,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN
@class ASRangeController;
@interface ASCollectionView ()
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator;
- (instancetype)_initWithFrame:(CGRect)frame collectionViewLayout:(UICollectionViewLayout *)layout layoutFacilitator:(nullable id<ASCollectionViewLayoutFacilitatorProtocol>)layoutFacilitator eventLog:(ASEventLog*)eventLog;
@property (nonatomic, weak, readwrite) ASCollectionNode *collectionNode;
@property (nonatomic, strong, readonly) ASDataController *dataController;

View File

@@ -14,9 +14,16 @@
#import <AsyncDisplayKit/ASDealloc2MainObject.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASFlowLayoutController.h>
#import <AsyncDisplayKit/ASEventLog.h>
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 <ASFlowLayoutControllerDataSource>
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource NS_DESIGNATED_INITIALIZER;
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)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;

View File

@@ -71,7 +71,7 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
#pragma mark - Lifecycle
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)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<ASDataControllerSource> fakeDataSource = nil;
return [self initWithDataSource:fakeDataSource];
ASEventLog *eventLog = nil;
return [self initWithDataSource:fakeDataSource eventLog:eventLog];
}
- (void)setDelegate:(id<ASDataControllerDelegate>)delegate

View File

@@ -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<NSDictionary *> * _Nullable propertyGroups);

View File

@@ -1,5 +1,5 @@
//
// ASObjectDescriptions.m
// ASObjectDescriptionHelpers.m
// AsyncDisplayKit
//
// Created by Adlai Holler on 9/7/16.

View File

@@ -122,10 +122,11 @@ 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;
#if ASEVENTLOG_ENABLE
ASEventLog *_eventLog;
#endif
// Main thread only
BOOL _automaticallyManagesSubnodes;
_ASTransitionContext *_pendingLayoutTransitionContext;

View File

@@ -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<NSString *> *)backtrace format:(NSString *)format, ... NS_FORMAT_FUNCTION(2, 3);
@end

View File

@@ -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<ASTraceEvent *> *_eventLog;
}
@end
@implementation ASEventLog
- (instancetype)init
{
if ((self = [super init])) {
_eventLogHead = -1;
}
return self;
}
- (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: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<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 < ASEVENTLOG_CAPACITY; actualIndex++) {
NSInteger ringIndex = (tail + actualIndex) % ASEVENTLOG_CAPACITY;
if (ringIndex < count) {
[result addObject:_eventLog[ringIndex]];
}
}
return result;
}
@end

View File

@@ -12,6 +12,7 @@
#import <Foundation/Foundation.h>
#import <vector>
#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 <ASDescriptionProvider, ASDebugDescriptionProvider>
// 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 <ASDescriptionProvider, ASDebugDescriptionProvider>
@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 <ASDescriptionProvider, ASDebugDescriptionProvider>
- (instancetype)initWithOldData:(std::vector<NSInteger>)oldItemCounts NS_DESIGNATED_INITIALIZER;

View File

@@ -15,7 +15,7 @@
#import "NSIndexSet+ASHelpers.h"
#import "ASAssert.h"
#import "ASDisplayNode+Beta.h"
#import "ASObjectDescriptionHelpers.h"
#import <unordered_map>
// 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<NSDictionary *> *)propertiesForDescription
{
NSMutableArray<NSDictionary *> *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<NSDictionary *> *)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<NSDictionary *> *)propertiesForDescription
{
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
[result addObject:@{ @"indexes" : [self.indexSet as_smallDescription] }];
return result;
}
- (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
{
NSMutableArray<NSDictionary *> *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<NSDictionary *> *)propertiesForDescription
{
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
[result addObject:@{ @"indexPaths" : self.indexPaths }];
return result;
}
- (NSMutableArray<NSDictionary *> *)propertiesForDebugDescription
{
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
[result addObject:@{ @"anim" : @(_animationOptions) }];
[result addObject:@{ @"type" : NSStringFromASHierarchyChangeType(_changeType) }];
[result addObject:@{ @"indexPaths" : self.indexPaths }];
return result;
}
@end