mirror of
https://github.com/TextureGroup/Texture.git
synced 2026-04-07 19:17:39 +00:00
[6.29] Better logging
This commit is contained in:
@@ -372,6 +372,8 @@
|
||||
CCA282D11E9EBF6C0037E8B7 /* ASTipsWindow.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA282CF1E9EBF6C0037E8B7 /* ASTipsWindow.m */; };
|
||||
CCA5F62C1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA5F62B1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.m */; };
|
||||
CCA5F62E1EECC2A80060C137 /* ASAssert.m in Sources */ = {isa = PBXBuildFile; fileRef = CCA5F62D1EECC2A80060C137 /* ASAssert.m */; };
|
||||
CCB1F95A1EFB60A5009C7475 /* ASLog.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB1F9591EFB60A5009C7475 /* ASLog.m */; };
|
||||
CCB1F95C1EFB6350009C7475 /* ASSignpost.h in Headers */ = {isa = PBXBuildFile; fileRef = CCB1F95B1EFB6316009C7475 /* ASSignpost.h */; };
|
||||
CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; };
|
||||
CCB338E41EEE11160081F21A /* OCMockObject+ASAdditions.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB338E31EEE11160081F21A /* OCMockObject+ASAdditions.m */; };
|
||||
CCB338E71EEE27760081F21A /* ASTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB338E61EEE27760081F21A /* ASTestCase.m */; };
|
||||
@@ -835,6 +837,8 @@
|
||||
CCA5F62A1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSInvocation+ASTestHelpers.h"; sourceTree = "<group>"; };
|
||||
CCA5F62B1EEC9E9B0060C137 /* NSInvocation+ASTestHelpers.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSInvocation+ASTestHelpers.m"; sourceTree = "<group>"; };
|
||||
CCA5F62D1EECC2A80060C137 /* ASAssert.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASAssert.m; sourceTree = "<group>"; };
|
||||
CCB1F9591EFB60A5009C7475 /* ASLog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASLog.m; sourceTree = "<group>"; };
|
||||
CCB1F95B1EFB6316009C7475 /* ASSignpost.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASSignpost.h; sourceTree = "<group>"; };
|
||||
CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASDisplayNodeSnapshotTests.m; sourceTree = "<group>"; };
|
||||
CCB338E21EEE11160081F21A /* OCMockObject+ASAdditions.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "OCMockObject+ASAdditions.h"; sourceTree = "<group>"; };
|
||||
CCB338E31EEE11160081F21A /* OCMockObject+ASAdditions.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "OCMockObject+ASAdditions.m"; sourceTree = "<group>"; };
|
||||
@@ -1391,6 +1395,8 @@
|
||||
058D0A44195D058D00B7D73C /* ASBaseDefines.h */,
|
||||
1950C4481A3BB5C1005C8279 /* ASEqualityHelpers.h */,
|
||||
0516FA3B1A15563400B4EBED /* ASLog.h */,
|
||||
CCB1F9591EFB60A5009C7475 /* ASLog.m */,
|
||||
CCB1F95B1EFB6316009C7475 /* ASSignpost.h */,
|
||||
);
|
||||
path = Base;
|
||||
sourceTree = "<group>";
|
||||
@@ -1682,6 +1688,7 @@
|
||||
9019FBBF1ED8061D00C45F72 /* ASYogaUtilities.h in Headers */,
|
||||
DE84918D1C8FFF2B003D89E9 /* ASRunLoopQueue.h in Headers */,
|
||||
CC0F88621E4281E200576FED /* ASSectionController.h in Headers */,
|
||||
CCB1F95C1EFB6350009C7475 /* ASSignpost.h in Headers */,
|
||||
A2763D7A1CBDD57D00A9ADBD /* ASPINRemoteImageDownloader.h in Headers */,
|
||||
34EFC7611B701C9C00AD841F /* ASBackgroundLayoutSpec.h in Headers */,
|
||||
B35062591B010F070018CF92 /* ASBaseDefines.h in Headers */,
|
||||
@@ -2168,6 +2175,7 @@
|
||||
B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */,
|
||||
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */,
|
||||
B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */,
|
||||
CCB1F95A1EFB60A5009C7475 /* ASLog.m in Sources */,
|
||||
767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */,
|
||||
CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */,
|
||||
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
|
||||
|
||||
@@ -322,13 +322,13 @@
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"collectionNode" : ASObjectDescriptionMakeTiny(owningNode) }];
|
||||
[result addObject:@{ @"collectionNode" : owningNode }];
|
||||
} else if ([owningNode isKindOfClass:[ASTableNode class]]) {
|
||||
NSIndexPath *ip = [(ASTableNode *)owningNode indexPathForNode:self];
|
||||
if (ip != nil) {
|
||||
[result addObject:@{ @"indexPath" : ip }];
|
||||
}
|
||||
[result addObject:@{ @"tableNode" : ASObjectDescriptionMakeTiny(owningNode) }];
|
||||
[result addObject:@{ @"tableNode" : owningNode }];
|
||||
|
||||
} else if ([scrollView isKindOfClass:[ASCollectionView class]]) {
|
||||
NSIndexPath *ip = [(ASCollectionView *)scrollView indexPathForNode:self];
|
||||
|
||||
@@ -820,6 +820,8 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
if (_batchUpdateCount == 0) {
|
||||
_changeSet = [[_ASHierarchyChangeSet alloc] initWithOldData:[_dataController itemCountsFromDataSource]];
|
||||
_changeSet.rootActivity = as_activity_create("Perform async collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT);
|
||||
_changeSet.submitActivity = as_activity_create("Submit changes for collection update", _changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT);
|
||||
}
|
||||
_batchUpdateCount++;
|
||||
}
|
||||
@@ -837,6 +839,7 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
if (_batchUpdateCount == 0) {
|
||||
_ASHierarchyChangeSet *changeSet = _changeSet;
|
||||
|
||||
// Nil out _changeSet before forwarding to _dataController to allow the change set to cause subsequent batch updates on the same run loop
|
||||
_changeSet = nil;
|
||||
changeSet.animated = animated;
|
||||
@@ -848,8 +851,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
[self beginUpdates];
|
||||
if (updates) {
|
||||
updates();
|
||||
as_activity_scope(_changeSet.rootActivity);
|
||||
{
|
||||
as_activity_scope(_changeSet.submitActivity);
|
||||
if (updates) {
|
||||
updates();
|
||||
}
|
||||
}
|
||||
[self endUpdatesAnimated:animated completion:completion];
|
||||
}
|
||||
@@ -1595,10 +1602,12 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
|
||||
- (void)_beginBatchFetching
|
||||
{
|
||||
as_activity_scope(as_activity_create("Batch fetch for collection node", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
[_batchContext beginBatchFetching];
|
||||
if (_asyncDelegateFlags.collectionNodeWillBeginBatchFetch) {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
|
||||
as_log_debug(ASCollectionLog(), "Beginning batch fetch for %@ with context %@", collectionNode, _batchContext);
|
||||
[_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:_batchContext];
|
||||
});
|
||||
} else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) {
|
||||
@@ -1915,9 +1924,11 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
}
|
||||
|
||||
ASPerformBlockWithoutAnimation(!changeSet.animated, ^{
|
||||
if(changeSet.includesReloadData) {
|
||||
as_activity_scope(as_activity_create("Commit collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
if (changeSet.includesReloadData) {
|
||||
_superIsPendingDataLoad = YES;
|
||||
[super reloadData];
|
||||
as_log_debug(ASCollectionLog(), "Did reloadData %@", self.collectionNode);
|
||||
[changeSet executeCompletionHandlerWithFinished:YES];
|
||||
} else {
|
||||
[_layoutFacilitator collectionViewWillPerformBatchUpdates];
|
||||
@@ -1954,13 +1965,17 @@ static NSString * const kReuseIdentifier = @"_ASCollectionReuseIdentifier";
|
||||
numberOfUpdates++;
|
||||
}
|
||||
} completion:^(BOOL finished){
|
||||
as_activity_scope(as_activity_create("Handle collection update completion", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_verbose(ASCollectionLog(), "Update animation finished %{public}@", self.collectionNode);
|
||||
// Flush any range changes that happened as part of the update animations ending.
|
||||
[_rangeController updateIfNeeded];
|
||||
[self _scheduleCheckForBatchFetchingForNumberOfChanges:numberOfUpdates];
|
||||
[changeSet executeCompletionHandlerWithFinished:finished];
|
||||
}];
|
||||
as_log_debug(ASCollectionLog(), "Completed batch update %{public}@", self.collectionNode);
|
||||
|
||||
// Flush any range changes that happened as part of submitting the update.
|
||||
as_activity_scope(changeSet.rootActivity);
|
||||
[_rangeController updateIfNeeded];
|
||||
}
|
||||
});
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkSubclasses.h>
|
||||
|
||||
@@ -127,13 +128,8 @@ ASLayoutElementStyleExtensibilityForwarding
|
||||
|
||||
ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark -
|
||||
#pragma mark - ASLayoutElementAsciiArtProtocol
|
||||
|
||||
@implementation ASDisplayNode (ASLayoutElementAsciiArtProtocol)
|
||||
|
||||
- (NSString *)asciiArtString
|
||||
{
|
||||
return [ASLayoutSpec asciiArtStringForChildren:@[] parentName:[self asciiArtName]];
|
||||
@@ -141,11 +137,11 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
|
||||
- (NSString *)asciiArtName
|
||||
{
|
||||
NSString *string = NSStringFromClass([self class]);
|
||||
NSMutableString *result = [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding];
|
||||
if (_debugName) {
|
||||
string = [string stringByAppendingString:[NSString stringWithFormat:@"\"%@\"", _debugName]];
|
||||
[result appendFormat:@" (%@)", _debugName];
|
||||
}
|
||||
return string;
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -222,6 +218,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
*/
|
||||
- (void)_setNeedsLayoutFromAbove
|
||||
{
|
||||
as_activity_scope(as_activity_create("Set needs layout from above", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
ASDisplayNodeAssertThreadAffinity(self);
|
||||
|
||||
// Mark the node for layout in the next layout pass
|
||||
@@ -315,6 +312,8 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
}
|
||||
}
|
||||
|
||||
as_activity_scope(as_activity_create("Update node layout for current bounds", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_verbose(ASLayoutLog(), "Node %@, bounds size %@, calculatedSize %@, calculatedIsDirty %d", self, NSStringFromCGSize(boundsSizeForLayout), NSStringFromCGSize(_calculatedDisplayNodeLayout->layout.size), _calculatedDisplayNodeLayout->isDirty());
|
||||
// _calculatedDisplayNodeLayout is not reusable we need to transition to a new one
|
||||
[self cancelLayoutTransition];
|
||||
|
||||
@@ -332,7 +331,20 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
|
||||
// nextLayout was likely created by a call to layoutThatFits:, check if it is valid and can be applied.
|
||||
// If our bounds size is different than it, or invalid, recalculate. Use #define to avoid nullptr->
|
||||
if (nextLayout == nullptr || nextLayout->isDirty() == YES || layoutSizeDifferentFromBounds) {
|
||||
BOOL pendingLayoutApplicable = NO;
|
||||
if (nextLayout == nullptr) {
|
||||
as_log_verbose(ASLayoutLog(), "No pending layout.");
|
||||
} else if (nextLayout->isDirty()) {
|
||||
as_log_verbose(ASLayoutLog(), "Pending layout is invalid.");
|
||||
} else if (layoutSizeDifferentFromBounds) {
|
||||
as_log_verbose(ASLayoutLog(), "Pending layout size %@ doesn't match bounds size.", NSStringFromCGSize(nextLayout->layout.size));
|
||||
} else {
|
||||
as_log_verbose(ASLayoutLog(), "Using pending layout %@.", nextLayout->layout);
|
||||
pendingLayoutApplicable = YES;
|
||||
}
|
||||
|
||||
if (!pendingLayoutApplicable) {
|
||||
as_log_verbose(ASLayoutLog(), "Measuring with previous constrained size.");
|
||||
// Use the last known constrainedSize passed from a parent during layout (if never, use bounds).
|
||||
ASSizeRange constrainedSize = [self _locked_constrainedSizeForLayoutPass];
|
||||
ASLayout *layout = [self calculateLayoutThatFits:constrainedSize
|
||||
@@ -350,6 +362,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
// This can occur for either pre-calculated or newly-calculated layouts.
|
||||
if (nextLayout->requestedLayoutFromAbove == NO
|
||||
&& CGSizeEqualToSize(boundsSizeForLayout, nextLayout->layout.size) == NO) {
|
||||
as_log_verbose(ASLayoutLog(), "Layout size doesn't match bounds size. Requesting layout from above.");
|
||||
// The layout that we have specifies that this node (self) would like to be a different size
|
||||
// than it currently is. Because that size has been computed within the constrainedSize, we
|
||||
// expect that calling setNeedsLayoutFromAbove will result in our parent resizing us to this.
|
||||
@@ -506,10 +519,13 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
measurementCompletion:(void(^)())completion
|
||||
{
|
||||
ASDisplayNodeAssertMainThread();
|
||||
as_activity_scope(as_activity_create("Transition node layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_debug(ASLayoutLog(), "Transition layout for %@ sizeRange %@ anim %d asyncMeasure %d", self, NSStringFromASSizeRange(constrainedSize), animated, shouldMeasureAsync);
|
||||
|
||||
if (constrainedSize.max.width <= 0.0 || constrainedSize.max.height <= 0.0) {
|
||||
// Using CGSizeZero for the sizeRange can cause negative values in client layout code.
|
||||
// Most likely called transitionLayout: without providing a size, before first layout pass.
|
||||
as_log_verbose(ASLayoutLog(), "Ignoring transition due to bad size range.");
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -526,9 +542,14 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
|
||||
// Every new layout transition has a transition id associated to check in subsequent transitions for cancelling
|
||||
int32_t transitionID = [self _startNewTransition];
|
||||
as_log_verbose(ASLayoutLog(), "Transition ID is %d", transitionID);
|
||||
// NOTE: This block captures self. It's cheaper than hitting the weak table.
|
||||
asdisplaynode_iscancelled_block_t isCancelled = ^{
|
||||
return (BOOL)(_transitionID != transitionID);
|
||||
BOOL result = (_transitionID != transitionID);
|
||||
if (result) {
|
||||
as_log_verbose(ASLayoutLog(), "Transition %d canceled, superseded by %d", transitionID, _transitionID.load());
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
// Move all subnodes in layout pending state for this transition
|
||||
@@ -573,6 +594,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
if (isCancelled()) {
|
||||
return;
|
||||
}
|
||||
as_activity_scope(as_activity_create("Commit layout transition", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
ASLayoutTransition *pendingLayoutTransition;
|
||||
_ASTransitionContext *pendingLayoutTransitionContext;
|
||||
{
|
||||
@@ -598,10 +620,13 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
}
|
||||
|
||||
// Apply complete layout transitions for all subnodes
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node _completePendingLayoutTransition];
|
||||
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
||||
});
|
||||
{
|
||||
as_activity_scope(as_activity_create("Complete pending layout transitions for subtree", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
ASDisplayNodePerformBlockOnEverySubnode(self, NO, ^(ASDisplayNode * _Nonnull node) {
|
||||
[node _completePendingLayoutTransition];
|
||||
node.hierarchyState &= (~ASHierarchyStateLayoutPending);
|
||||
});
|
||||
}
|
||||
|
||||
// Measurement pass completion
|
||||
// Give the subclass a change to hook into before calling the completion block
|
||||
@@ -614,7 +639,10 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
[pendingLayoutTransition applySubnodeInsertions];
|
||||
|
||||
// Kick off animating the layout transition
|
||||
[self animateLayoutTransition:pendingLayoutTransitionContext];
|
||||
{
|
||||
as_activity_scope(as_activity_create("Animate layout transition", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
[self animateLayoutTransition:pendingLayoutTransitionContext];
|
||||
}
|
||||
|
||||
// Mark transaction as finished
|
||||
[self _finishOrCancelTransition];
|
||||
@@ -805,7 +833,7 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
*/
|
||||
- (void)_completeLayoutTransition:(ASLayoutTransition *)layoutTransition
|
||||
{
|
||||
// Layout transition is not supported for nodes that are not have automatic subnode management enabled
|
||||
// Layout transition is not supported for nodes that do not have automatic subnode management enabled
|
||||
if (layoutTransition == nil || self.automaticallyManagesSubnodes == NO) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -514,7 +514,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
#define ASDisplayNodeAssertThreadAffinity(viewNode) ASDisplayNodeAssert(!viewNode || ASMainThreadAssertionsAreDisabled() || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
#define ASDisplayNodeCAssertThreadAffinity(viewNode) ASDisplayNodeCAssert(!viewNode || ASMainThreadAssertionsAreDisabled() || ASDisplayNodeThreadIsMain() || !(viewNode).nodeLoaded, @"Incorrect display node thread affinity - this method should not be called off the main thread after the ASDisplayNode's view or layer have been created")
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
@@ -748,9 +748,6 @@ extern NSInteger const ASDefaultDrawingPriority;
|
||||
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (ASLayoutElementAsciiArtProtocol) <ASLayoutElementAsciiArtProtocol>
|
||||
@end
|
||||
|
||||
@interface ASDisplayNode (ASLayout)
|
||||
|
||||
/** @name Managing dimensions */
|
||||
|
||||
+52
-17
@@ -41,18 +41,14 @@
|
||||
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpec.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpecPrivate.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
#import <AsyncDisplayKit/ASSignpost.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
#import <AsyncDisplayKit/ASWeakProxy.h>
|
||||
#import <AsyncDisplayKit/ASResponderChainEnumerator.h>
|
||||
#import <AsyncDisplayKit/ASTipsController.h>
|
||||
|
||||
#if ASDisplayNodeLoggingEnabled
|
||||
#define LOG(...) NSLog(__VA_ARGS__)
|
||||
#else
|
||||
#define LOG(...)
|
||||
#endif
|
||||
|
||||
// Conditionally time these scopes to our debug ivars (only exist in debug/profile builds)
|
||||
#if TIME_DISPLAYNODE_OPS
|
||||
#define TIME_SCOPED(outVar) ASDN::ScopeTimer t(outVar)
|
||||
@@ -452,10 +448,10 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
for (Ivar ivar : ivars) {
|
||||
id value = object_getIvar(self, ivar);
|
||||
if (ASClassRequiresMainThreadDeallocation(object_getClass(value))) {
|
||||
LOG(@"Trampolining ivar '%s' value %@ for main deallocation.", ivar_getName(ivar), value);
|
||||
as_log_debug(ASMainThreadDeallocationLog(), "%@: Trampolining ivar '%s' value %@ for main deallocation.", self, ivar_getName(ivar), value);
|
||||
ASPerformMainThreadDeallocation(value);
|
||||
} else {
|
||||
LOG(@"Not trampolining ivar '%s' value %@.", ivar_getName(ivar), value);
|
||||
as_log_debug(ASMainThreadDeallocationLog(), "%@: Not trampolining ivar '%s' value %@.", self, ivar_getName(ivar), value);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -512,16 +508,16 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
// If it's `id` we have to include it just in case.
|
||||
resultIvars[resultCount] = ivar;
|
||||
resultCount += 1;
|
||||
LOG(@"Marking ivar '%s' for possible main deallocation due to type id", ivar_getName(ivar));
|
||||
as_log_debug(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for possible main deallocation due to type id", self, ivar_getName(ivar));
|
||||
} else {
|
||||
// If it's an ivar with a static type, check the type.
|
||||
Class c = ASGetClassFromType(type);
|
||||
if (ASClassRequiresMainThreadDeallocation(c)) {
|
||||
resultIvars[resultCount] = ivar;
|
||||
resultCount += 1;
|
||||
LOG(@"Marking ivar '%s' for main deallocation due to class %@", ivar_getName(ivar), c);
|
||||
as_log_debug(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for main deallocation due to class %@", self, ivar_getName(ivar), c);
|
||||
} else {
|
||||
LOG(@"Skipping ivar '%s' for main deallocation.", ivar_getName(ivar));
|
||||
as_log_debug(ASMainThreadDeallocationLog(), "%@: Skipping ivar '%s' for main deallocation.", self, ivar_getName(ivar));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -629,6 +625,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
ASDisplayNodeAssertMainThread();
|
||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||
ASDisplayNodeLogEvent(self, @"didLoad");
|
||||
as_log_verbose(ASNodeLog(), "didLoad %@", self);
|
||||
TIME_SCOPED(_debugTimeForDidLoad);
|
||||
|
||||
[self didLoad];
|
||||
@@ -914,7 +911,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
// Performing layout on a zero-bounds view often results in frame calculations
|
||||
// with negative sizes after applying margins, which will cause
|
||||
// measureWithSizeRange: on subnodes to assert.
|
||||
LOG(@"Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self);
|
||||
as_log_debug(OS_LOG_DISABLED, "Warning: No size given for node before node was trying to layout itself: %@. Please provide a frame for the node.", self);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -962,6 +959,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
// This is fast enough to do it unconditionally.
|
||||
auto key = ASPthreadStaticKey(NULL);
|
||||
BOOL isRootCall = (pthread_getspecific(key) == NULL);
|
||||
as_activity_scope_verbose(as_activity_create("Calculate node layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_verbose(ASLayoutLog(), "Calculating layout for %@ sizeRange %@", self, NSStringFromASSizeRange(constrainedSize));
|
||||
if (isRootCall) {
|
||||
pthread_setspecific(key, kCFBooleanTrue);
|
||||
ASSignpostStart(ASSignpostCalculateLayout);
|
||||
@@ -970,6 +969,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
ASSizeRange styleAndParentSize = ASLayoutElementSizeResolve(self.style.size, parentSize);
|
||||
const ASSizeRange resolvedRange = ASSizeRangeIntersect(constrainedSize, styleAndParentSize);
|
||||
ASLayout *result = [self calculateLayoutThatFits:resolvedRange];
|
||||
as_log_verbose(ASLayoutLog(), "Calculated layout %@", result);
|
||||
|
||||
if (isRootCall) {
|
||||
pthread_setspecific(key, NULL);
|
||||
@@ -1024,6 +1024,12 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
|
||||
|
||||
// Get layout element from the node
|
||||
id<ASLayoutElement> layoutElement = [self _locked_layoutElementThatFits:constrainedSize];
|
||||
#if ASEnableVerboseLogging
|
||||
for (NSString *asciiLine in [[layoutElement asciiArtString] componentsSeparatedByString:@"\n"]) {
|
||||
as_log_verbose(ASLayoutLog(), "%@", asciiLine);
|
||||
}
|
||||
#endif
|
||||
|
||||
|
||||
// Certain properties are necessary to set on an element of type ASLayoutSpec
|
||||
if (layoutElement.layoutElementType == ASLayoutElementTypeLayoutSpec) {
|
||||
@@ -1304,6 +1310,7 @@ NSString * const ASRenderingEngineDidDisplayNodesScheduledBeforeTimestamp = @"AS
|
||||
}];
|
||||
});
|
||||
|
||||
as_log_verbose(ASRenderLog(), "%s %@", sel_getName(_cmd), node);
|
||||
[renderQueue enqueue:node];
|
||||
}
|
||||
|
||||
@@ -1906,6 +1913,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
- (void)_insertSubnode:(ASDisplayNode *)subnode atSubnodeIndex:(NSInteger)subnodeIndex sublayerIndex:(NSInteger)sublayerIndex andRemoveSubnode:(ASDisplayNode *)oldSubnode
|
||||
{
|
||||
ASDisplayNodeAssertLockUnownedByCurrentThread(__instanceLock__);
|
||||
as_log_verbose(ASNodeLog(), "Insert subnode %@ at index %zd of %@ and remove subnode %@", subnode, subnodeIndex, self, oldSubnode);
|
||||
|
||||
if (subnode == nil || subnode == self) {
|
||||
ASDisplayNodeFailAssert(@"Cannot insert a nil subnode or self as subnode");
|
||||
@@ -2337,6 +2345,11 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
|
||||
- (void)_removeFromSupernode:(ASDisplayNode *)supernode view:(UIView *)view layer:(CALayer *)layer
|
||||
{
|
||||
// TODO: Should we simply return early if supernode is nil? This currently gets called for every insertSubnode.
|
||||
if (supernode != nil) {
|
||||
as_log_verbose(ASNodeLog(), "Remove %@ from supernode %@", self, supernode);
|
||||
}
|
||||
|
||||
// Clear supernode's reference to us before removing the view from the hierarchy, as _ASDisplayView
|
||||
// will trigger us to clear our _supernode pointer in willMoveToSuperview:nil.
|
||||
// This may result in removing the last strong reference, triggering deallocation after this method.
|
||||
@@ -2688,7 +2701,7 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
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));
|
||||
ASDisplayNodeLogEvent(self, @"%s %@", sel_getName(_cmd), NSStringFromASInterfaceState(interfaceState));
|
||||
ASDisplayNodePerformBlockOnEveryNode(nil, self, YES, ^(ASDisplayNode *node) {
|
||||
node.interfaceState &= (~interfaceState);
|
||||
});
|
||||
@@ -2696,6 +2709,8 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
|
||||
- (void)recursivelySetInterfaceState:(ASInterfaceState)newInterfaceState
|
||||
{
|
||||
as_activity_scope(as_activity_create("Recursively set interface state", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
|
||||
// Instead of each node in the recursion assuming it needs to schedule itself for display,
|
||||
// setInterfaceState: skips this when handling range-managed nodes (our whole subtree has this set).
|
||||
// If our range manager intends for us to be displayed right now, and didn't before, get started!
|
||||
@@ -2819,7 +2834,14 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
}
|
||||
}
|
||||
|
||||
ASDisplayNodeLogEvent(self, @"interfaceStateDidChange: %@, old: %@", NSStringFromASInterfaceState(newState), NSStringFromASInterfaceState(oldState));
|
||||
// Log this change, unless it's just the node going from {} -> {Measure} because that change happens
|
||||
// for all cell nodes and it isn't currently meaningful.
|
||||
BOOL isJustEnteringMeasure = (oldState == ASInterfaceStateNone && newState == ASInterfaceStateMeasureLayout);
|
||||
if (!isJustEnteringMeasure) {
|
||||
as_log_verbose(ASNodeLog(), "%s %@ %@", sel_getName(_cmd), NSStringFromASInterfaceStateChange(oldState, newState), self);
|
||||
}
|
||||
|
||||
ASDisplayNodeLogEvent(self, @"interfaceStateDidChange: %@", NSStringFromASInterfaceStateChange(oldState, newState));
|
||||
[self interfaceStateDidChange:newState fromState:oldState];
|
||||
}
|
||||
|
||||
@@ -3172,9 +3194,19 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
|
||||
if (self.debugName.length > 0) {
|
||||
[result addObject:@{ @"debugName" : ASStringWithQuotesIfMultiword(self.debugName) }];
|
||||
ASPushMainThreadAssertionsDisabled();
|
||||
|
||||
NSString *debugName = self.debugName;
|
||||
if (debugName.length > 0) {
|
||||
[result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(debugName) }];
|
||||
}
|
||||
|
||||
NSString *axId = self.accessibilityIdentifier;
|
||||
if (axId.length > 0) {
|
||||
[result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(axId) }];
|
||||
}
|
||||
|
||||
ASPopMainThreadAssertionsDisabled();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -3185,7 +3217,10 @@ ASDISPLAYNODE_INLINE BOOL subtreeIsRasterized(ASDisplayNode *node) {
|
||||
if (self.debugName.length > 0) {
|
||||
[result addObject:@{ @"debugName" : ASStringWithQuotesIfMultiword(self.debugName)}];
|
||||
}
|
||||
|
||||
if (self.accessibilityIdentifier.length > 0) {
|
||||
[result addObject:@{ @"axId": ASStringWithQuotesIfMultiword(self.accessibilityIdentifier) }];
|
||||
}
|
||||
|
||||
CGRect windowFrame = [self _frameInWindow];
|
||||
if (CGRectIsNull(windowFrame) == NO) {
|
||||
[result addObject:@{ @"frameInWindow" : [NSValue valueWithCGRect:windowFrame] }];
|
||||
|
||||
@@ -27,8 +27,10 @@
|
||||
* for the nodes will be set to `MYButtonNode.titleNode` and `MYButtonNode.countNode`.
|
||||
*/
|
||||
#if DEBUG
|
||||
#define ASSetDebugName(node, format, ...) node.debugName = [NSString stringWithFormat:format, __VA_ARGS__]
|
||||
#define ASSetDebugNames(...) _ASSetDebugNames(self.class, @"" # __VA_ARGS__, __VA_ARGS__, nil)
|
||||
#else
|
||||
#define ASSetDebugName(node, name)
|
||||
#define ASSetDebugNames(...)
|
||||
#endif
|
||||
|
||||
@@ -78,6 +80,30 @@ __unused static NSString * _Nonnull NSStringFromASInterfaceState(ASInterfaceStat
|
||||
return [NSString stringWithFormat:@"{ %@ }", [states componentsJoinedByString:@" | "]];
|
||||
}
|
||||
|
||||
#define INTERFACE_STATE_DELTA(Name) ({ \
|
||||
if ((oldState & ASInterfaceState##Name) != (newState & ASInterfaceState##Name)) { \
|
||||
[changes appendFormat:@"%c%s ", (newState & ASInterfaceState##Name ? '+' : '-'), #Name]; \
|
||||
} \
|
||||
})
|
||||
|
||||
/// e.g. { +Visible, -Preload } (although that should never actually happen.)
|
||||
/// NOTE: Changes to MeasureLayout state don't really mean anything so we omit them for now.
|
||||
__unused static NSString * _Nonnull NSStringFromASInterfaceStateChange(ASInterfaceState oldState, ASInterfaceState newState)
|
||||
{
|
||||
if (oldState == newState) {
|
||||
return @"{ }";
|
||||
}
|
||||
|
||||
NSMutableString *changes = [NSMutableString stringWithString:@"{ "];
|
||||
INTERFACE_STATE_DELTA(Preload);
|
||||
INTERFACE_STATE_DELTA(Display);
|
||||
INTERFACE_STATE_DELTA(Visible);
|
||||
[changes appendString:@"}"];
|
||||
return changes;
|
||||
}
|
||||
|
||||
#undef INTERFACE_STATE_DELTA
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
|
||||
@@ -24,6 +24,7 @@
|
||||
#import <AsyncDisplayKit/ASPhotosFrameworkImageRequest.h>
|
||||
#import <AsyncDisplayKit/ASEqualityHelpers.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#if AS_PIN_REMOTE_IMAGE
|
||||
#import <AsyncDisplayKit/ASPINRemoteImageDownloader.h>
|
||||
@@ -450,7 +451,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
// Grab the best possible image we can load right now.
|
||||
id bestImmediatelyAvailableImageIdentifier = nil;
|
||||
UIImage *bestImmediatelyAvailableImage = [self _bestImmediatelyAvailableImageFromDataSource:&bestImmediatelyAvailableImageIdentifier];
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "%@ Best immediately available image identifier is %@", self, bestImmediatelyAvailableImageIdentifier);
|
||||
|
||||
// Load it. This kicks off cache fetching/downloading, as appropriate.
|
||||
[self _finishedLoadingImage:bestImmediatelyAvailableImage forIdentifier:bestImmediatelyAvailableImageIdentifier error:nil];
|
||||
@@ -580,6 +581,8 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
return;
|
||||
}
|
||||
|
||||
as_activity_scope(as_activity_create("Load next image for multiplex image node", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_verbose(ASImageLoadingLog(), "Loading image for %@ ident: %@", self, nextImageIdentifier);
|
||||
self.loadingImageIdentifier = nextImageIdentifier;
|
||||
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
@@ -595,13 +598,11 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
[strongSelf _finishedLoadingImage:image forIdentifier:imageIdentifier error:error];
|
||||
};
|
||||
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Loading next image, ident: %@", self, nextImageIdentifier);
|
||||
|
||||
// Ask our data-source if it's got this image.
|
||||
if (_dataSourceFlags.image) {
|
||||
UIImage *image = [_dataSource multiplexImageNode:self imageForImageIdentifier:nextImageIdentifier];
|
||||
if (image) {
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] Acquired next image (%@) from data-source", self, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from data source for %@ ident: %@", self, nextImageIdentifier);
|
||||
finishedLoadingBlock(image, nextImageIdentifier, nil);
|
||||
return;
|
||||
}
|
||||
@@ -610,7 +611,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
NSURL *nextImageURL = (_dataSourceFlags.URL) ? [_dataSource multiplexImageNode:self URLForImageIdentifier:nextImageIdentifier] : nil;
|
||||
// If we fail to get a URL for the image, we have no source and can't proceed.
|
||||
if (!nextImageURL) {
|
||||
ASMultiplexImageNodeLogError(@"[%p] Could not acquire URL for next image (%@). Bailing.", self, nextImageIdentifier);
|
||||
as_log_error(ASImageLoadingLog(), "Could not acquire URL %@ ident: (%@)", self, nextImageIdentifier);
|
||||
finishedLoadingBlock(nil, nil, [NSError errorWithDomain:ASMultiplexImageNodeErrorDomain code:ASMultiplexImageNodeErrorCodeNoSourceForImage userInfo:nil]);
|
||||
return;
|
||||
}
|
||||
@@ -620,14 +621,14 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
if ([[nextImageURL scheme] isEqualToString:kAssetsLibraryURLScheme]) {
|
||||
// Load the asset.
|
||||
[self _loadALAssetWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from asset library", weakSelf, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from assets library for %@ %@", weakSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(downloadedImage, nextImageIdentifier, error);
|
||||
}];
|
||||
}
|
||||
// Likewise, if it's a iOS 8 Photo asset, we need to fetch it accordingly.
|
||||
else if (ASPhotosFrameworkImageRequest *request = [ASPhotosFrameworkImageRequest requestWithURL:nextImageURL]) {
|
||||
[self _loadPHAssetWithRequest:request identifier:nextImageIdentifier completion:^(UIImage *image, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from Photos Framework", weakSelf, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from Photos for %@ %@", weakSelf, nextImageIdentifier);
|
||||
finishedLoadingBlock(image, nextImageIdentifier, error);
|
||||
}];
|
||||
}
|
||||
@@ -642,7 +643,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
|
||||
// If we had a cache-hit, we're done.
|
||||
if (imageFromCache) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from cache", strongSelf, nextImageIdentifier);
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from cache for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, imageFromCache);
|
||||
finishedLoadingBlock(imageFromCache, nextImageIdentifier, nil);
|
||||
return;
|
||||
}
|
||||
@@ -655,7 +656,12 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
|
||||
// Otherwise, we've got to download it.
|
||||
[strongSelf _downloadImageWithIdentifier:nextImageIdentifier URL:nextImageURL completion:^(UIImage *downloadedImage, NSError *error) {
|
||||
ASMultiplexImageNodeCLogDebug(@"[%p] Acquired next image (%@) from download", strongSelf, nextImageIdentifier);
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (downloadedImage) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Acquired image from download for %@ id: %@ img: %@", strongSelf, nextImageIdentifier, downloadedImage);
|
||||
} else {
|
||||
as_log_error(ASImageLoadingLog(), "Error downloading image for %@ id: %@ err: %@", strongSelf, nextImageIdentifier, error);
|
||||
}
|
||||
finishedLoadingBlock(downloadedImage, nextImageIdentifier, error);
|
||||
}];
|
||||
}];
|
||||
@@ -851,7 +857,7 @@ typedef void(^ASMultiplexImageLoadCompletionBlock)(UIImage *image, id imageIdent
|
||||
// We explicitly perform this check because our datasource often doesn't give back immediately available images, even though we might have downloaded one already.
|
||||
// Because we seed this call with bestImmediatelyAvailableImageFromDataSource, we must be careful not to trample an existing image.
|
||||
if (image || imageIdentifierCount == 0) {
|
||||
ASMultiplexImageNodeLogDebug(@"[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image);
|
||||
as_log_verbose(ASImageLoadingLog(), "[%p] loaded -> displaying (%@, %@)", self, imageIdentifier, image);
|
||||
id previousIdentifier = self.loadedImageIdentifier;
|
||||
UIImage *previousImage = self.image;
|
||||
|
||||
|
||||
@@ -16,6 +16,8 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASNavigationController.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
|
||||
@implementation ASNavigationController
|
||||
{
|
||||
@@ -65,39 +67,53 @@ ASVisibilityDepthImplementation;
|
||||
|
||||
- (NSArray *)popToViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
as_activity_scope(as_activity_create("Pop multiple from ASNavigationController", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
NSArray *viewControllers = [super popToViewController:viewController animated:animated];
|
||||
as_log_info(ASNodeLog(), "Popped %@ to %@, removing %@", self, viewController, ASGetDescriptionValueString(viewControllers));
|
||||
|
||||
[self visibilityDepthDidChange];
|
||||
return viewControllers;
|
||||
}
|
||||
|
||||
- (NSArray *)popToRootViewControllerAnimated:(BOOL)animated
|
||||
{
|
||||
as_activity_scope(as_activity_create("Pop to root of ASNavigationController", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
NSArray *viewControllers = [super popToRootViewControllerAnimated:animated];
|
||||
as_log_info(ASNodeLog(), "Popped view controllers %@ from %@", ASGetDescriptionValueString(viewControllers), self);
|
||||
|
||||
[self visibilityDepthDidChange];
|
||||
return viewControllers;
|
||||
}
|
||||
|
||||
- (void)setViewControllers:(NSArray *)viewControllers
|
||||
{
|
||||
// NOTE: As of now this method calls through to setViewControllers:animated: so no need to log/activity here.
|
||||
|
||||
[super setViewControllers:viewControllers];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (void)setViewControllers:(NSArray *)viewControllers animated:(BOOL)animated
|
||||
{
|
||||
as_activity_scope(as_activity_create("Set view controllers of ASNavigationController", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_info(ASNodeLog(), "Set view controllers of %@ to %@ animated: %d", self, ASGetDescriptionValueString(viewControllers), animated);
|
||||
[super setViewControllers:viewControllers animated:animated];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (void)pushViewController:(UIViewController *)viewController animated:(BOOL)animated
|
||||
{
|
||||
as_activity_scope(as_activity_create("Push view controller on ASNavigationController", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_info(ASNodeLog(), "Pushing %@ onto %@", viewController, self);
|
||||
[super pushViewController:viewController animated:animated];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (UIViewController *)popViewControllerAnimated:(BOOL)animated
|
||||
{
|
||||
as_activity_scope(as_activity_create("Pop view controller from ASNavigationController", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
UIViewController *viewController = [super popViewControllerAnimated:animated];
|
||||
as_log_info(ASNodeLog(), "Popped %@ from %@", viewController, self);
|
||||
[self visibilityDepthDidChange];
|
||||
return viewController;
|
||||
}
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#import <AsyncDisplayKit/ASImageNode+Private.h>
|
||||
#import <AsyncDisplayKit/ASImageNode+AnimatedImagePrivate.h>
|
||||
#import <AsyncDisplayKit/ASImageContainerProtocolCategories.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#if AS_PIN_REMOTE_IMAGE
|
||||
#import <AsyncDisplayKit/ASPINRemoteImageDownloader.h>
|
||||
@@ -417,6 +418,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
return;
|
||||
}
|
||||
|
||||
as_log_verbose(ASImageLoadingLog(), "Received progress image for %@ q: %.2g id: %@", self, progress, progressImage);
|
||||
[self _locked_setCurrentImageQuality:progress];
|
||||
[self _locked__setImage:progressImage];
|
||||
}
|
||||
@@ -443,12 +445,14 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
// Unbind from the previous download.
|
||||
if (oldDownloadIDForProgressBlock != nil) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Disabled progress images for %@ id: %@", self, oldDownloadIDForProgressBlock);
|
||||
[_downloader setProgressImageBlock:nil callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:oldDownloadIDForProgressBlock];
|
||||
}
|
||||
|
||||
// Bind to the current download.
|
||||
if (newDownloadIDForProgressBlock != nil) {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
as_log_verbose(ASImageLoadingLog(), "Enabled progress images for %@ id: %@", self, newDownloadIDForProgressBlock);
|
||||
[_downloader setProgressImageBlock:^(UIImage * _Nonnull progressImage, CGFloat progress, id _Nullable downloadIdentifier) {
|
||||
[weakSelf handleProgressImage:progressImage progress:progress downloadIdentifier:downloadIdentifier];
|
||||
} callbackQueue:dispatch_get_main_queue() withDownloadIdentifier:newDownloadIDForProgressBlock];
|
||||
@@ -507,6 +511,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
if (_cacheFlags.cacheSupportsClearing) {
|
||||
if (_URL != nil) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Clearing cached image for %@ url: %@", self, _URL);
|
||||
[_cache clearFetchedImageFromCacheWithURL:_URL];
|
||||
}
|
||||
}
|
||||
@@ -526,8 +531,10 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
if (_downloadIdentifier) {
|
||||
if (storeResume && _downloaderFlags.downloaderImplementsCancelWithResume) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Canceling image download w resume for %@ id: %@", self, _downloadIdentifier);
|
||||
[_downloader cancelImageDownloadWithResumePossibilityForIdentifier:_downloadIdentifier];
|
||||
} else {
|
||||
as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, _downloadIdentifier);
|
||||
[_downloader cancelImageDownloadForIdentifier:_downloadIdentifier];
|
||||
}
|
||||
}
|
||||
@@ -550,15 +557,17 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
url = _URL;
|
||||
}
|
||||
|
||||
|
||||
downloadIdentifier = [_downloader downloadImageWithURL:url
|
||||
callbackQueue:dispatch_get_main_queue()
|
||||
downloadProgress:NULL
|
||||
completion:^(id <ASImageContainerProtocol> _Nullable imageContainer, NSError * _Nullable error, id _Nullable downloadIdentifier) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Downloaded image for %@ img: %@ url: %@", self, [imageContainer asdk_image], url);
|
||||
if (finished != NULL) {
|
||||
finished(imageContainer, error, downloadIdentifier);
|
||||
}
|
||||
}];
|
||||
as_log_verbose(ASImageLoadingLog(), "Downloading image for %@ url: %@", self, url);
|
||||
|
||||
{
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
@@ -574,6 +583,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
|
||||
if (cancelAndReattempt) {
|
||||
if (downloadIdentifier != nil) {
|
||||
as_log_verbose(ASImageLoadingLog(), "Canceling image download no resume for %@ id: %@", self, downloadIdentifier);
|
||||
[_downloader cancelImageDownloadForIdentifier:downloadIdentifier];
|
||||
}
|
||||
[self _downloadImageWithCompletion:finished];
|
||||
@@ -654,7 +664,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
});
|
||||
} else {
|
||||
__weak __typeof__(self) weakSelf = self;
|
||||
void (^finished)(id <ASImageContainerProtocol>, NSError *, id downloadIdentifier) = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier) {
|
||||
auto finished = ^(id <ASImageContainerProtocol>imageContainer, NSError *error, id downloadIdentifier) {
|
||||
|
||||
__typeof__(self) strongSelf = weakSelf;
|
||||
if (strongSelf == nil) {
|
||||
@@ -702,6 +712,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
_cacheUUID = cacheUUID;
|
||||
__instanceLock__.unlock();
|
||||
|
||||
as_log_verbose(ASImageLoadingLog(), "Decaching image for %@ url: %@", self, URL);
|
||||
[_cache cachedImageWithURL:URL
|
||||
callbackQueue:dispatch_get_main_queue()
|
||||
completion:^(id <ASImageContainerProtocol> imageContainer) {
|
||||
@@ -717,6 +728,7 @@ static const CGSize kMinReleaseImageOnBackgroundSize = {20.0, 20.0};
|
||||
if ([imageContainer asdk_image] == nil && _downloader != nil) {
|
||||
[self _downloadImageWithCompletion:finished];
|
||||
} else {
|
||||
as_log_verbose(ASImageLoadingLog(), "Decached image for %@ img: %@ url: %@", self, [imageContainer asdk_image], URL);
|
||||
finished(imageContainer, nil, nil);
|
||||
}
|
||||
}];
|
||||
|
||||
+25
-10
@@ -16,9 +16,11 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASRunLoopQueue.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASSignpost.h>
|
||||
#import <QuartzCore/QuartzCore.h>
|
||||
#import <cstdlib>
|
||||
#import <deque>
|
||||
@@ -190,7 +192,10 @@ static void runLoopSourceCallback(void *info) {
|
||||
CFRunLoopObserverRef _runLoopObserver;
|
||||
NSPointerArray *_internalQueue; // Use NSPointerArray so we can decide __strong or __weak per-instance.
|
||||
ASDN::RecursiveMutex _internalQueueLock;
|
||||
|
||||
|
||||
// In order to not pollute the top-level activities, each queue has 1 root activity.
|
||||
os_activity_t _rootActivity;
|
||||
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
NSTimer *_runloopQueueLoggingTimer;
|
||||
#endif
|
||||
@@ -272,6 +277,15 @@ typedef enum {
|
||||
_queueConsumer = handlerBlock;
|
||||
_batchSize = 1;
|
||||
_ensureExclusiveMembership = YES;
|
||||
|
||||
// We don't want to pollute the top-level app activities with run loop batches, so we create one top-level
|
||||
// activity per queue, and each batch activity joins that one instead.
|
||||
_rootActivity = as_activity_create("Process run loop queue items", OS_ACTIVITY_NONE, OS_ACTIVITY_FLAG_DEFAULT);
|
||||
{
|
||||
// Log a message identifying this queue into the queue's root activity.
|
||||
as_activity_scope_verbose(_rootActivity);
|
||||
as_log_verbose(ASRenderLog(), "Created run loop queue: %@", self);
|
||||
}
|
||||
|
||||
// Self is guaranteed to outlive the observer. Without the high cost of a weak pointer,
|
||||
// __unsafe_unretained allows us to avoid flagging the memory cycle detector.
|
||||
@@ -375,16 +389,17 @@ typedef enum {
|
||||
}
|
||||
|
||||
// itemsToProcess will be empty if _queueConsumer == nil so no need to check again.
|
||||
if (itemsToProcess.empty() == false) {
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
NSLog(@"<%@> - Starting processing of: %ld", self, itemsToProcess.size());
|
||||
#endif
|
||||
auto count = itemsToProcess.size();
|
||||
if (count > 0) {
|
||||
as_activity_scope_verbose(as_activity_create("Process run loop queue batch", _rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
auto itemsEnd = itemsToProcess.cend();
|
||||
for (auto iterator = itemsToProcess.begin(); iterator < itemsEnd; iterator++) {
|
||||
_queueConsumer(*iterator, isQueueDrained && iterator == itemsEnd - 1);
|
||||
#if ASRunLoopQueueLoggingEnabled
|
||||
NSLog(@"<%@> - Finished processing 1 item", self);
|
||||
#endif
|
||||
__unsafe_unretained id value = *iterator;
|
||||
_queueConsumer(value, isQueueDrained && iterator == itemsEnd - 1);
|
||||
as_log_verbose(ASRenderLog(), "processed %@", value);
|
||||
}
|
||||
if (count > 1) {
|
||||
as_log_verbose(ASRenderLog(), "processed %lu items", (unsigned long)count);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASTabBarController.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
@implementation ASTabBarController
|
||||
{
|
||||
@@ -72,12 +73,18 @@ ASVisibilityDepthImplementation;
|
||||
|
||||
- (void)setSelectedIndex:(NSUInteger)selectedIndex
|
||||
{
|
||||
as_activity_scope(as_activity_create("Set selected index of ASTabBarController", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_info(ASNodeLog(), "Selected tab %tu of %@", selectedIndex, self);
|
||||
|
||||
[super setSelectedIndex:selectedIndex];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
- (void)setSelectedViewController:(__kindof UIViewController *)selectedViewController
|
||||
{
|
||||
as_activity_scope(as_activity_create("Set selected view controller of ASTabBarController", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_info(ASNodeLog(), "Selected view controller %@ of %@", selectedViewController, self);
|
||||
|
||||
[super setSelectedViewController:selectedViewController];
|
||||
[self visibilityDepthDidChange];
|
||||
}
|
||||
|
||||
@@ -250,7 +250,7 @@ static NSArray *DefaultLinkAttributeNames = @[ NSLinkAttributeName ];
|
||||
NSMutableArray *result = [super propertiesForDescription];
|
||||
NSString *plainString = [self _plainStringForDescription];
|
||||
if (plainString.length > 0) {
|
||||
[result insertObject:@{ @"text" : ASStringWithQuotesIfMultiword(plainString) } atIndex:0];
|
||||
[result addObject:@{ (id)kCFNull : ASStringWithQuotesIfMultiword(plainString) }];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -19,12 +19,11 @@
|
||||
#import <AsyncDisplayKit/ASAssert.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNode+FrameworkPrivate.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
#import <AsyncDisplayKit/ASRangeControllerUpdateRangeProtocol+Beta.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
|
||||
#define AS_LOG_VISIBILITY_CHANGES 0
|
||||
|
||||
@implementation ASViewController
|
||||
{
|
||||
BOOL _ensureDisplayed;
|
||||
@@ -169,7 +168,11 @@ ASVisibilityDidMoveToParentViewController;
|
||||
|
||||
- (void)viewWillAppear:(BOOL)animated
|
||||
{
|
||||
as_activity_scope(as_activity_create("ASViewController will appear", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_debug(ASNodeLog(), "View controller %@ will appear", self);
|
||||
|
||||
[super viewWillAppear:animated];
|
||||
|
||||
_ensureDisplayed = YES;
|
||||
|
||||
// A layout pass is forced this early to get nodes like ASCollectionNode, ASTableNode etc.
|
||||
@@ -191,7 +194,7 @@ ASVisibilityDepthImplementation;
|
||||
- (void)visibilityDepthDidChange
|
||||
{
|
||||
ASLayoutRangeMode rangeMode = ASLayoutRangeModeForVisibilityDepth(self.visibilityDepth);
|
||||
#if AS_LOG_VISIBILITY_CHANGES
|
||||
#if ASEnableVerboseLogging
|
||||
NSString *rangeModeString;
|
||||
switch (rangeMode) {
|
||||
case ASLayoutRangeModeMinimum:
|
||||
@@ -213,7 +216,7 @@ ASVisibilityDepthImplementation;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
NSLog(@"Updating visibility of:%@ to: %@ (visibility depth: %d)", self, rangeModeString, self.visibilityDepth);
|
||||
as_log_verbose(ASNodeLog(), "Updating visibility of %@ to: %@ (visibility depth: %zd)", self, rangeModeString, self.visibilityDepth);
|
||||
#endif
|
||||
[self updateCurrentRangeModeWithModeIfPossible:rangeMode];
|
||||
}
|
||||
@@ -284,6 +287,8 @@ ASVisibilityDepthImplementation;
|
||||
ASPrimitiveTraitCollection oldTraitCollection = self.node.primitiveTraitCollection;
|
||||
|
||||
if (ASPrimitiveTraitCollectionIsEqualToASPrimitiveTraitCollection(traitCollection, oldTraitCollection) == NO) {
|
||||
as_activity_scope_verbose(as_activity_create("Propagate ASViewController trait collection", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_debug(ASNodeLog(), "Propagating new traits for %@: %@", self, NSStringFromASPrimitiveTraitCollection(traitCollection));
|
||||
self.node.primitiveTraitCollection = traitCollection;
|
||||
|
||||
NSArray<id<ASLayoutElement>> *children = [self.node sublayoutElements];
|
||||
@@ -295,6 +300,7 @@ ASVisibilityDepthImplementation;
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
// Once we've propagated all the traits, layout this node.
|
||||
// Remeasure the node with the latest constrained size – old constrained size may be incorrect.
|
||||
as_activity_scope_verbose(as_activity_create("Layout ASViewController node with new traits", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
[_node layoutThatFits:[self nodeConstrainedSize]];
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
// The C++ compiler mangles C function names. extern "C" { /* your C functions */ } prevents this.
|
||||
// You should wrap all C function prototypes declared in headers with ASDISPLAYNODE_EXTERN_C_BEGIN/END, even if
|
||||
// they are included only from .m (Objective-C) files. It's common for .m files to start using C++
|
||||
|
||||
+87
-79
@@ -16,93 +16,101 @@
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASAvailability.h>
|
||||
#import <AsyncDisplayKit/ASBaseDefines.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <os/log.h>
|
||||
#import <os/activity.h>
|
||||
|
||||
/// The signposts we use. Signposts are grouped by color. The SystemTrace.tracetemplate file
|
||||
/// should be kept up-to-date with these values.
|
||||
typedef NS_ENUM(uint32_t, ASSignpostName) {
|
||||
// Collection/Table (Blue)
|
||||
ASSignpostDataControllerBatch = 300, // Alloc/layout nodes before collection update.
|
||||
ASSignpostRangeControllerUpdate, // Ranges update pass.
|
||||
ASSignpostCollectionUpdate, // Entire update process, from -endUpdates to [super perform…]
|
||||
|
||||
// Rendering (Green)
|
||||
ASSignpostLayerDisplay = 325, // Client display callout.
|
||||
ASSignpostRunLoopQueueBatch, // One batch of ASRunLoopQueue.
|
||||
|
||||
// Layout (Purple)
|
||||
ASSignpostCalculateLayout = 350, // Start of calculateLayoutThatFits to end. Max 1 per thread.
|
||||
|
||||
// Misc (Orange)
|
||||
ASSignpostDeallocQueueDrain = 375, // One chunk of dealloc queue work. arg0 is count.
|
||||
ASSignpostCATransactionLayout, // The CA transaction commit layout phase.
|
||||
ASSignpostCATransactionCommit // The CA transaction commit post-layout phase.
|
||||
};
|
||||
|
||||
typedef NS_ENUM(uintptr_t, ASSignpostColor) {
|
||||
ASSignpostColorBlue,
|
||||
ASSignpostColorGreen,
|
||||
ASSignpostColorPurple,
|
||||
ASSignpostColorOrange,
|
||||
ASSignpostColorRed,
|
||||
ASSignpostColorDefault
|
||||
};
|
||||
|
||||
static inline ASSignpostColor ASSignpostGetColor(ASSignpostName name, ASSignpostColor colorPref) {
|
||||
if (colorPref == ASSignpostColorDefault) {
|
||||
return (ASSignpostColor)((name / 25) % 4);
|
||||
} else {
|
||||
return colorPref;
|
||||
}
|
||||
}
|
||||
|
||||
#define ASMultiplexImageNodeLogDebug(...)
|
||||
#define ASMultiplexImageNodeCLogDebug(...)
|
||||
|
||||
#define ASMultiplexImageNodeLogError(...)
|
||||
#define ASMultiplexImageNodeCLogError(...)
|
||||
|
||||
#define AS_KDEBUG_ENABLE defined(PROFILE) && __has_include(<sys/kdebug_signpost.h>)
|
||||
|
||||
#if AS_KDEBUG_ENABLE
|
||||
|
||||
#import <sys/kdebug_signpost.h>
|
||||
|
||||
// These definitions are required to build the backward-compatible kdebug trace
|
||||
// on the iOS 10 SDK. The kdebug_trace function crashes if run on iOS 9 and earlier.
|
||||
// It's valuable to support trace signposts on iOS 9, because A5 devices don't support iOS 10.
|
||||
#ifndef DBG_MACH_CHUD
|
||||
#define DBG_MACH_CHUD 0x0A
|
||||
#define DBG_FUNC_NONE 0
|
||||
#define DBG_FUNC_START 1
|
||||
#define DBG_FUNC_END 2
|
||||
#define DBG_APPS 33
|
||||
#define SYS_kdebug_trace 180
|
||||
#define KDBG_CODE(Class, SubClass, code) (((Class & 0xff) << 24) | ((SubClass & 0xff) << 16) | ((code & 0x3fff) << 2))
|
||||
#define APPSDBG_CODE(SubClass,code) KDBG_CODE(DBG_APPS, SubClass, code)
|
||||
#ifndef ASEnableLogs
|
||||
#define ASEnableLogs 1
|
||||
#endif
|
||||
|
||||
// Currently we'll reserve arg3.
|
||||
#define ASSignpost(name, identifier, arg2, color) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_NONE, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color));
|
||||
#ifndef ASEnableVerboseLogging
|
||||
#define ASEnableVerboseLogging 0
|
||||
#endif
|
||||
|
||||
#define ASSignpostStartCustom(name, identifier, arg2) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost_start(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_START, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0);
|
||||
#define ASSignpostStart(name) ASSignpostStartCustom(name, self, 0)
|
||||
ASDISPLAYNODE_EXTERN_C_BEGIN
|
||||
|
||||
#define ASSignpostEndCustom(name, identifier, arg2, color) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost_end(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_END, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color));
|
||||
#define ASSignpostEnd(name) ASSignpostEndCustom(name, self, 0, ASSignpostColorDefault)
|
||||
/// Log for general node events e.g. interfaceState, didLoad.
|
||||
#define ASNodeLogEnabled 1
|
||||
os_log_t ASNodeLog();
|
||||
|
||||
/// Log for layout-specific events e.g. calculateLayout.
|
||||
#define ASLayoutLogEnabled 1
|
||||
os_log_t ASLayoutLog();
|
||||
|
||||
/// Log for display-specific events e.g. display queue batches.
|
||||
#define ASRenderLogEnabled 1
|
||||
os_log_t ASRenderLog();
|
||||
|
||||
/// Log for collection events e.g. reloadData, performBatchUpdates.
|
||||
#define ASCollectionLogEnabled 1
|
||||
os_log_t ASCollectionLog();
|
||||
|
||||
/// Log for ASNetworkImageNode and ASMultiplexImageNode events.
|
||||
#define ASImageLoadingLogEnabled 1
|
||||
os_log_t ASImageLoadingLog();
|
||||
|
||||
/// Specialized log for our main thread deallocation trampoline.
|
||||
#define ASMainThreadDeallocationLogEnabled 0
|
||||
os_log_t ASMainThreadDeallocationLog();
|
||||
|
||||
ASDISPLAYNODE_EXTERN_C_END
|
||||
|
||||
/**
|
||||
* The activity tracing system changed a lot between iOS 9 and 10.
|
||||
* In iOS 10, the system was merged with logging and became much more powerful
|
||||
* and adopted a new API.
|
||||
*
|
||||
* The legacy API is visible, but its functionality is extremely limited and the API is so different
|
||||
* that we don't bother with it. For example, activities described by os_activity_start/end are not
|
||||
* reflected in the log whereas activities described by the newer
|
||||
* os_activity_scope are. So unfortunately we must use these iOS 10
|
||||
* APIs to get meaningful logging data.
|
||||
*/
|
||||
#if OS_LOG_TARGET_HAS_10_12_FEATURES
|
||||
|
||||
#define OS_ACTIVITY_NULLABLE nullable
|
||||
#define AS_ACTIVITY_CURRENT OS_ACTIVITY_CURRENT
|
||||
#define as_activity_scope(activity) os_activity_scope(activity)
|
||||
#define as_activity_apply(activity, block) os_activity_apply(activity, block)
|
||||
#define as_activity_create(description, parent_activity, flags) os_activity_create(description, parent_activity, flags)
|
||||
#define as_activity_scope_enter(activity, statePtr) os_activity_scope_enter(activity, statePtr)
|
||||
#define as_activity_scope_leave(statePtr) os_activity_scope_leave(statePtr)
|
||||
#define as_activity_get_identifier(activity, outParentID) os_activity_get_identifier(activity, outParentID)
|
||||
|
||||
#if ASEnableVerboseLogging
|
||||
#define as_activity_scope_verbose(activity) as_activity_scope(activity)
|
||||
#else
|
||||
#define as_activity_scope_verbose(activity)
|
||||
#endif
|
||||
|
||||
#else
|
||||
|
||||
#define ASSignpost(name, identifier, arg2, color)
|
||||
#define ASSignpostStartCustom(name, identifier, arg2)
|
||||
#define ASSignpostStart(name)
|
||||
#define ASSignpostEndCustom(name, identifier, arg2, color)
|
||||
#define ASSignpostEnd(name)
|
||||
#define OS_ACTIVITY_NULLABLE
|
||||
#define AS_ACTIVITY_CURRENT OS_ACTIVITY_NULL
|
||||
#define as_activity_scope(activity)
|
||||
#define as_activity_scope_verbose(activity)
|
||||
#define as_activity_apply(activity, block)
|
||||
#define as_activity_create(description, parent_activity, flags) OS_ACTIVITY_NULL
|
||||
#define as_activity_scope_enter(activity, statePtr)
|
||||
#define as_activity_scope_leave(statePtr)
|
||||
#define as_activity_get_identifier(activity, outParentID) (os_activity_id_t)0
|
||||
|
||||
#endif // OS_LOG_TARGET_HAS_10_12_FEATURES
|
||||
|
||||
/**
|
||||
* The logging macros are not guarded by deployment-target checks like the activity macros are, but they are
|
||||
* only available on iOS >= 9 at runtime, so just make them conditional.
|
||||
*/
|
||||
#define as_log_create(subsystem, category) (AS_AT_LEAST_IOS9 ? os_log_create(subsystem, category) : (os_log_t)0)
|
||||
#define as_log_debug(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_debug(log, format, ##__VA_ARGS__) : (void)0)
|
||||
#define as_log_info(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_info(log, format, ##__VA_ARGS__) : (void)0)
|
||||
#define as_log_error(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_error(log, format, ##__VA_ARGS__) : (void)0)
|
||||
#define as_log_fault(log, format, ...) (AS_AT_LEAST_IOS9 ? os_log_fault(log, format, ##__VA_ARGS__) : (void)0)
|
||||
|
||||
#if ASEnableVerboseLogging
|
||||
#define as_log_verbose(log, format, ...) as_log_debug(log, format, ##__VA_ARGS__)
|
||||
#else
|
||||
#define as_log_verbose(log, format, ...)
|
||||
#endif
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
//
|
||||
// ASLog.m
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
os_log_t ASNodeLog() {
|
||||
static os_log_t log;
|
||||
#if ASEnableLogs && ASNodeLogEnabled
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
log = as_log_create("org.TextureGroup.Texture", "Node");
|
||||
});
|
||||
#endif
|
||||
return log;
|
||||
}
|
||||
|
||||
os_log_t ASLayoutLog() {
|
||||
static os_log_t log;
|
||||
#if ASEnableLogs && ASLayoutLogEnabled
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
log = as_log_create("org.TextureGroup.Texture", "Layout");
|
||||
});
|
||||
#endif
|
||||
return log;
|
||||
}
|
||||
|
||||
os_log_t ASCollectionLog() {
|
||||
static os_log_t log;
|
||||
#if ASEnableLogs && ASCollectionLogEnabled
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
log = as_log_create("org.TextureGroup.Texture", "Collection");
|
||||
});
|
||||
#endif
|
||||
return log;
|
||||
}
|
||||
|
||||
os_log_t ASRenderLog() {
|
||||
static os_log_t log;
|
||||
#if ASEnableLogs && ASRenderLogEnabled
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
log = as_log_create("org.TextureGroup.Texture", "Render");
|
||||
});
|
||||
#endif
|
||||
return log;
|
||||
}
|
||||
|
||||
os_log_t ASImageLoadingLog() {
|
||||
static os_log_t log;
|
||||
#if ASEnableLogs && ASImageLoadingLogEnabled
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
log = as_log_create("org.TextureGroup.Texture", "ImageLoading");
|
||||
});
|
||||
#endif
|
||||
return log;
|
||||
}
|
||||
|
||||
os_log_t ASMainThreadDeallocationLog() {
|
||||
static os_log_t log;
|
||||
#if ASEnableLogs && ASMainThreadDeallocationLogEnabled
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
log = as_log_create("org.TextureGroup.Texture", "MainDealloc");
|
||||
});
|
||||
#endif
|
||||
return log;
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// ASSignpost.h
|
||||
// Texture
|
||||
//
|
||||
// Copyright (c) 2017-present, Pinterest, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
/// The signposts we use. Signposts are grouped by color. The SystemTrace.tracetemplate file
|
||||
/// should be kept up-to-date with these values.
|
||||
typedef NS_ENUM(uint32_t, ASSignpostName) {
|
||||
// Collection/Table (Blue)
|
||||
ASSignpostDataControllerBatch = 300, // Alloc/layout nodes before collection update.
|
||||
ASSignpostRangeControllerUpdate, // Ranges update pass.
|
||||
ASSignpostCollectionUpdate, // Entire update process, from -endUpdates to [super perform…]
|
||||
|
||||
// Rendering (Green)
|
||||
ASSignpostLayerDisplay = 325, // Client display callout.
|
||||
ASSignpostRunLoopQueueBatch, // One batch of ASRunLoopQueue.
|
||||
|
||||
// Layout (Purple)
|
||||
ASSignpostCalculateLayout = 350, // Start of calculateLayoutThatFits to end. Max 1 per thread.
|
||||
|
||||
// Misc (Orange)
|
||||
ASSignpostDeallocQueueDrain = 375, // One chunk of dealloc queue work. arg0 is count.
|
||||
ASSignpostCATransactionLayout, // The CA transaction commit layout phase.
|
||||
ASSignpostCATransactionCommit // The CA transaction commit post-layout phase.
|
||||
};
|
||||
|
||||
typedef NS_ENUM(uintptr_t, ASSignpostColor) {
|
||||
ASSignpostColorBlue,
|
||||
ASSignpostColorGreen,
|
||||
ASSignpostColorPurple,
|
||||
ASSignpostColorOrange,
|
||||
ASSignpostColorRed,
|
||||
ASSignpostColorDefault
|
||||
};
|
||||
|
||||
static inline ASSignpostColor ASSignpostGetColor(ASSignpostName name, ASSignpostColor colorPref) {
|
||||
if (colorPref == ASSignpostColorDefault) {
|
||||
return (ASSignpostColor)((name / 25) % 4);
|
||||
} else {
|
||||
return colorPref;
|
||||
}
|
||||
}
|
||||
|
||||
#define AS_KDEBUG_ENABLE defined(PROFILE) && __has_include(<sys/kdebug_signpost.h>)
|
||||
|
||||
#if AS_KDEBUG_ENABLE
|
||||
|
||||
#import <sys/kdebug_signpost.h>
|
||||
|
||||
// These definitions are required to build the backward-compatible kdebug trace
|
||||
// on the iOS 10 SDK. The kdebug_trace function crashes if run on iOS 9 and earlier.
|
||||
// It's valuable to support trace signposts on iOS 9, because A5 devices don't support iOS 10.
|
||||
#ifndef DBG_MACH_CHUD
|
||||
#define DBG_MACH_CHUD 0x0A
|
||||
#define DBG_FUNC_NONE 0
|
||||
#define DBG_FUNC_START 1
|
||||
#define DBG_FUNC_END 2
|
||||
#define DBG_APPS 33
|
||||
#define SYS_kdebug_trace 180
|
||||
#define KDBG_CODE(Class, SubClass, code) (((Class & 0xff) << 24) | ((SubClass & 0xff) << 16) | ((code & 0x3fff) << 2))
|
||||
#define APPSDBG_CODE(SubClass,code) KDBG_CODE(DBG_APPS, SubClass, code)
|
||||
#endif
|
||||
|
||||
// Currently we'll reserve arg3.
|
||||
#define ASSignpost(name, identifier, arg2, color) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_NONE, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color));
|
||||
|
||||
#define ASSignpostStartCustom(name, identifier, arg2) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost_start(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_START, (uintptr_t)identifier, (uintptr_t)arg2, 0, 0);
|
||||
#define ASSignpostStart(name) ASSignpostStartCustom(name, self, 0)
|
||||
|
||||
#define ASSignpostEndCustom(name, identifier, arg2, color) \
|
||||
AS_AT_LEAST_IOS10 ? kdebug_signpost_end(name, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color)) \
|
||||
: syscall(SYS_kdebug_trace, APPSDBG_CODE(DBG_MACH_CHUD, name) | DBG_FUNC_END, (uintptr_t)identifier, (uintptr_t)arg2, 0, ASSignpostGetColor(name, color));
|
||||
#define ASSignpostEnd(name) ASSignpostEndCustom(name, self, 0, ASSignpostColorDefault)
|
||||
|
||||
#else
|
||||
|
||||
#define ASSignpost(name, identifier, arg2, color)
|
||||
#define ASSignpostStartCustom(name, identifier, arg2)
|
||||
#define ASSignpostStart(name)
|
||||
#define ASSignpostEndCustom(name, identifier, arg2, color)
|
||||
#define ASSignpostEnd(name)
|
||||
|
||||
#endif
|
||||
@@ -17,6 +17,7 @@
|
||||
|
||||
#import <AsyncDisplayKit/ASBatchContext.h>
|
||||
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASThread.h>
|
||||
|
||||
typedef NS_ENUM(NSInteger, ASBatchContextState) {
|
||||
@@ -63,6 +64,7 @@ typedef NS_ENUM(NSInteger, ASBatchContextState) {
|
||||
- (void)completeBatchFetching:(BOOL)didComplete
|
||||
{
|
||||
if (didComplete) {
|
||||
as_log_debug(ASCollectionLog(), "Completed batch fetch with context %@", self);
|
||||
ASDN::MutexLocker l(__instanceLock__);
|
||||
_state = ASBatchContextStateCompleted;
|
||||
}
|
||||
|
||||
@@ -23,8 +23,11 @@
|
||||
#import <AsyncDisplayKit/ASCollectionElement.h>
|
||||
#import <AsyncDisplayKit/ASCollectionLayoutContext.h>
|
||||
#import <AsyncDisplayKit/ASDispatch.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASSignpost.h>
|
||||
#import <AsyncDisplayKit/ASMainSerialQueue.h>
|
||||
#import <AsyncDisplayKit/ASMutableElementMap.h>
|
||||
#import <AsyncDisplayKit/ASRangeManagingNode.h>
|
||||
@@ -167,7 +170,11 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
|
||||
for (NSUInteger i = 0; i < count; i += batchSize) {
|
||||
NSRange batchedRange = NSMakeRange(i, MIN(count - i, batchSize));
|
||||
NSArray<ASCollectionElement *> *batchedElements = [elements subarrayWithRange:batchedRange];
|
||||
NSArray<ASCellNode *> *nodes = [self _allocateNodesFromElements:batchedElements andLayout:shouldLayout];
|
||||
NSArray<ASCellNode *> *nodes;
|
||||
{
|
||||
as_activity_scope(as_activity_create("Data controller batch", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
nodes = [self _allocateNodesFromElements:batchedElements andLayout:shouldLayout];
|
||||
}
|
||||
batchCompletionHandler(batchedElements, nodes);
|
||||
}
|
||||
|
||||
@@ -491,7 +498,14 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
|
||||
ASDisplayNodeAssertMainThread();
|
||||
|
||||
if (changeSet.includesReloadData) {
|
||||
_initialReloadDataHasBeenCalled = YES;
|
||||
if (_initialReloadDataHasBeenCalled) {
|
||||
as_log_debug(ASCollectionLog(), "reloadData %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView)));
|
||||
} else {
|
||||
as_log_debug(ASCollectionLog(), "Initial reloadData %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView)));
|
||||
_initialReloadDataHasBeenCalled = YES;
|
||||
}
|
||||
} else {
|
||||
as_log_debug(ASCollectionLog(), "performBatchUpdates %@ %@", ASViewToDisplayNode(ASDynamicCast(self.dataSource, UIView)), changeSet);
|
||||
}
|
||||
|
||||
dispatch_group_wait(_editingTransactionGroup, DISPATCH_TIME_FOREVER);
|
||||
@@ -500,6 +514,7 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
|
||||
// See ASUICollectionViewTests.testThatIssuingAnUpdateBeforeInitialReloadIsUnacceptable
|
||||
// for the issue that UICollectionView has that we're choosing to workaround.
|
||||
if (!_initialReloadDataHasBeenCalled) {
|
||||
as_log_debug(ASCollectionLog(), "%@ Skipped update because load hasn't happened.", ASObjectDescriptionMakeTiny(_dataSource));
|
||||
[changeSet executeCompletionHandlerWithFinished:YES];
|
||||
return;
|
||||
}
|
||||
@@ -540,27 +555,36 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
|
||||
}
|
||||
|
||||
// Mutable copy of current data.
|
||||
ASElementMap *previousMap = _pendingMap;
|
||||
ASMutableElementMap *mutableMap = [previousMap mutableCopy];
|
||||
|
||||
BOOL canDelegateLayout = (_layoutDelegate != nil);
|
||||
BOOL canDelegateLayout;
|
||||
ASElementMap *newMap;
|
||||
id layoutContext;
|
||||
{
|
||||
as_activity_scope(as_activity_create("Latch new data for collection update", changeSet.rootActivity, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
ASElementMap *previousMap = _pendingMap;
|
||||
ASMutableElementMap *mutableMap = [previousMap mutableCopy];
|
||||
|
||||
// Step 1: Update the mutable copies to match the data source's state
|
||||
[self _updateSectionContextsInMap:mutableMap changeSet:changeSet];
|
||||
ASPrimitiveTraitCollection existingTraitCollection = [self.node primitiveTraitCollection];
|
||||
[self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegateLayout) previousMap:previousMap];
|
||||
|
||||
// Step 2: Clone the new data
|
||||
ASElementMap *newMap = [mutableMap copy];
|
||||
self.pendingMap = newMap;
|
||||
|
||||
// Step 3: Ask layout delegate for contexts
|
||||
id layoutContext = nil;
|
||||
if (canDelegateLayout) {
|
||||
layoutContext = [_layoutDelegate layoutContextWithElements:newMap];
|
||||
canDelegateLayout = (_layoutDelegate != nil);
|
||||
|
||||
// Step 1: Update the mutable copies to match the data source's state
|
||||
[self _updateSectionContextsInMap:mutableMap changeSet:changeSet];
|
||||
ASPrimitiveTraitCollection existingTraitCollection = [self.node primitiveTraitCollection];
|
||||
[self _updateElementsInMap:mutableMap changeSet:changeSet traitCollection:existingTraitCollection shouldFetchSizeRanges:(! canDelegateLayout) previousMap:previousMap];
|
||||
|
||||
// Step 2: Clone the new data
|
||||
newMap = [mutableMap copy];
|
||||
self.pendingMap = newMap;
|
||||
|
||||
// Step 3: Ask layout delegate for contexts
|
||||
if (canDelegateLayout) {
|
||||
layoutContext = [_layoutDelegate layoutContextWithElements:newMap];
|
||||
}
|
||||
}
|
||||
|
||||
as_log_debug(ASCollectionLog(), "New content: %@", newMap.smallDescription);
|
||||
|
||||
dispatch_group_async(_editingTransactionGroup, _editingTransactionQueue, ^{
|
||||
__block __unused os_activity_scope_state_s preparationScope = {}; // unused if deployment target < iOS10
|
||||
as_activity_scope_enter(as_activity_create("Prepare nodes for collection update", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT), &preparationScope);
|
||||
// Step 4: Allocate and layout elements if can't delegate
|
||||
NSArray<ASCollectionElement *> *elementsToProcess;
|
||||
if (canDelegateLayout) {
|
||||
@@ -581,8 +605,9 @@ typedef void (^ASDataControllerCompletionBlock)(NSArray<ASCollectionElement *> *
|
||||
if (canDelegateLayout) {
|
||||
[_layoutDelegate prepareLayoutWithContext:layoutContext];
|
||||
}
|
||||
|
||||
|
||||
[_mainSerialQueue performBlockOnMainThread:^{
|
||||
as_activity_scope_leave(&preparationScope);
|
||||
[_delegate dataController:self willUpdateWithChangeSet:changeSet];
|
||||
|
||||
// Step 5: Deploy the new data as "completed" and inform delegate
|
||||
|
||||
@@ -96,6 +96,11 @@ AS_SUBCLASSING_RESTRICTED
|
||||
*/
|
||||
- (nullable ASCollectionElement *)elementForLayoutAttributes:(UICollectionViewLayoutAttributes *)layoutAttributes;
|
||||
|
||||
/**
|
||||
* A very terse description e.g. { itemCounts = [ <S0: 1> <S1: 16> ] }
|
||||
*/
|
||||
@property (atomic, readonly) NSString *smallDescription;
|
||||
|
||||
#pragma mark - Initialization -- Only Useful to ASDataController
|
||||
|
||||
|
||||
|
||||
@@ -183,6 +183,18 @@
|
||||
return [_elementToIndexPathMap countByEnumeratingWithState:state objects:buffer count:len];
|
||||
}
|
||||
|
||||
- (NSString *)smallDescription
|
||||
{
|
||||
NSMutableArray *sectionDescriptions = [NSMutableArray array];
|
||||
|
||||
NSUInteger i = 0;
|
||||
for (NSArray *section in _sectionsOfItems) {
|
||||
[sectionDescriptions addObject:[NSString stringWithFormat:@"<S%tu: %tu>", i, section.count]];
|
||||
i++;
|
||||
}
|
||||
return ASObjectDescriptionMakeWithoutObject(@[ @{ @"itemCounts": sectionDescriptions }]);
|
||||
}
|
||||
|
||||
#pragma mark - ASDescriptionProvider
|
||||
|
||||
- (NSString *)description
|
||||
|
||||
@@ -47,6 +47,9 @@ NSString *ASGetDescriptionValueString(id object)
|
||||
[strings addObject:[NSString stringWithFormat:@"%lu", (unsigned long)[indexPath indexAtPosition:i]]];
|
||||
}
|
||||
return [NSString stringWithFormat:@"(%@)", [strings componentsJoinedByString:@", "]];
|
||||
} else if ([object respondsToSelector:@selector(componentsJoinedByString:)]) {
|
||||
// e.g. "[ <MYObject: 0x00000000> <MYObject: 0xFFFFFFFF> ]"
|
||||
return [NSString stringWithFormat:@"[ %@ ]", [object componentsJoinedByString:@" "]];
|
||||
}
|
||||
return [object description];
|
||||
}
|
||||
@@ -56,7 +59,13 @@ NSString *_ASObjectDescriptionMakePropertyList(NSArray<NSDictionary *> * _Nullab
|
||||
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)]];
|
||||
NSString *str;
|
||||
if (key == (id)kCFNull) {
|
||||
str = ASGetDescriptionValueString(obj);
|
||||
} else {
|
||||
str = [NSString stringWithFormat:@"%@ = %@", key, ASGetDescriptionValueString(obj)];
|
||||
}
|
||||
[components addObject:str];
|
||||
}];
|
||||
}
|
||||
return [components componentsJoinedByString:@"; "];
|
||||
@@ -73,7 +82,7 @@ NSString *ASObjectDescriptionMake(__autoreleasing id object, NSArray<NSDictionar
|
||||
return @"(null)";
|
||||
}
|
||||
|
||||
NSMutableString *str = [NSMutableString stringWithFormat:@"<%@: %p", [object class], object];
|
||||
NSMutableString *str = [NSMutableString stringWithFormat:@"<%s: %p", object_getClassName(object), object];
|
||||
|
||||
NSString *propList = _ASObjectDescriptionMakePropertyList(propertyGroups);
|
||||
if (propList.length > 0) {
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h> // Required for interfaceState and hierarchyState setter methods.
|
||||
#import <AsyncDisplayKit/ASElementMap.h>
|
||||
#import <AsyncDisplayKit/ASInternalHelpers.h>
|
||||
#import <AsyncDisplayKit/ASSignpost.h>
|
||||
#import <AsyncDisplayKit/ASTwoDimensionalArrayUtils.h>
|
||||
#import <AsyncDisplayKit/ASWeakSet.h>
|
||||
|
||||
@@ -203,6 +204,8 @@ static UIApplicationState __ApplicationState = UIApplicationStateActive;
|
||||
|
||||
- (void)_updateVisibleNodeIndexPaths
|
||||
{
|
||||
as_activity_scope_verbose(as_activity_create("Update range controller", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_verbose(ASCollectionLog(), "Updating ranges for %@", ASViewToDisplayNode(ASDynamicCast(self.delegate, UIView)));
|
||||
ASDisplayNodeAssert(_layoutController, @"An ASLayoutController is required by ASRangeController");
|
||||
if (!_layoutController || !_dataSource) {
|
||||
return;
|
||||
|
||||
@@ -198,7 +198,7 @@
|
||||
NSMutableString *description = [[super description] mutableCopy];
|
||||
ASDisplayNode *node = self.asyncdisplaykit_node;
|
||||
if (node != nil) {
|
||||
NSString *classString = [NSString stringWithFormat:@"%@-", [node class]];
|
||||
NSString *classString = [NSString stringWithFormat:@"%s-", object_getClassName(node)];
|
||||
[description replaceOccurrencesOfString:@"_ASDisplay" withString:classString options:kNilOptions range:NSMakeRange(0, description.length)];
|
||||
NSUInteger insertionIndex = [description rangeOfString:@">"].location;
|
||||
if (insertionIndex != NSNotFound) {
|
||||
|
||||
@@ -58,7 +58,7 @@
|
||||
ASDisplayNode *node = _asyncdisplaykit_node;
|
||||
|
||||
if (node != nil) {
|
||||
NSString *classString = [NSString stringWithFormat:@"%@-", [node class]];
|
||||
NSString *classString = [NSString stringWithFormat:@"%s-", object_getClassName(node)];
|
||||
[description replaceOccurrencesOfString:@"_ASDisplay" withString:classString options:kNilOptions range:NSMakeRange(0, description.length)];
|
||||
NSUInteger semicolon = [description rangeOfString:@";"].location;
|
||||
if (semicolon != NSNotFound) {
|
||||
|
||||
@@ -107,9 +107,17 @@ ASSizeRange ASSizeRangeIntersect(ASSizeRange sizeRange, ASSizeRange otherSizeRan
|
||||
|
||||
NSString *NSStringFromASSizeRange(ASSizeRange sizeRange)
|
||||
{
|
||||
return [NSString stringWithFormat:@"<ASSizeRange: min=%@, max=%@>",
|
||||
NSStringFromCGSize(sizeRange.min),
|
||||
NSStringFromCGSize(sizeRange.max)];
|
||||
// 17 field length copied from iOS 10.3 impl of NSStringFromCGSize.
|
||||
if (CGSizeEqualToSize(sizeRange.min, sizeRange.max)) {
|
||||
return [NSString stringWithFormat:@"{{%.*g, %.*g}}",
|
||||
17, sizeRange.min.width,
|
||||
17, sizeRange.min.height];
|
||||
}
|
||||
return [NSString stringWithFormat:@"{{%.*g, %.*g}, {%.*g, %.*g}}",
|
||||
17, sizeRange.min.width,
|
||||
17, sizeRange.min.height,
|
||||
17, sizeRange.max.width,
|
||||
17, sizeRange.max.height];
|
||||
}
|
||||
|
||||
#if YOGA
|
||||
|
||||
@@ -66,9 +66,9 @@ ASDISPLAYNODE_EXTERN_C_END
|
||||
@property (nonatomic, assign, readonly) CGSize size;
|
||||
|
||||
/**
|
||||
* Position in parent. Default to CGPointNull.
|
||||
* Position in parent. Default to ASPointNull.
|
||||
*
|
||||
* @discussion When being used as a sublayout, this property must not equal CGPointNull.
|
||||
* @discussion When being used as a sublayout, this property must not equal ASPointNull.
|
||||
*/
|
||||
@property (nonatomic, assign, readonly) CGPoint position;
|
||||
|
||||
|
||||
@@ -287,9 +287,16 @@ static std::atomic_bool static_retainsSublayoutLayoutElements = ATOMIC_VAR_INIT(
|
||||
- (NSMutableArray <NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
NSMutableArray *result = [NSMutableArray array];
|
||||
[result addObject:@{ @"layoutElement" : (self.layoutElement ?: (id)kCFNull) }];
|
||||
[result addObject:@{ @"position" : [NSValue valueWithCGPoint:self.position] }];
|
||||
[result addObject:@{ @"size" : [NSValue valueWithCGSize:self.size] }];
|
||||
|
||||
if (auto layoutElement = self.layoutElement) {
|
||||
[result addObject:@{ @"layoutElement" : layoutElement }];
|
||||
}
|
||||
|
||||
auto pos = self.position;
|
||||
if (!ASPointIsNull(pos)) {
|
||||
[result addObject:@{ @"position" : [NSValue valueWithCGPoint:pos] }];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#import <AsyncDisplayKit/ASStackLayoutElement.h>
|
||||
#import <AsyncDisplayKit/ASAbsoluteLayoutElement.h>
|
||||
#import <AsyncDisplayKit/ASTraitCollection.h>
|
||||
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
|
||||
|
||||
@class ASLayout;
|
||||
@class ASLayoutSpec;
|
||||
@@ -60,7 +61,7 @@ typedef NS_ENUM(NSUInteger, ASLayoutElementType) {
|
||||
* access to the options via convenience properties. If you are creating custom layout spec, then you can
|
||||
* extend the backing layout options class to accommodate any new layout options.
|
||||
*/
|
||||
@protocol ASLayoutElement <ASLayoutElementExtensibility, ASTraitEnvironment>
|
||||
@protocol ASLayoutElement <ASLayoutElementExtensibility, ASTraitEnvironment, ASLayoutElementAsciiArtProtocol>
|
||||
|
||||
#pragma mark - Getter
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
/**
|
||||
* A layout spec is an immutable object that describes a layout, loosely inspired by React.
|
||||
*/
|
||||
@interface ASLayoutSpec : NSObject <ASLayoutElement, ASLayoutElementStylability, NSFastEnumeration>
|
||||
@interface ASLayoutSpec : NSObject <ASLayoutElement, ASLayoutElementStylability, NSFastEnumeration, ASDescriptionProvider>
|
||||
|
||||
/**
|
||||
* Creation of a layout spec should only happen by a user in layoutSpecThatFits:. During that method, a
|
||||
@@ -95,7 +95,7 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@end
|
||||
|
||||
@interface ASLayoutSpec (Debugging) <ASLayoutElementAsciiArtProtocol, ASDebugNameProvider>
|
||||
@interface ASLayoutSpec (Debugging) <ASDebugNameProvider>
|
||||
/**
|
||||
* Used by other layout specs to create ascii art debug strings
|
||||
*/
|
||||
|
||||
@@ -173,6 +173,24 @@ ASPrimitiveTraitCollectionDeprecatedImplementation
|
||||
|
||||
ASLayoutElementStyleExtensibilityForwarding
|
||||
|
||||
#pragma mark - ASDescriptionProvider
|
||||
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
auto result = [NSMutableArray<NSDictionary *> array];
|
||||
if (NSArray *children = self.children) {
|
||||
// Use tiny descriptions because these trees can get nested very deep.
|
||||
auto tinyDescriptions = ASArrayByFlatMapping(children, id object, ASObjectDescriptionMakeTiny(object));
|
||||
[result addObject:@{ @"children": tinyDescriptions }];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return ASObjectDescriptionMake(self, [self propertiesForDescription]);
|
||||
}
|
||||
|
||||
#pragma mark - Framework Private
|
||||
|
||||
#if AS_DEDUPE_LAYOUT_SPEC_TREE
|
||||
@@ -236,6 +254,23 @@ ASLayoutElementStyleExtensibilityForwarding
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark - ASLayoutElementAsciiArtProtocol
|
||||
|
||||
- (NSString *)asciiArtString
|
||||
{
|
||||
NSArray *children = self.children.count < 2 && self.child ? @[self.child] : self.children;
|
||||
return [ASLayoutSpec asciiArtStringForChildren:children parentName:[self asciiArtName]];
|
||||
}
|
||||
|
||||
- (NSString *)asciiArtName
|
||||
{
|
||||
NSMutableString *result = [NSMutableString stringWithCString:object_getClassName(self) encoding:NSASCIIStringEncoding];
|
||||
if (_debugName) {
|
||||
[result appendFormat:@" (%@)", _debugName];
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASWrapperLayoutSpec
|
||||
@@ -295,7 +330,7 @@ ASLayoutElementStyleExtensibilityForwarding
|
||||
|
||||
@implementation ASLayoutSpec (Debugging)
|
||||
|
||||
#pragma mark - ASLayoutElementAsciiArtProtocol
|
||||
#pragma mark - ASCII Art Helpers
|
||||
|
||||
+ (NSString *)asciiArtStringForChildren:(NSArray *)children parentName:(NSString *)parentName direction:(ASStackLayoutDirection)direction
|
||||
{
|
||||
@@ -317,21 +352,6 @@ ASLayoutElementStyleExtensibilityForwarding
|
||||
return [self asciiArtStringForChildren:children parentName:parentName direction:ASStackLayoutDirectionHorizontal];
|
||||
}
|
||||
|
||||
- (NSString *)asciiArtString
|
||||
{
|
||||
NSArray *children = self.children.count < 2 && self.child ? @[self.child] : self.children;
|
||||
return [ASLayoutSpec asciiArtStringForChildren:children parentName:[self asciiArtName]];
|
||||
}
|
||||
|
||||
- (NSString *)asciiArtName
|
||||
{
|
||||
NSString *string = NSStringFromClass([self class]);
|
||||
if (_debugName) {
|
||||
string = [string stringByAppendingString:[NSString stringWithFormat:@" (debugName = %@)",_debugName]];
|
||||
}
|
||||
return string;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
#pragma mark - ASLayoutSpec (Deprecated)
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
#import <AsyncDisplayKit/ASLayoutElement.h>
|
||||
#import <AsyncDisplayKit/ASLayoutElementStylePrivate.h>
|
||||
#import <AsyncDisplayKit/ASLayoutSpecUtilities.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
#import <AsyncDisplayKit/ASStackPositionedLayout.h>
|
||||
#import <AsyncDisplayKit/ASStackUnpositionedLayout.h>
|
||||
|
||||
@@ -134,6 +135,8 @@
|
||||
return [ASLayout layoutWithLayoutElement:self size:constrainedSize.min];
|
||||
}
|
||||
|
||||
as_activity_scope_verbose(as_activity_create("Calculate stack layout", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
as_log_verbose(ASLayoutLog(), "Stack layout %@", self);
|
||||
// Accessing the style and size property is pretty costly we create layout spec children we use to figure
|
||||
// out the layout for each child
|
||||
const auto stackChildren = AS::map(children, [&](const id<ASLayoutElement> child) -> ASStackLayoutSpecChild {
|
||||
@@ -177,6 +180,23 @@
|
||||
}
|
||||
}
|
||||
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
auto result = [super propertiesForDescription];
|
||||
|
||||
// Add our direction
|
||||
switch (self.direction) {
|
||||
case ASStackLayoutDirectionVertical:
|
||||
[result insertObject:@{ (id)kCFNull: @"vertical" } atIndex:0];
|
||||
break;
|
||||
case ASStackLayoutDirectionHorizontal:
|
||||
[result insertObject:@{ (id)kCFNull: @"horizontal" } atIndex:0];
|
||||
break;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ASStackLayoutSpec (Debugging)
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import <AsyncDisplayKit/ASLayout.h>
|
||||
#import <AsyncDisplayKit/ASDisplayNodeInternal.h> // Required for _insertSubnode... / _removeFromSupernode.
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
#import <queue>
|
||||
#import <memory>
|
||||
@@ -106,6 +107,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
|
||||
ASDN::MutexSharedLocker l(__instanceLock__);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
|
||||
// Create an activity even if no subnodes affected.
|
||||
as_activity_scope(as_activity_create("Apply subnode insertions", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
if (_insertedSubnodes.count == 0) {
|
||||
return;
|
||||
}
|
||||
@@ -121,6 +124,7 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
|
||||
|
||||
- (void)applySubnodeRemovals
|
||||
{
|
||||
as_activity_scope(as_activity_create("Apply subnode removals", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
ASDN::MutexSharedLocker l(__instanceLock__);
|
||||
[self calculateSubnodeOperationsIfNeeded];
|
||||
|
||||
@@ -144,6 +148,8 @@ static inline BOOL ASLayoutCanTransitionAsynchronous(ASLayout *layout) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create an activity even if no subnodes affected.
|
||||
as_activity_scope(as_activity_create("Calculate subnode operations", AS_ACTIVITY_CURRENT, OS_ACTIVITY_FLAG_DEFAULT));
|
||||
ASLayout *previousLayout = _previousLayout->layout;
|
||||
ASLayout *pendingLayout = _pendingLayout->layout;
|
||||
|
||||
|
||||
@@ -25,7 +25,7 @@ static NSUInteger ASRectSize(const void *ptr)
|
||||
|
||||
@implementation NSMapTable (ASRectTableMethods)
|
||||
|
||||
+ (instancetype)rectTableWithKeyPointerFunctions:(NSPointerFunctions *)keyFuncs
|
||||
+ (NSMapTable *)rectTableWithKeyPointerFunctions:(NSPointerFunctions *)keyFuncs
|
||||
{
|
||||
static NSPointerFunctions *cgRectFuncs;
|
||||
static dispatch_once_t onceToken;
|
||||
@@ -37,7 +37,7 @@ static NSUInteger ASRectSize(const void *ptr)
|
||||
return [[NSMapTable alloc] initWithKeyPointerFunctions:keyFuncs valuePointerFunctions:cgRectFuncs capacity:0];
|
||||
}
|
||||
|
||||
+ (instancetype)rectTableForStrongObjectPointers
|
||||
+ (NSMapTable *)rectTableForStrongObjectPointers
|
||||
{
|
||||
static NSPointerFunctions *strongObjectPointerFuncs;
|
||||
static dispatch_once_t onceToken;
|
||||
@@ -47,7 +47,7 @@ static NSUInteger ASRectSize(const void *ptr)
|
||||
return [self rectTableWithKeyPointerFunctions:strongObjectPointerFuncs];
|
||||
}
|
||||
|
||||
+ (instancetype)rectTableForWeakObjectPointers
|
||||
+ (NSMapTable *)rectTableForWeakObjectPointers
|
||||
{
|
||||
static NSPointerFunctions *weakObjectPointerFuncs;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <vector>
|
||||
#import <AsyncDisplayKit/ASObjectDescriptionHelpers.h>
|
||||
#import <AsyncDisplayKit/ASLog.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@@ -119,6 +120,12 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType);
|
||||
/// Indicates whether the change set is empty, that is it includes neither reload data nor per item or section changes.
|
||||
@property (nonatomic, readonly) BOOL isEmpty;
|
||||
|
||||
/// The top-level activity for this update.
|
||||
@property (nonatomic, OS_ACTIVITY_NULLABLE) os_activity_t rootActivity;
|
||||
|
||||
/// The activity for submitting this update i.e. between -beginUpdates and -endUpdates.
|
||||
@property (nonatomic, OS_ACTIVITY_NULLABLE) os_activity_t submitActivity;
|
||||
|
||||
- (instancetype)initWithOldData:(std::vector<NSInteger>)oldItemCounts NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
/**
|
||||
|
||||
@@ -601,7 +601,7 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType)
|
||||
|
||||
- (NSString *)description
|
||||
{
|
||||
return ASObjectDescriptionMake(self, [self propertiesForDescription]);
|
||||
return ASObjectDescriptionMakeWithoutObject([self propertiesForDescription]);
|
||||
}
|
||||
|
||||
- (NSString *)debugDescription
|
||||
@@ -612,7 +612,9 @@ NSString *NSStringFromASHierarchyChangeType(_ASHierarchyChangeType changeType)
|
||||
- (NSMutableArray<NSDictionary *> *)propertiesForDescription
|
||||
{
|
||||
NSMutableArray<NSDictionary *> *result = [NSMutableArray array];
|
||||
[result addObject:@{ @"includesReloadData" : @(_includesReloadData) }];
|
||||
if (_includesReloadData) {
|
||||
[result addObject:@{ @"reloadData" : @"YES" }];
|
||||
}
|
||||
if (_reloadSectionChanges.count > 0) {
|
||||
[result addObject:@{ @"reloadSections" : [_ASHierarchySectionChange smallDescriptionForSectionChanges:_reloadSectionChanges] }];
|
||||
}
|
||||
|
||||
@@ -1880,10 +1880,8 @@ static bool stringContainsPointer(NSString *description, id p) {
|
||||
node.debugName = @"big troll eater name";
|
||||
|
||||
XCTAssertTrue([node.description containsString:node.debugName], @"debugName didn't end up in description");
|
||||
XCTAssertTrue([node.description containsString:@"debugName"], @"Node description should contain `debugName`.");
|
||||
[node layer];
|
||||
XCTAssertTrue([node.description containsString:node.debugName], @"debugName didn't end up in description");
|
||||
XCTAssertTrue([node.description containsString:@"debugName"], @"Node description should contain `debugName`.");
|
||||
}
|
||||
|
||||
- (void)testNameInDescriptionLayer
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
#import "ItemNode.h"
|
||||
#import "ItemStyles.h"
|
||||
#import "PlaceholderNetworkImageNode.h"
|
||||
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
|
||||
|
||||
const CGFloat kFixedLabelsAreaHeight = 96.0;
|
||||
const CGFloat kDesignWidth = 320.0;
|
||||
@@ -45,6 +46,7 @@ const CGFloat kSoldOutGBHeight = 50.0;
|
||||
@end
|
||||
|
||||
@implementation ItemNode
|
||||
@dynamic viewModel;
|
||||
|
||||
- (instancetype)initWithViewModel:(ItemViewModel *)viewModel
|
||||
{
|
||||
@@ -55,6 +57,8 @@ const CGFloat kSoldOutGBHeight = 50.0;
|
||||
[self updateLabels];
|
||||
[self updateBackgroundColor];
|
||||
|
||||
ASSetDebugName(self, @"Item #%zd", viewModel.identifier);
|
||||
self.accessibilityIdentifier = viewModel.titleText;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,18 @@
|
||||
//
|
||||
// ItemViewModel.h
|
||||
// Sample
|
||||
//
|
||||
// Created by Samuel Stow on 12/29/15.
|
||||
// Texture
|
||||
//
|
||||
// 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.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
@@ -22,8 +20,9 @@
|
||||
|
||||
@interface ItemViewModel : NSObject
|
||||
|
||||
+ (instancetype)randomItem;
|
||||
+ (ItemViewModel *)randomItem;
|
||||
|
||||
@property (nonatomic, readonly) NSInteger identifier;
|
||||
@property (nonatomic, copy) NSString *titleText;
|
||||
@property (nonatomic, copy) NSString *firstInfoText;
|
||||
@property (nonatomic, copy) NSString *secondInfoText;
|
||||
|
||||
@@ -1,23 +1,22 @@
|
||||
//
|
||||
// ItemViewModel.m
|
||||
// Sample
|
||||
//
|
||||
// Created by Samuel Stow on 12/29/15.
|
||||
// Texture
|
||||
//
|
||||
// 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.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
||||
// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
||||
// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
||||
// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
||||
// you may not use this file except in compliance with the License.
|
||||
// You may obtain a copy of the License at
|
||||
//
|
||||
// http://www.apache.org/licenses/LICENSE-2.0
|
||||
//
|
||||
|
||||
#import "ItemViewModel.h"
|
||||
#import <stdatomic.h>
|
||||
|
||||
NSArray *titles;
|
||||
NSArray *firstInfos;
|
||||
@@ -32,28 +31,29 @@ NSArray *badges;
|
||||
|
||||
@implementation ItemViewModel
|
||||
|
||||
+ (instancetype)randomItem {
|
||||
+ (ItemViewModel *)randomItem {
|
||||
return [[ItemViewModel alloc] init];
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_titleText = [self randomObjectFromArray:titles];
|
||||
_firstInfoText = [self randomObjectFromArray:firstInfos];
|
||||
_secondInfoText = [NSString stringWithFormat:@"%zd+ bought", [self randomNumberInRange:5 to:6000]];
|
||||
_originalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:40 to:90]];
|
||||
_finalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:5 to:30]];
|
||||
BOOL isSoldOut = arc4random() % 5 == 0;
|
||||
_soldOutText = isSoldOut ? @"SOLD OUT" : nil;
|
||||
_distanceLabelText = [NSString stringWithFormat:@"%zd mi", [self randomNumberInRange:1 to:20]];
|
||||
BOOL isBadged = arc4random() % 2 == 0;
|
||||
if (isBadged) {
|
||||
_badgeText = [self randomObjectFromArray:badges];
|
||||
}
|
||||
_catNumber = [self randomNumberInRange:1 to:10];
|
||||
_labelNumber = [self randomNumberInRange:1 to:10000];
|
||||
|
||||
static _Atomic(NSInteger) nextID = ATOMIC_VAR_INIT(1);
|
||||
_identifier = atomic_fetch_add(&nextID, 1);
|
||||
_titleText = [self randomObjectFromArray:titles];
|
||||
_firstInfoText = [self randomObjectFromArray:firstInfos];
|
||||
_secondInfoText = [NSString stringWithFormat:@"%zd+ bought", [self randomNumberInRange:5 to:6000]];
|
||||
_originalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:40 to:90]];
|
||||
_finalPriceText = [NSString stringWithFormat:@"$%zd", [self randomNumberInRange:5 to:30]];
|
||||
BOOL isSoldOut = arc4random() % 5 == 0;
|
||||
_soldOutText = isSoldOut ? @"SOLD OUT" : nil;
|
||||
_distanceLabelText = [NSString stringWithFormat:@"%zd mi", [self randomNumberInRange:1 to:20]];
|
||||
BOOL isBadged = arc4random() % 2 == 0;
|
||||
if (isBadged) {
|
||||
_badgeText = [self randomObjectFromArray:badges];
|
||||
}
|
||||
_catNumber = [self randomNumberInRange:1 to:10];
|
||||
_labelNumber = [self randomNumberInRange:1 to:10000];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
//
|
||||
// ViewController.m
|
||||
// Sample
|
||||
// Texture
|
||||
//
|
||||
// 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.
|
||||
// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional
|
||||
// grant of patent rights can be found in the PATENTS file in the same directory.
|
||||
//
|
||||
// Modifications to this file made after 4/13/2017 are: Copyright (c) 2017-present,
|
||||
// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@@ -56,6 +56,7 @@ static const CGFloat kHorizontalSectionPadding = 10.0f;
|
||||
_collectionNode.dataSource = self;
|
||||
_collectionNode.delegate = self;
|
||||
_collectionNode.backgroundColor = [UIColor grayColor];
|
||||
_collectionNode.accessibilityIdentifier = @"Cat deals list";
|
||||
|
||||
ASRangeTuningParameters preloadTuning;
|
||||
preloadTuning.leadingBufferScreenfuls = 2;
|
||||
@@ -96,12 +97,10 @@ static const CGFloat kHorizontalSectionPadding = 10.0f;
|
||||
ViewController *strongSelf = weakSelf;
|
||||
if (strongSelf != nil)
|
||||
{
|
||||
NSLog(@"ViewController is not nil");
|
||||
[strongSelf appendMoreItems:kBatchSize completion:completion];
|
||||
NSLog(@"ViewController finished updating collectionView");
|
||||
}
|
||||
else {
|
||||
NSLog(@"ViewController is nil - won't update collectionView");
|
||||
NSLog(@"ViewController is nil - won't update collection");
|
||||
}
|
||||
};
|
||||
|
||||
@@ -113,13 +112,11 @@ static const CGFloat kHorizontalSectionPadding = 10.0f;
|
||||
|
||||
- (void)appendMoreItems:(NSInteger)numberOfNewItems completion:(void (^)(BOOL))completion {
|
||||
NSArray *newData = [self getMoreData:numberOfNewItems];
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[_collectionNode performBatchAnimated:YES updates:^{
|
||||
[_data addObjectsFromArray:newData];
|
||||
NSArray *addedIndexPaths = [self indexPathsForObjects:newData];
|
||||
[_collectionNode insertItemsAtIndexPaths:addedIndexPaths];
|
||||
} completion:completion];
|
||||
});
|
||||
[_collectionNode performBatchAnimated:YES updates:^{
|
||||
[_data addObjectsFromArray:newData];
|
||||
NSArray *addedIndexPaths = [self indexPathsForObjects:newData];
|
||||
[_collectionNode insertItemsAtIndexPaths:addedIndexPaths];
|
||||
} completion:completion];
|
||||
}
|
||||
|
||||
- (NSArray *)getMoreData:(NSInteger)count {
|
||||
@@ -196,7 +193,6 @@ static const CGFloat kHorizontalSectionPadding = 10.0f;
|
||||
|
||||
- (void)collectionNode:(ASCollectionNode *)collectionNode willBeginBatchFetchWithContext:(ASBatchContext *)context
|
||||
{
|
||||
NSLog(@"fetch additional content");
|
||||
[self fetchMoreCatsWithCompletion:^(BOOL finished){
|
||||
[context completeBatchFetching:YES];
|
||||
}];
|
||||
|
||||
Reference in New Issue
Block a user