Let nodes deallocate naturally, manually trampoline UIKit ivars

Be more aggressive with main thread punting

Trampoline setting the dataSource/delegate onto the main thread

Short-circuit the supplementary nodes method if no data source

Don't rely on assertions

Mark variable unused to fix release builds

Handle ASCollectionNode/ASTableNode deallocation better

Add some comments about new macro
This commit is contained in:
Adlai Holler
2016-09-09 11:09:57 -07:00
parent 1b0b9e4830
commit 6d01bbeb19
19 changed files with 394 additions and 651 deletions

View File

@@ -64,7 +64,6 @@
058D0A3D195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A33195D057000B7D73C /* ASTextKitCoreTextAdditionsTests.m */; };
058D0A40195D057000B7D73C /* ASTextNodeTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A36195D057000B7D73C /* ASTextNodeTests.m */; };
058D0A41195D057000B7D73C /* ASTextNodeWordKernerTests.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */; };
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
05EA6FE71AC0966E00E35788 /* ASSnapshotTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */; };
18C2ED7F1B9B7DE800F627B3 /* ASCollectionNode.h in Headers */ = {isa = PBXBuildFile; fileRef = 18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */; settings = {ATTRIBUTES = (Public, ); }; };
18C2ED801B9B7DE800F627B3 /* ASCollectionNode.mm in Sources */ = {isa = PBXBuildFile; fileRef = 18C2ED7D1B9B7DE800F627B3 /* ASCollectionNode.mm */; };
@@ -388,8 +387,6 @@
B35062161B010EFD0018CF92 /* ASBatchContext.mm in Sources */ = {isa = PBXBuildFile; fileRef = 299DA1A81A828D2900162D41 /* ASBatchContext.mm */; };
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */ = {isa = PBXBuildFile; fileRef = 464052191A3F83C40061C0BA /* ASDataController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521A1A3F83C40061C0BA /* ASDataController.mm */; };
B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */; settings = {COMPILER_FLAGS = "-fno-objc-arc"; }; };
B350621B1B010EFD0018CF92 /* ASFlowLayoutController.h in Headers */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; settings = {ATTRIBUTES = (Public, ); }; };
B350621C1B010EFD0018CF92 /* ASFlowLayoutController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 4640521C1A3F83C40061C0BA /* ASFlowLayoutController.mm */; };
B350621D1B010EFD0018CF92 /* ASHighlightOverlayLayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -414,7 +411,6 @@
B35062421B010EFD0018CF92 /* _ASAsyncTransactionGroup.m in Sources */ = {isa = PBXBuildFile; fileRef = 058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */; };
B35062431B010EFD0018CF92 /* UIView+ASConvenience.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D09FF195D050800B7D73C /* UIView+ASConvenience.h */; settings = {ATTRIBUTES = (Public, ); }; };
B35062461B010EFD0018CF92 /* ASBasicImageDownloaderInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = 2967F9E11AB0A4CF0072E4AB /* ASBasicImageDownloaderInternal.h */; };
B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A02195D050800B7D73C /* _AS-objc-internal.h */; };
B35062491B010EFD0018CF92 /* _ASCoreAnimationExtras.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */; };
B350624A1B010EFD0018CF92 /* _ASCoreAnimationExtras.mm in Sources */ = {isa = PBXBuildFile; fileRef = 058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */; };
B350624B1B010EFD0018CF92 /* _ASPendingState.h in Headers */ = {isa = PBXBuildFile; fileRef = 058D0A05195D050800B7D73C /* _ASPendingState.h */; };
@@ -551,7 +547,6 @@
F7CE6C3A1D2CDB3E00BE4C15 /* ASBatchContext.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 299DA1A71A828D2900162D41 /* ASBatchContext.h */; };
F7CE6C3B1D2CDB3E00BE4C15 /* ASCollectionViewFlowLayoutInspector.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 251B8EF41BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.h */; };
F7CE6C3D1D2CDB3E00BE4C15 /* ASCollectionViewLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */; };
F7CE6C3E1D2CDB3E00BE4C15 /* ASDealloc2MainObject.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */; };
F7CE6C3F1D2CDB3E00BE4C15 /* ASEnvironment.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 698548611CA9E025008A345F /* ASEnvironment.h */; };
F7CE6C401D2CDB3E00BE4C15 /* ASFlowLayoutController.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */; };
F7CE6C411D2CDB3E00BE4C15 /* ASHighlightOverlayLayer.h in CopyFiles */ = {isa = PBXBuildFile; fileRef = 058D09E6195D050800B7D73C /* ASHighlightOverlayLayer.h */; };
@@ -729,7 +724,6 @@
F7CE6C3A1D2CDB3E00BE4C15 /* ASBatchContext.h in CopyFiles */,
F7CE6C3B1D2CDB3E00BE4C15 /* ASCollectionViewFlowLayoutInspector.h in CopyFiles */,
F7CE6C3D1D2CDB3E00BE4C15 /* ASCollectionViewLayoutController.h in CopyFiles */,
F7CE6C3E1D2CDB3E00BE4C15 /* ASDealloc2MainObject.h in CopyFiles */,
F7CE6C3F1D2CDB3E00BE4C15 /* ASEnvironment.h in CopyFiles */,
F7CE6C401D2CDB3E00BE4C15 /* ASFlowLayoutController.h in CopyFiles */,
F7CE6C411D2CDB3E00BE4C15 /* ASHighlightOverlayLayer.h in CopyFiles */,
@@ -896,7 +890,6 @@
058D09FD195D050800B7D73C /* _ASAsyncTransactionGroup.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASAsyncTransactionGroup.h; sourceTree = "<group>"; };
058D09FE195D050800B7D73C /* _ASAsyncTransactionGroup.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = _ASAsyncTransactionGroup.m; sourceTree = "<group>"; };
058D09FF195D050800B7D73C /* UIView+ASConvenience.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "UIView+ASConvenience.h"; sourceTree = "<group>"; };
058D0A02195D050800B7D73C /* _AS-objc-internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "_AS-objc-internal.h"; sourceTree = "<group>"; };
058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASCoreAnimationExtras.h; sourceTree = "<group>"; };
058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = _ASCoreAnimationExtras.mm; sourceTree = "<group>"; };
058D0A05195D050800B7D73C /* _ASPendingState.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = _ASPendingState.h; sourceTree = "<group>"; };
@@ -921,8 +914,6 @@
058D0A37195D057000B7D73C /* ASTextNodeWordKernerTests.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; path = ASTextNodeWordKernerTests.mm; sourceTree = "<group>"; };
058D0A43195D058D00B7D73C /* ASAssert.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASAssert.h; sourceTree = "<group>"; };
058D0A44195D058D00B7D73C /* ASBaseDefines.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASBaseDefines.h; sourceTree = "<group>"; };
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ASDealloc2MainObject.h; path = ../Details/ASDealloc2MainObject.h; sourceTree = "<group>"; };
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = ASDealloc2MainObject.m; path = ../Details/ASDealloc2MainObject.m; sourceTree = "<group>"; };
05EA6FE61AC0966E00E35788 /* ASSnapshotTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASSnapshotTestCase.m; sourceTree = "<group>"; };
05F20AA31A15733C00DCA68A /* ASImageProtocols.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASImageProtocols.h; sourceTree = "<group>"; };
18C2ED7C1B9B7DE800F627B3 /* ASCollectionNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASCollectionNode.h; sourceTree = "<group>"; };
@@ -1508,8 +1499,6 @@
251B8EF51BBB3D690087C538 /* ASCollectionViewFlowLayoutInspector.m */,
205F0E1B1B373A2C007741D0 /* ASCollectionViewLayoutController.h */,
205F0E1C1B373A2C007741D0 /* ASCollectionViewLayoutController.mm */,
05A6D05819D0EB64002DD95E /* ASDealloc2MainObject.h */,
05A6D05919D0EB64002DD95E /* ASDealloc2MainObject.m */,
698548611CA9E025008A345F /* ASEnvironment.h */,
9CFFC6BD1CCAC52B006A6476 /* ASEnvironment.mm */,
4640521B1A3F83C40061C0BA /* ASFlowLayoutController.h */,
@@ -1583,7 +1572,6 @@
058D0A01195D050800B7D73C /* Private */ = {
isa = PBXGroup;
children = (
058D0A02195D050800B7D73C /* _AS-objc-internal.h */,
058D0A03195D050800B7D73C /* _ASCoreAnimationExtras.h */,
058D0A04195D050800B7D73C /* _ASCoreAnimationExtras.mm */,
AC026B6D1BD57DBF00BBC17E /* _ASHierarchyChangeSet.h */,
@@ -1823,7 +1811,6 @@
698C8B621CAB49FC0052DC3F /* ASLayoutElementExtensibility.h in Headers */,
698548641CA9E025008A345F /* ASEnvironment.h in Headers */,
AC026B6A1BD57D6F00BBC17E /* ASChangeSetDataController.h in Headers */,
B35062481B010EFD0018CF92 /* _AS-objc-internal.h in Headers */,
69F10C871C84C35D0026140C /* ASRangeControllerUpdateRangeProtocol+Beta.h in Headers */,
B350623C1B010EFD0018CF92 /* _ASAsyncTransaction.h in Headers */,
9C70F20D1CDBE9CB007D6C76 /* ASDefaultPlayButton.h in Headers */,
@@ -1884,7 +1871,6 @@
B35061FA1B010EFD0018CF92 /* ASControlNode+Subclasses.h in Headers */,
B35061F81B010EFD0018CF92 /* ASControlNode.h in Headers */,
B35062171B010EFD0018CF92 /* ASDataController.h in Headers */,
B35062191B010EFD0018CF92 /* ASDealloc2MainObject.h in Headers */,
34EFC75B1B701BAF00AD841F /* ASDimension.h in Headers */,
68FC85EA1CE29C7D00EDD713 /* ASVisibilityProtocols.h in Headers */,
69C4CAF61DA3147000B1EC9B /* ASLayoutElementStylePrivate.h in Headers */,
@@ -2238,7 +2224,6 @@
058D0A13195D050800B7D73C /* ASControlNode.mm in Sources */,
464052211A3F83C40061C0BA /* ASDataController.mm in Sources */,
B30BF6531C5964B0004FCD53 /* ASLayoutManager.m in Sources */,
05A6D05B19D0EB64002DD95E /* ASDealloc2MainObject.m in Sources */,
ACF6ED211B17843500DA7C62 /* ASDimension.mm in Sources */,
8021EC1E1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */,
058D0A28195D050800B7D73C /* ASDisplayNode+AsyncDisplay.mm in Sources */,
@@ -2432,7 +2417,6 @@
B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */,
8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */,
B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */,
B350621A1B010EFD0018CF92 /* ASDealloc2MainObject.m in Sources */,
767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */,
34EFC75C1B701BD200AD841F /* ASDimension.mm in Sources */,
B350624E1B010EFD0018CF92 /* ASDisplayNode+AsyncDisplay.mm in Sources */,

View File

@@ -131,12 +131,6 @@
return nil;
}
- (void)dealloc
{
self.delegate = nil;
self.dataSource = nil;
}
#pragma mark ASDisplayNode
- (void)didLoad
@@ -226,7 +220,15 @@
_pendingState.delegate = delegate;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.asyncDelegate = delegate;
// Manually trampoline to the main thread. The view requires this be called on main
// and asserting here isn't an option it is a common pattern for users to clear
// the delegate/dataSource in dealloc, which may be running on a background thread.
// It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
ASCollectionView *view = (ASCollectionView *)_view;
ASPerformBlockOnMainThread(^{
view.asyncDelegate = delegate;
});
}
}
@@ -245,7 +247,14 @@
_pendingState.dataSource = dataSource;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASCollectionNode should be loaded if pendingState doesn't exist");
self.view.asyncDataSource = dataSource;
// Manually trampoline to the main thread. The view requires this be called on main
// and asserting here isn't an option it is a common pattern for users to clear
// the delegate/dataSource in dealloc, which may be running on a background thread.
// It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
ASCollectionView *view = (ASCollectionView *)_view;
ASPerformBlockOnMainThread(^{
view.asyncDataSource = dataSource;
});
}
}

View File

@@ -27,6 +27,25 @@
#import "ASSectionContext.h"
#import "ASCollectionView+Undeprecated.h"
/**
* A macro to get self.collectionNode and assign it to a local variable, or return
* the given value if nil.
*
* Previously we would set ASCollectionNode's dataSource & delegate to nil
* during dealloc. However, our asyncDelegate & asyncDataSource must be set on the
* main thread, so if the node is deallocated off-main, we won't learn about the change
* until later on. Since our @c collectionNode parameter to delegate methods (e.g.
* collectionNode:didEndDisplayingItemWithNode:) is nonnull, it's important that we never
* unintentionally pass nil (this will crash in Swift, in production). So we can use
* this macro to ensure that our node is still alive before calling out to the user
* on its behalf.
*/
#define GET_COLLECTIONNODE_OR_RETURN(__var, __val) \
ASCollectionNode *__var = self.collectionNode; \
if (__var == nil) { \
return __val; \
}
/// What, if any, invalidation should we perform during the next -layoutSubviews.
typedef NS_ENUM(NSUInteger, ASCollectionViewInvalidationStyle) {
/// Perform no invalidation.
@@ -302,10 +321,15 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
// Sometimes the UIKit classes can call back to their delegate even during deallocation, due to animation completion blocks etc.
_isDeallocating = YES;
[self setAsyncDelegate:nil];
[self setAsyncDataSource:nil];
// Data controller & range controller may own a ton of nodes, let's deallocate those off-main.
ASPerformBackgroundDeallocation(_dataController);
ASPerformBackgroundDeallocation(_rangeController);
}
#pragma mark -
@@ -372,6 +396,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)setAsyncDataSource:(id<ASCollectionDataSource>)asyncDataSource
{
// Changing super.dataSource will trigger a setNeedsLayout, so this must happen on the main thread.
ASDisplayNodeAssertMainThread();
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
@@ -424,6 +451,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)setAsyncDelegate:(id<ASCollectionDelegate>)asyncDelegate
{
// Changing super.delegate will trigger a setNeedsLayout, so this must happen on the main thread.
ASDisplayNodeAssertMainThread();
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
@@ -814,7 +844,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath);
if (_asyncDelegateFlags.collectionNodeWillDisplayItem) {
[_asyncDelegate collectionNode:self.collectionNode willDisplayItemWithNode:cellNode];
if (ASCollectionNode *collectionNode = self.collectionNode) {
[_asyncDelegate collectionNode:collectionNode willDisplayItemWithNode:cellNode];
}
} else if (_asyncDelegateFlags.collectionViewWillDisplayNodeForItem) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -837,7 +869,9 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
if (_asyncDelegateFlags.collectionNodeDidEndDisplayingItem) {
[_asyncDelegate collectionNode:self.collectionNode didEndDisplayingItemWithNode:cellNode];
if (ASCollectionNode *collectionNode = self.collectionNode) {
[_asyncDelegate collectionNode:collectionNode didEndDisplayingItemWithNode:cellNode];
}
} else if (_asyncDelegateFlags.collectionViewDidEndDisplayingNodeForItem) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -856,27 +890,30 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView willDisplaySupplementaryView:(UICollectionReusableView *)view forElementKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeWillDisplaySupplementaryElement) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
[_asyncDelegate collectionNode:self.collectionNode willDisplaySupplementaryElementWithNode:node];
[_asyncDelegate collectionNode:collectionNode willDisplaySupplementaryElementWithNode:node];
}
}
- (void)collectionView:(UICollectionView *)collectionView didEndDisplayingSupplementaryView:(UICollectionReusableView *)view forElementOfKind:(NSString *)elementKind atIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidEndDisplayingSupplementaryElement) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
ASCellNode *node = [self supplementaryNodeForElementKind:elementKind atIndexPath:indexPath];
ASDisplayNodeAssert([node.supplementaryElementKind isEqualToString:elementKind], @"Expected node for supplementary element to have kind '%@', got '%@'.", elementKind, node.supplementaryElementKind);
[_asyncDelegate collectionNode:self.collectionNode didEndDisplayingSupplementaryElementWithNode:node];
[_asyncDelegate collectionNode:collectionNode didEndDisplayingSupplementaryElementWithNode:node];
}
}
- (BOOL)collectionView:(UICollectionView *)collectionView shouldSelectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldSelectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldSelectItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldSelectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewShouldSelectItem) {
#pragma clang diagnostic push
@@ -890,9 +927,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didSelectItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidSelectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didSelectItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didSelectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidSelectItem) {
#pragma clang diagnostic push
@@ -905,9 +943,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView shouldDeselectItemAtIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldDeselectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldDeselectItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldDeselectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewShouldDeselectItem) {
#pragma clang diagnostic push
@@ -921,9 +960,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didDeselectItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidDeselectItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didDeselectItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didDeselectItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidDeselectItem) {
#pragma clang diagnostic push
@@ -936,9 +976,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView shouldHighlightItemAtIndexPath:(NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldHighlightItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldHighlightItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldHighlightItemAtIndexPath:indexPath];
} else {
return YES;
}
@@ -954,9 +995,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didHighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidHighlightItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didHighlightItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didHighlightItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidHighlightItem) {
#pragma clang diagnostic push
@@ -969,9 +1011,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView didUnhighlightItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeDidUnhighlightItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode didUnhighlightItemAtIndexPath:indexPath];
[_asyncDelegate collectionNode:collectionNode didUnhighlightItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewDidUnhighlightItem) {
#pragma clang diagnostic push
@@ -984,9 +1027,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView shouldShowMenuForItemAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.collectionNodeShouldShowMenuForItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode shouldShowMenuForItemAtIndexPath:indexPath];
return [_asyncDelegate collectionNode:collectionNode shouldShowMenuForItemAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.collectionViewShouldShowMenuForItem) {
#pragma clang diagnostic push
@@ -1000,9 +1044,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (BOOL)collectionView:(UICollectionView *)collectionView canPerformAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
{
if (_asyncDelegateFlags.collectionNodeCanPerformActionForItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate collectionNode:self.collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender];
return [_asyncDelegate collectionNode:collectionNode canPerformAction:action forItemAtIndexPath:indexPath sender:sender];
}
} else if (_asyncDelegateFlags.collectionViewCanPerformActionForItem) {
#pragma clang diagnostic push
@@ -1016,9 +1061,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (void)collectionView:(UICollectionView *)collectionView performAction:(nonnull SEL)action forItemAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
{
if (_asyncDelegateFlags.collectionNodePerformActionForItem) {
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
indexPath = [self convertIndexPathToCollectionNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate collectionNode:self.collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender];
[_asyncDelegate collectionNode:collectionNode performAction:action forItemAtIndexPath:indexPath sender:sender];
}
} else if (_asyncDelegateFlags.collectionViewPerformActionForItem) {
#pragma clang diagnostic push
@@ -1183,7 +1229,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
// if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = _asyncDelegateFlags.collectionNodeWillBeginBatchFetch || _asyncDelegateFlags.collectionViewWillBeginBatchFetch;
if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionNode) {
return [_asyncDelegate shouldBatchFetchForCollectionNode:self.collectionNode];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, NO);
return [_asyncDelegate shouldBatchFetchForCollectionNode:collectionNode];
} else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForCollectionView) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1229,7 +1276,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
[_batchContext beginBatchFetching];
if (_asyncDelegateFlags.collectionNodeWillBeginBatchFetch) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate collectionNode:self.collectionNode willBeginBatchFetchWithContext:_batchContext];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, (void)0);
[_asyncDelegate collectionNode:collectionNode willBeginBatchFetchWithContext:_batchContext];
});
} else if (_asyncDelegateFlags.collectionViewWillBeginBatchFetch) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@@ -1248,9 +1296,11 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
ASCellNodeBlock block = nil;
if (_asyncDataSourceFlags.collectionNodeNodeBlockForItem) {
block = [_asyncDataSource collectionNode:self.collectionNode nodeBlockForItemAtIndexPath:indexPath];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; });
block = [_asyncDataSource collectionNode:collectionNode nodeBlockForItemAtIndexPath:indexPath];
} else if (_asyncDataSourceFlags.collectionNodeNodeForItem) {
ASCellNode *node = [_asyncDataSource collectionNode:self.collectionNode nodeForItemAtIndexPath:indexPath];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, ^{ return [[ASCellNode alloc] init]; });
ASCellNode *node = [_asyncDataSource collectionNode:collectionNode nodeForItemAtIndexPath:indexPath];
if ([node isKindOfClass:[ASCellNode class]]) {
block = ^{
return node;
@@ -1304,7 +1354,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
{
if (_asyncDataSourceFlags.collectionNodeNumberOfItemsInSection) {
return [_asyncDataSource collectionNode:self.collectionNode numberOfItemsInSection:section];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0);
return [_asyncDataSource collectionNode:collectionNode numberOfItemsInSection:section];
} else if (_asyncDataSourceFlags.collectionViewNumberOfItemsInSection) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1317,7 +1368,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController {
if (_asyncDataSourceFlags.numberOfSectionsInCollectionNode) {
return [_asyncDataSource numberOfSectionsInCollectionNode:self.collectionNode];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, 0);
return [_asyncDataSource numberOfSectionsInCollectionNode:collectionNode];
} else if (_asyncDataSourceFlags.numberOfSectionsInCollectionView) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1339,7 +1391,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
{
ASCellNode *node = nil;
if (_asyncDataSourceFlags.collectionNodeNodeForSupplementaryElement) {
node = [_asyncDataSource collectionNode:self.collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, [[ASCellNode alloc] init] );
node = [_asyncDataSource collectionNode:collectionNode nodeForSupplementaryElementOfKind:kind atIndexPath:indexPath];
} else if (_asyncDataSourceFlags.collectionViewNodeForSupplementaryElement) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1363,6 +1416,10 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
- (NSUInteger)dataController:(ASCollectionDataController *)dataController supplementaryNodesOfKind:(NSString *)kind inSection:(NSUInteger)section
{
if (_asyncDataSource == nil) {
return 0;
}
return [self.layoutInspector collectionView:self supplementaryNodesOfKind:kind inSection:section];
}
@@ -1372,7 +1429,8 @@ static NSString * const kCellReuseIdentifier = @"_ASCollectionViewCell";
id<ASSectionContext> context = nil;
if (_asyncDataSourceFlags.collectionNodeContextForSection) {
context = [_asyncDataSource collectionNode:self.collectionNode contextForSection:section];
GET_COLLECTIONNODE_OR_RETURN(collectionNode, nil);
context = [_asyncDataSource collectionNode:collectionNode contextForSection:section];
}
if (context != nil) {

View File

@@ -14,7 +14,6 @@
#import <AsyncDisplayKit/_ASAsyncTransactionContainer.h>
#import <AsyncDisplayKit/ASBaseDefines.h>
#import <AsyncDisplayKit/ASDealloc2MainObject.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASAsciiArtBoxCreator.h>
#import <AsyncDisplayKit/ASLayoutElement.h>
@@ -106,7 +105,7 @@ extern NSInteger const ASDefaultDrawingPriority;
*
*/
@interface ASDisplayNode : ASDealloc2MainObject <ASLayoutElement>
@interface ASDisplayNode : NSObject <ASLayoutElement>
/** @name Initializing a node object */

View File

@@ -427,7 +427,8 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
_flags.isDeallocating = YES;
// Synchronous nodes may not be able to call the hierarchy notifications, so only enforce for regular nodes.
ASDisplayNodeAssert(_flags.synchronous || !ASInterfaceStateIncludesVisible(_interfaceState), @"Node should always be marked invisible before deallocating. Node: %@", self);
@@ -443,15 +444,123 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
for (ASDisplayNode *subnode in _subnodes)
[subnode __setSupernode:nil];
_view = nil;
// Trampoline any UIKit ivars' deallocation to main
if (ASDisplayNodeThreadIsMain() == NO) {
[self _scheduleIvarsForMainDeallocation];
}
_subnodes = nil;
_layer = nil;
// TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway.
[self __setSupernode:nil];
_pendingViewState = nil;
}
_pendingDisplayNodes = nil;
- (void)_scheduleIvarsForMainDeallocation
{
/**
* UIKit components must be deallocated on the main thread. We use this shared
* run loop queue to gradually deallocate them across many turns of the main run loop.
*/
static ASRunLoopQueue *queue;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
queue = [[ASRunLoopQueue alloc] initWithRunLoop:CFRunLoopGetMain() andHandler:^(id _Nonnull dequeuedItem, BOOL isQueueDrained) { }];
queue.batchSize = 10;
});
NSValue *ivarsObj = [[self class] _ivarsThatMayNeedMainDeallocation];
// Unwrap the ivar array
unsigned int count = 0;
// Will be unused if assertions are disabled.
__unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count);
ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType);
Ivar ivars[count];
[ivarsObj getValue:ivars];
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);
[queue enqueue:value];
} else {
LOG(@"Not trampolining ivar '%s' value %@.", ivar_getName(ivar), value);
}
}
}
/**
* Returns an NSValue-wrapped array of all the ivars in this class or its superclasses
* up through ASDisplayNode, that we expect may need to be deallocated on main.
*
* This method caches its results.
*/
+ (NSValue/*<[Ivar]>*/ * _Nonnull)_ivarsThatMayNeedMainDeallocation
{
static NSCache<Class, NSValue *> *ivarsCache;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
ivarsCache = [[NSCache alloc] init];
});
NSValue *result = [ivarsCache objectForKey:self];
if (result != nil) {
return result;
}
// Cache miss.
unsigned int resultCount = 0;
static const int kMaxDealloc2MainIvarsPerClassTree = 64;
Ivar resultIvars[kMaxDealloc2MainIvarsPerClassTree];
// Get superclass results first.
Class c = class_getSuperclass(self);
if (c != [NSObject class]) {
NSValue *ivarsObj = [c _ivarsThatMayNeedMainDeallocation];
// Unwrap the ivar array and append it to our working array
unsigned int count = 0;
// Will be unused if assertions are disabled.
__unused int scanResult = sscanf(ivarsObj.objCType, "[%u^{objc_ivar}]", &count);
ASDisplayNodeAssert(scanResult == 1, @"Unexpected type in NSValue: %s", ivarsObj.objCType);
ASDisplayNodeCAssert(resultCount + count < kMaxDealloc2MainIvarsPerClassTree, @"More than %d dealloc2main ivars are not supported. Count: %d", kMaxDealloc2MainIvarsPerClassTree, resultCount + count);
[ivarsObj getValue:resultIvars + resultCount];
resultCount += count;
}
// Now gather ivars from this particular class.
unsigned int allMyIvarsCount;
Ivar *allMyIvars = class_copyIvarList(self, &allMyIvarsCount);
for (NSUInteger i = 0; i < allMyIvarsCount; i++) {
Ivar ivar = allMyIvars[i];
const char *type = ivar_getTypeEncoding(ivar);
if (strcmp(type, @encode(id)) == 0) {
// 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));
} 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);
} else {
LOG(@"Skipping ivar '%s' for main deallocation.", ivar_getName(ivar));
}
}
}
free(allMyIvars);
// Encode the type (array of Ivars) into a string and wrap it in an NSValue
char arrayType[32];
snprintf(arrayType, 32, "[%u^{objc_ivar}]", resultCount);
result = [NSValue valueWithBytes:resultIvars objCType:arrayType];
[ivarsCache setObject:result forKey:self];
return result;
}
#pragma mark - Core
@@ -539,7 +648,7 @@ static ASDisplayNodeMethodOverrides GetASDisplayNodeMethodOverrides(Class c)
{
ASDN::MutexLocker l(__instanceLock__);
if (self._isDeallocating) {
if (_flags.isDeallocating) {
return;
}

View File

@@ -123,12 +123,6 @@
}
}
- (void)dealloc
{
self.delegate = nil;
self.dataSource = nil;
}
- (ASTableView *)view
{
return (ASTableView *)[super view];
@@ -195,7 +189,15 @@
_pendingState.delegate = delegate;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
self.view.asyncDelegate = delegate;
// Manually trampoline to the main thread. The view requires this be called on main
// and asserting here isn't an option it is a common pattern for users to clear
// the delegate/dataSource in dealloc, which may be running on a background thread.
// It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
ASTableView *view = (ASTableView *)_view;
ASPerformBlockOnMainThread(^{
view.asyncDelegate = delegate;
});
}
}
@@ -214,7 +216,15 @@
_pendingState.dataSource = dataSource;
} else {
ASDisplayNodeAssert([self isNodeLoaded], @"ASTableNode should be loaded if pendingState doesn't exist");
self.view.asyncDataSource = dataSource;
// Manually trampoline to the main thread. The view requires this be called on main
// and asserting here isn't an option it is a common pattern for users to clear
// the delegate/dataSource in dealloc, which may be running on a background thread.
// It is important that we avoid retaining self in this block, so that this method is dealloc-safe.
ASTableView *view = (ASTableView *)_view;
ASPerformBlockOnMainThread(^{
view.asyncDataSource = dataSource;
});
}
}

View File

@@ -31,6 +31,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
//#define LOG(...) NSLog(__VA_ARGS__)
#define LOG(...)
/**
* See note at the top of ASCollectionView.mm near declaration of macro GET_COLLECTIONNODE_OR_RETURN
*/
#define GET_TABLENODE_OR_RETURN(__var, __val) \
ASTableNode *__var = self.tableNode; \
if (__var == nil) { \
return __val; \
}
#pragma mark -
#pragma mark ASCellNode<->UITableViewCell bridging.
@@ -282,10 +291,15 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
// Sometimes the UIKit classes can call back to their delegate even during deallocation.
_isDeallocating = YES;
[self setAsyncDelegate:nil];
[self setAsyncDataSource:nil];
// Data controller & range controller may own a ton of nodes, let's deallocate those off-main
ASPerformBackgroundDeallocation(_dataController);
ASPerformBackgroundDeallocation(_rangeController);
}
#pragma mark -
@@ -310,6 +324,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)setAsyncDataSource:(id<ASTableDataSource>)asyncDataSource
{
// Changing super.dataSource will trigger a setNeedsLayout, so this must happen on the main thread.
ASDisplayNodeAssertMainThread();
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDataSource in the ViewController's dealloc. In this case our _asyncDataSource
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
@@ -354,6 +371,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)setAsyncDelegate:(id<ASTableDelegate>)asyncDelegate
{
// Changing super.delegate will trigger a setNeedsLayout, so this must happen on the main thread.
ASDisplayNodeAssertMainThread();
// Note: It's common to check if the value hasn't changed and short-circuit but we aren't doing that here to handle
// the (common) case of nilling the asyncDelegate in the ViewController's dealloc. In this case our _asyncDelegate
// will return as nil (ARC magic) even though the _proxyDataSource still exists. It's really important to hold a strong
@@ -792,7 +812,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with cell that will be displayed not to be nil. indexPath: %@", indexPath);
if (_asyncDelegateFlags.tableNodeWillDisplayNodeForRow) {
[_asyncDelegate tableNode:self.tableNode willDisplayRowWithNode:cellNode];
GET_TABLENODE_OR_RETURN(tableNode, (void)0);
[_asyncDelegate tableNode:tableNode willDisplayRowWithNode:cellNode];
} else if (_asyncDelegateFlags.tableViewWillDisplayNodeForRow) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -821,7 +842,9 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
ASDisplayNodeAssertNotNil(cellNode, @"Expected node associated with removed cell not to be nil.");
if (_asyncDelegateFlags.tableNodeDidEndDisplayingNodeForRow) {
[_asyncDelegate tableNode:self.tableNode didEndDisplayingRowWithNode:cellNode];
if (ASTableNode *tableNode = self.tableNode) {
[_asyncDelegate tableNode:tableNode didEndDisplayingRowWithNode:cellNode];
}
} else if (_asyncDelegateFlags.tableViewDidEndDisplayingNodeForRow) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -837,12 +860,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (NSIndexPath *)tableView:(UITableView *)tableView willSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeWillSelectRow) {
GET_TABLENODE_OR_RETURN(tableNode, indexPath);
NSIndexPath *result = [self convertIndexPathToTableNode:indexPath];
// If this item was is gone, just let the table view do its default behavior and select.
if (result == nil) {
return indexPath;
} else {
result = [_asyncDelegate tableNode:self.tableNode willSelectRowAtIndexPath:result];
result = [_asyncDelegate tableNode:tableNode willSelectRowAtIndexPath:result];
result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES];
return result;
}
@@ -859,9 +883,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeDidSelectRow) {
GET_TABLENODE_OR_RETURN(tableNode, (void)0);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate tableNode:self.tableNode didSelectRowAtIndexPath:indexPath];
[_asyncDelegate tableNode:tableNode didSelectRowAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.tableViewDidSelectRow) {
#pragma clang diagnostic push
@@ -874,12 +899,13 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (NSIndexPath *)tableView:(UITableView *)tableView willDeselectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeWillDeselectRow) {
GET_TABLENODE_OR_RETURN(tableNode, indexPath);
NSIndexPath *result = [self convertIndexPathToTableNode:indexPath];
// If this item was is gone, just let the table view do its default behavior and deselect.
if (result == nil) {
return indexPath;
} else {
result = [_asyncDelegate tableNode:self.tableNode willDeselectRowAtIndexPath:result];
result = [_asyncDelegate tableNode:tableNode willDeselectRowAtIndexPath:result];
result = [self convertIndexPathFromTableNode:result waitingIfNeeded:YES];
return result;
}
@@ -895,9 +921,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)tableView:(UITableView *)tableView didDeselectRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeDidDeselectRow) {
GET_TABLENODE_OR_RETURN(tableNode, (void)0);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate tableNode:self.tableNode didDeselectRowAtIndexPath:indexPath];
[_asyncDelegate tableNode:tableNode didDeselectRowAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.tableViewDidDeselectRow) {
#pragma clang diagnostic push
@@ -910,9 +937,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeShouldHighlightRow) {
GET_TABLENODE_OR_RETURN(tableNode, NO);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate tableNode:self.tableNode shouldHighlightRowAtIndexPath:indexPath];
return [_asyncDelegate tableNode:tableNode shouldHighlightRowAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.tableViewShouldHighlightRow) {
#pragma clang diagnostic push
@@ -926,9 +954,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)tableView:(UITableView *)tableView didHighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeDidHighlightRow) {
GET_TABLENODE_OR_RETURN(tableNode, (void)0);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate tableNode:self.tableNode didHighlightRowAtIndexPath:indexPath];
return [_asyncDelegate tableNode:tableNode didHighlightRowAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.tableViewDidHighlightRow) {
#pragma clang diagnostic push
@@ -941,9 +970,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)tableView:(UITableView *)tableView didUnhighlightRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeDidHighlightRow) {
GET_TABLENODE_OR_RETURN(tableNode, (void)0);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate tableNode:self.tableNode didUnhighlightRowAtIndexPath:indexPath];
return [_asyncDelegate tableNode:tableNode didUnhighlightRowAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.tableViewDidUnhighlightRow) {
#pragma clang diagnostic push
@@ -956,9 +986,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(nonnull NSIndexPath *)indexPath
{
if (_asyncDelegateFlags.tableNodeShouldShowMenuForRow) {
GET_TABLENODE_OR_RETURN(tableNode, NO);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate tableNode:self.tableNode shouldShowMenuForRowAtIndexPath:indexPath];
return [_asyncDelegate tableNode:tableNode shouldShowMenuForRowAtIndexPath:indexPath];
}
} else if (_asyncDelegateFlags.tableViewShouldShowMenuForRow) {
#pragma clang diagnostic push
@@ -972,9 +1003,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (BOOL)tableView:(UITableView *)tableView canPerformAction:(nonnull SEL)action forRowAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
{
if (_asyncDelegateFlags.tableNodeCanPerformActionForRow) {
GET_TABLENODE_OR_RETURN(tableNode, NO);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
return [_asyncDelegate tableNode:self.tableNode canPerformAction:action forRowAtIndexPath:indexPath withSender:sender];
return [_asyncDelegate tableNode:tableNode canPerformAction:action forRowAtIndexPath:indexPath withSender:sender];
}
} else if (_asyncDelegateFlags.tableViewCanPerformActionForRow) {
#pragma clang diagnostic push
@@ -988,9 +1020,10 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (void)tableView:(UITableView *)tableView performAction:(nonnull SEL)action forRowAtIndexPath:(nonnull NSIndexPath *)indexPath withSender:(nullable id)sender
{
if (_asyncDelegateFlags.tableNodePerformActionForRow) {
GET_TABLENODE_OR_RETURN(tableNode, (void)0);
indexPath = [self convertIndexPathToTableNode:indexPath];
if (indexPath != nil) {
[_asyncDelegate tableNode:self.tableNode performAction:action forRowAtIndexPath:indexPath withSender:sender];
[_asyncDelegate tableNode:tableNode performAction:action forRowAtIndexPath:indexPath withSender:sender];
}
} else if (_asyncDelegateFlags.tableViewPerformActionForRow) {
#pragma clang diagnostic push
@@ -1119,7 +1152,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
// if the delegate does not respond to this method, there is no point in starting to fetch
BOOL canFetch = _asyncDelegateFlags.tableNodeWillBeginBatchFetch || _asyncDelegateFlags.tableViewWillBeginBatchFetch;
if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableNode) {
return [_asyncDelegate shouldBatchFetchForTableNode:self.tableNode];
GET_TABLENODE_OR_RETURN(tableNode, NO);
return [_asyncDelegate shouldBatchFetchForTableNode:tableNode];
} else if (canFetch && _asyncDelegateFlags.shouldBatchFetchForTableView) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1165,7 +1199,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
[_batchContext beginBatchFetching];
if (_asyncDelegateFlags.tableNodeWillBeginBatchFetch) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[_asyncDelegate tableNode:self.tableNode willBeginBatchFetchWithContext:_batchContext];
GET_TABLENODE_OR_RETURN(tableNode, (void)0);
[_asyncDelegate tableNode:tableNode willBeginBatchFetchWithContext:_batchContext];
});
} else if (_asyncDelegateFlags.tableViewWillBeginBatchFetch) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
@@ -1401,9 +1436,14 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
ASCellNodeBlock block = nil;
if (_asyncDataSourceFlags.tableNodeNodeBlockForRow) {
block = [_asyncDataSource tableNode:self.tableNode nodeBlockForRowAtIndexPath:indexPath];
if (ASTableNode *tableNode = self.tableNode) {
block = [_asyncDataSource tableNode:tableNode nodeBlockForRowAtIndexPath:indexPath];
}
} else if (_asyncDataSourceFlags.tableNodeNodeForRow) {
ASCellNode *node = [_asyncDataSource tableNode:self.tableNode nodeForRowAtIndexPath:indexPath];
ASCellNode *node = nil;
if (ASTableNode *tableNode = self.tableNode) {
node = [_asyncDataSource tableNode:tableNode nodeForRowAtIndexPath:indexPath];
}
if ([node isKindOfClass:[ASCellNode class]]) {
block = ^{
return node;
@@ -1453,7 +1493,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
{
ASSizeRange constrainedSize = kInvalidSizeRange;
if (_asyncDelegateFlags.tableNodeConstrainedSizeForRow) {
ASSizeRange delegateConstrainedSize = [_asyncDelegate tableNode:self.tableNode constrainedSizeForRowAtIndexPath:indexPath];
GET_TABLENODE_OR_RETURN(tableNode, constrainedSize);
ASSizeRange delegateConstrainedSize = [_asyncDelegate tableNode:tableNode constrainedSizeForRowAtIndexPath:indexPath];
// ignore widths in the returned size range (for TableView)
constrainedSize = ASSizeRangeMake(CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.min.height),
CGSizeMake(_nodesConstrainedWidth, delegateConstrainedSize.max.height));
@@ -1475,7 +1516,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (NSUInteger)dataController:(ASDataController *)dataController rowsInSection:(NSUInteger)section
{
if (_asyncDataSourceFlags.tableNodeNumberOfRowsInSection) {
return [_asyncDataSource tableNode:self.tableNode numberOfRowsInSection:section];
GET_TABLENODE_OR_RETURN(tableNode, 0);
return [_asyncDataSource tableNode:tableNode numberOfRowsInSection:section];
} else if (_asyncDataSourceFlags.tableViewNumberOfRowsInSection) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -1489,7 +1531,8 @@ static NSString * const kCellReuseIdentifier = @"_ASTableViewCell";
- (NSUInteger)numberOfSectionsInDataController:(ASDataController *)dataController
{
if (_asyncDataSourceFlags.numberOfSectionsInTableNode) {
return [_asyncDataSource numberOfSectionsInTableNode:self.tableNode];
GET_TABLENODE_OR_RETURN(tableNode, 0);
return [_asyncDataSource numberOfSectionsInTableNode:tableNode];
} else if (_asyncDataSourceFlags.numberOfSectionsInTableView) {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

View File

@@ -19,6 +19,7 @@
#import "ASTraitCollection.h"
#import "ASEnvironmentInternal.h"
#import "ASRangeControllerUpdateRangeProtocol+Beta.h"
#import "ASInternalHelpers.h"
#define AS_LOG_VISIBILITY_CHANGES 0
@@ -61,6 +62,11 @@
return self;
}
- (void)dealloc
{
ASPerformBackgroundDeallocation(_node);
}
- (void)loadView
{
ASDisplayNodeAssertTrue(!_node.layerBacked);

View File

@@ -11,7 +11,6 @@
#pragma once
#import <UIKit/UIKit.h>
#import <AsyncDisplayKit/ASDealloc2MainObject.h>
#import <AsyncDisplayKit/ASDimension.h>
#import <AsyncDisplayKit/ASFlowLayoutController.h>
#import <AsyncDisplayKit/ASEventLog.h>
@@ -114,7 +113,7 @@ FOUNDATION_EXPORT NSString * const ASDataControllerRowNodeKind;
* For each data updating, the corresponding methods in delegate will be called.
*/
@protocol ASFlowLayoutControllerDataSource;
@interface ASDataController : ASDealloc2MainObject <ASFlowLayoutControllerDataSource>
@interface ASDataController : NSObject <ASFlowLayoutControllerDataSource>
- (instancetype)initWithDataSource:(id<ASDataControllerSource>)dataSource eventLog:(nullable ASEventLog *)eventLog NS_DESIGNATED_INITIALIZER;

View File

@@ -1057,26 +1057,6 @@ NSString * const ASDataControllerRowNodeKind = @"_ASDataControllerRowNodeKind";
ASMoveElementInTwoDimensionalArray(_completedNodes[ASDataControllerRowNodeKind], indexPath, newIndexPath);
}
#pragma mark - Dealloc
- (void)dealloc
{
ASDisplayNodeAssertMainThread();
for (NSMutableArray *sections in [_completedNodes objectEnumerator]) {
for (NSArray *section in sections) {
for (ASCellNode *node in section) {
if (node.isNodeLoaded) {
if (node.layerBacked) {
[node.layer removeFromSuperlayer];
} else {
[node.view removeFromSuperview];
}
}
}
}
}
}
@end
#if AS_MEASURE_AVOIDED_DATACONTROLLER_WORK

View File

@@ -1,26 +0,0 @@
//
// ASDealloc2MainObject.h
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import <Foundation/Foundation.h>
NS_ASSUME_NONNULL_BEGIN
/**
* _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN permits classes to implement their own reference counting and enforce
* deallocation on the main thread, but requires manual reference counting. This superclass exposes such functionality
* to ARC-enabled classes.
*/
@interface ASDealloc2MainObject : NSObject
- (BOOL)_isDeallocating;
@end
NS_ASSUME_NONNULL_END

View File

@@ -1,34 +0,0 @@
//
// ASDealloc2MainObject.m
// AsyncDisplayKit
//
// Copyright (c) 2014-present, Facebook, Inc. All rights reserved.
// This source code is licensed under the BSD-style license found in the
// LICENSE file in the root directory of this source tree. An additional grant
// of patent rights can be found in the PATENTS file in the same directory.
//
#import "ASDealloc2MainObject.h"
#import <pthread.h>
#import "_AS-objc-internal.h"
#if __has_feature(objc_arc)
#error This file must be compiled without ARC. Use -fno-objc-arc.
#endif
@interface ASDealloc2MainObject ()
{
@private
int _retainCount;
}
@end
@implementation ASDealloc2MainObject
#if !__has_feature(objc_arc)
_OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_retainCount);
#endif
@end

View File

@@ -30,7 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
* "working ranges" to trigger network calls and rendering, and is responsible for driving asynchronous layout of cells.
* This includes cancelling those asynchronous operations as cells fall outside of the working ranges.
*/
@interface ASRangeController : ASDealloc2MainObject <ASDataControllerDelegate>
@interface ASRangeController : NSObject <ASDataControllerDelegate>
{
id<ASLayoutController> _layoutController;
__weak id<ASRangeControllerDataSource> _dataSource;

View File

@@ -13,7 +13,6 @@
// These methods must never be called or overridden by other classes.
//
#import "_AS-objc-internal.h"
#import "ASDisplayNode.h"
#import "ASThread.h"

View File

@@ -14,7 +14,6 @@
//
#import <atomic>
#import "_AS-objc-internal.h"
#import "ASDisplayNode.h"
#import "ASThread.h"
#import "_ASTransitionContext.h"
@@ -103,6 +102,7 @@ FOUNDATION_EXPORT NSString * const ASRenderingEngineDidDisplayNodesScheduledBefo
unsigned isExitingHierarchy:1;
unsigned isInHierarchy:1;
unsigned visibilityNotificationsDisabled:VISIBILITY_NOTIFICATIONS_DISABLED_BITS;
unsigned isDeallocating:1;
} _flags;
@protected

View File

@@ -12,6 +12,8 @@
#import <AsyncDisplayKit/ASBaseDefines.h>
NS_ASSUME_NONNULL_BEGIN
ASDISPLAYNODE_EXTERN_C_BEGIN
BOOL ASSubclassOverridesSelector(Class superclass, Class subclass, SEL selector);
@@ -41,6 +43,10 @@ CGFloat ASCeilPixelValue(CGFloat f);
CGFloat ASRoundPixelValue(CGFloat f);
BOOL ASClassRequiresMainThreadDeallocation(Class _Nullable c);
Class _Nullable ASGetClassFromType(const char *type);
ASDISPLAYNODE_EXTERN_C_END
ASDISPLAYNODE_INLINE BOOL ASImageAlphaInfoIsOpaque(CGImageAlphaInfo info) {
@@ -81,3 +87,5 @@ ASDISPLAYNODE_INLINE void ASBoundsAndPositionForFrame(CGRect rect, CGPoint origi
@interface NSIndexPath (ASInverseComparison)
- (NSComparisonResult)asdk_inverseCompare:(NSIndexPath *)otherIndexPath;
@end
NS_ASSUME_NONNULL_END

View File

@@ -84,6 +84,48 @@ void ASPerformBackgroundDeallocation(id object)
[[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object];
}
BOOL ASClassRequiresMainThreadDeallocation(Class class)
{
if (class == [UIImage class] || class == [UIColor class]) {
return NO;
}
if ([class isSubclassOfClass:[UIResponder class]]
|| [class isSubclassOfClass:[CALayer class]]
|| [class isSubclassOfClass:[UIGestureRecognizer class]]) {
return YES;
}
const char *name = class_getName(class);
if (strncmp(name, "UI", 2) == 0 || strncmp(name, "AV", 2) == 0 || strncmp(name, "CA", 2) == 0) {
return YES;
}
return NO;
}
Class _Nullable ASGetClassFromType(const char *type)
{
// Class types all start with @"
if (strncmp(type, "@\"", 2) != 0) {
return nil;
}
// Ensure length >= 3
size_t typeLength = strlen(type);
if (typeLength < 3) {
ASDisplayNodeCFailAssert(@"Got invalid type-encoding: %s", type);
return nil;
}
// Copy type[2..(end-1)]. So @"UIImage" -> UIImage
size_t resultLength = typeLength - 3;
char className[resultLength + 1];
strncpy(className, type + 2, resultLength);
className[resultLength] = '\0';
return objc_getClass(className);
}
CGFloat ASScreenScale()
{
static CGFloat __scale = 0.0;

View File

@@ -1,473 +0,0 @@
//
// _AS-objc-internal.h
// AsyncDisplayKit
//
// 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.
//
/*
* Copyright (c) 2009 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#ifndef _OBJC_INTERNAL_H
#define _OBJC_INTERNAL_H
/*
* WARNING DANGER HAZARD BEWARE EEK
*
* Everything in this file is for Apple Internal use only.
* These will change in arbitrary OS updates and in unpredictable ways.
* When your program breaks, you get to keep both pieces.
*/
/*
* objc-internal.h: Private SPI for use by other system frameworks.
*/
#include <objc/objc.h>
#include <Availability.h>
#include <malloc/malloc.h>
#include <dispatch/dispatch.h>
__BEGIN_DECLS
// In-place construction of an Objective-C class.
OBJC_EXPORT Class objc_initializeClassPair(Class superclass_gen, const char *name, Class cls_gen, Class meta_gen)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0);
#if __OBJC2__ && __LP64__
// Register a tagged pointer class.
OBJC_EXPORT void _objc_insert_tagged_isa(unsigned char slotNumber, Class isa)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
#endif
// Batch object allocation using malloc_zone_batch_malloc().
OBJC_EXPORT unsigned class_createInstances(Class cls, size_t extraBytes,
id *results, unsigned num_requested)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3)
OBJC_ARC_UNAVAILABLE;
// Get the isa pointer written into objects just before being freed.
OBJC_EXPORT Class _objc_getFreedObjectClass(void)
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
// Substitute receiver for messages to nil.
// Not supported for all messages to nil.
OBJC_EXPORT id _objc_setNilReceiver(id newNilReceiver)
__OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA);
OBJC_EXPORT id _objc_getNilReceiver(void)
__OSX_AVAILABLE_STARTING(__MAC_10_3, __IPHONE_NA);
// Return NO if no instance of `cls` has ever owned an associative reference.
OBJC_EXPORT BOOL class_instancesHaveAssociatedObjects(Class cls)
__OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_0);
// Return YES if GC is on and `object` is a GC allocation.
OBJC_EXPORT BOOL objc_isAuto(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA);
// env NSObjCMessageLoggingEnabled
OBJC_EXPORT void instrumentObjcMessageSends(BOOL flag)
__OSX_AVAILABLE_STARTING(__MAC_10_0, __IPHONE_2_0);
// Initializer called by libSystem
#if __OBJC2__
OBJC_EXPORT void _objc_init(void)
__OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0);
#endif
// GC startup callback from Foundation
OBJC_EXPORT malloc_zone_t *objc_collect_init(int (*callback)(void))
__OSX_AVAILABLE_STARTING(__MAC_10_4, __IPHONE_NA);
// Plainly-implemented GC barriers. Rosetta used to use these.
OBJC_EXPORT id objc_assign_strongCast_generic(id value, id *dest)
UNAVAILABLE_ATTRIBUTE;
OBJC_EXPORT id objc_assign_global_generic(id value, id *dest)
UNAVAILABLE_ATTRIBUTE;
OBJC_EXPORT id objc_assign_threadlocal_generic(id value, id *dest)
UNAVAILABLE_ATTRIBUTE;
OBJC_EXPORT id objc_assign_ivar_generic(id value, id dest, ptrdiff_t offset)
UNAVAILABLE_ATTRIBUTE;
// Install missing-class callback. Used by the late unlamented ZeroLink.
OBJC_EXPORT void _objc_setClassLoader(BOOL (*newClassLoader)(const char *)) OBJC2_UNAVAILABLE;
// Install handler for allocation failures.
// Handler may abort, or throw, or provide an object to return.
OBJC_EXPORT void _objc_setBadAllocHandler(id (*newHandler)(Class isa))
__OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0);
// This can go away when AppKit stops calling it (rdar://7811851)
#if __OBJC2__
OBJC_EXPORT void objc_setMultithreaded (BOOL flag)
__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_NA,__IPHONE_NA);
#endif
// Used by ExceptionHandling.framework
#if !__OBJC2__
OBJC_EXPORT void _objc_error(id rcv, const char *fmt, va_list args)
__attribute__((noreturn))
__OSX_AVAILABLE_BUT_DEPRECATED(__MAC_10_0,__MAC_10_5, __IPHONE_NA,__IPHONE_NA);
#endif
// External Reference support. Used to support compaction.
enum {
OBJC_XREF_STRONG = 1,
OBJC_XREF_WEAK = 2
};
typedef uintptr_t objc_xref_type_t;
typedef uintptr_t objc_xref_t;
OBJC_EXPORT objc_xref_t _object_addExternalReference(id object, objc_xref_type_t type)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
OBJC_EXPORT void _object_removeExternalReference(objc_xref_t xref)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
OBJC_EXPORT id _object_readExternalReference(objc_xref_t xref)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_4_3);
OBJC_EXPORT uintptr_t _object_getExternalHash(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// Instance-specific instance variable layout.
OBJC_EXPORT void _class_setIvarLayoutAccessor(Class cls_gen, const uint8_t* (*accessor) (id object))
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_NA);
OBJC_EXPORT const uint8_t *_object_getIvarLayout(Class cls_gen, id object)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_NA);
OBJC_EXPORT BOOL _class_usesAutomaticRetainRelease(Class cls)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// Obsolete ARC conversions.
// hack - remove and reinstate objc.h's definitions
#undef objc_retainedObject
#undef objc_unretainedObject
#undef objc_unretainedPointer
OBJC_EXPORT id objc_retainedObject(objc_objectptr_t pointer)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT id objc_unretainedObject(objc_objectptr_t pointer)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT objc_objectptr_t objc_unretainedPointer(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
#if __has_feature(objc_arc)
# define objc_retainedObject(o) ((__bridge_transfer id)(objc_objectptr_t)(o))
# define objc_unretainedObject(o) ((__bridge id)(objc_objectptr_t)(o))
# define objc_unretainedPointer(o) ((__bridge objc_objectptr_t)(id)(o))
#else
# define objc_retainedObject(o) ((id)(objc_objectptr_t)(o))
# define objc_unretainedObject(o) ((id)(objc_objectptr_t)(o))
# define objc_unretainedPointer(o) ((objc_objectptr_t)(id)(o))
#endif
// API to only be called by root classes like NSObject or NSProxy
OBJC_EXPORT
id
_objc_rootRetain(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
_objc_rootRelease(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
bool
_objc_rootReleaseWasZero(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
bool
_objc_rootTryRetain(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
bool
_objc_rootIsDeallocating(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
id
_objc_rootAutorelease(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
uintptr_t
_objc_rootRetainCount(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
id
_objc_rootInit(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
id
_objc_rootAllocWithZone(Class cls, malloc_zone_t *zone)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
id
_objc_rootAlloc(Class cls)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
_objc_rootDealloc(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
_objc_rootFinalize(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
malloc_zone_t *
_objc_rootZone(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
uintptr_t
_objc_rootHash(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void *
objc_autoreleasePoolPush(void)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
objc_autoreleasePoolPop(void *context)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT id objc_retain(id obj)
__asm__("_objc_retain")
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT void objc_release(id obj)
__asm__("_objc_release")
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT id objc_autorelease(id obj)
__asm__("_objc_autorelease")
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// wraps objc_autorelease(obj) in a useful way when used with return values
OBJC_EXPORT
id
objc_autoreleaseReturnValue(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// wraps objc_autorelease(objc_retain(obj)) in a useful way when used with return values
OBJC_EXPORT
id
objc_retainAutoreleaseReturnValue(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// called ONLY by ARR by callers to undo the autorelease (if possible), otherwise objc_retain
OBJC_EXPORT
id
objc_retainAutoreleasedReturnValue(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
objc_storeStrong(id *location, id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
id
objc_retainAutorelease(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// obsolete.
OBJC_EXPORT id objc_retain_autorelease(id obj)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
id
objc_loadWeakRetained(id *location)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
id
objc_initWeak(id *addr, id val)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
objc_destroyWeak(id *addr)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
objc_copyWeak(id *to, id *from)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
objc_moveWeak(id *to, id *from)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
_objc_autoreleasePoolPrint(void)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT BOOL objc_should_deallocate(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT void objc_clear_deallocating(id object)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// to make CF link for now
OBJC_EXPORT
void *
_objc_autoreleasePoolPush(void)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
OBJC_EXPORT
void
_objc_autoreleasePoolPop(void *context)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// Extra @encode data for XPC, or NULL
OBJC_EXPORT const char *_protocol_getMethodTypeEncoding(Protocol *p, SEL sel, BOOL isRequiredMethod, BOOL isInstanceMethod)
__OSX_AVAILABLE_STARTING(__MAC_10_8, __IPHONE_6_0);
// API to only be called by classes that provide their own reference count storage
OBJC_EXPORT
void
_objc_deallocOnMainThreadHelper(void *context)
__OSX_AVAILABLE_STARTING(__MAC_10_7, __IPHONE_5_0);
// On async versus sync deallocation and the _dealloc2main flag
//
// Theory:
//
// If order matters, then code must always: [self dealloc].
// If order doesn't matter, then always async should be safe.
//
// Practice:
//
// The _dealloc2main bit is set for GUI objects that may be retained by other
// threads. Once deallocation begins on the main thread, doing more async
// deallocation will at best cause extra UI latency and at worst cause
// use-after-free bugs in unretained delegate style patterns. Yes, this is
// extremely fragile. Yes, in the long run, developers should switch to weak
// references.
//
// Note is NOT safe to do any equality check against the result of
// dispatch_get_current_queue(). The main thread can and does drain more than
// one dispatch queue. That is why we call pthread_main_np().
//
typedef enum {
_OBJC_DEALLOC_OBJECT_NOW = 1, /* call [self dealloc] immediately. */
_OBJC_DEALLOC_OBJECT_LATER = 2 /* call [self dealloc] on the main queue. */
} _objc_object_disposition_t;
#define _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC_STATEMENT(_rc_ivar, _logicStatement) \
-(id)retain { \
/* this will fail to compile if _rc_ivar is an unsigned type */ \
int _retain_count_ivar_must_not_be_unsigned[0L - (__typeof__(_rc_ivar))-1] __attribute__((unused)); \
__typeof__(_rc_ivar) _prev = __sync_fetch_and_add(&_rc_ivar, 2); \
if (_prev < 0) { \
__builtin_trap(); /* BUG: retain of over-released ref */ \
} \
if (_prev & 1) { \
__builtin_trap(); /* BUG: retain of over-released ref (dealloc) */ \
} \
return self; \
} \
-(oneway void)release { \
__typeof__(_rc_ivar) _prev = __sync_fetch_and_sub(&_rc_ivar, 2); \
if (_prev > 0) { \
return; \
} else if (_prev < 0) { \
__builtin_trap(); /* BUG: over-release */ \
} \
/* mark the object as deallocating. */ \
if (!__sync_bool_compare_and_swap(&_rc_ivar, -2, 1)) { \
__builtin_trap(); /* BUG: dangling ref did a retain */ \
} \
_objc_object_disposition_t fate = _logicStatement; \
if (fate == _OBJC_DEALLOC_OBJECT_NOW) { \
[self dealloc]; \
} else if (fate == _OBJC_DEALLOC_OBJECT_LATER) { \
dispatch_barrier_async_f(dispatch_get_main_queue(), self, \
_objc_deallocOnMainThreadHelper); \
} else { \
__builtin_trap(); /* BUG: bogus fate value */ \
} \
} \
-(NSUInteger)retainCount { \
return (__atomic_load_n(&_rc_ivar, __ATOMIC_SEQ_CST) + 2) >> 1; \
} \
-(BOOL)_tryRetain { \
__typeof__(_rc_ivar) _prev; \
do { \
_prev = __atomic_load_n(&_rc_ivar, __ATOMIC_SEQ_CST); \
if (_prev & 1) { \
return 0; \
} else if (_prev == -2) { \
return 0; \
} else if (_prev < -2) { \
__builtin_trap(); /* BUG: over-release elsewhere */ \
} \
} while ( ! __sync_bool_compare_and_swap(&_rc_ivar, _prev, _prev + 2)); \
return 1; \
} \
-(BOOL)_isDeallocating { \
__typeof__(_rc_ivar) _prev = __atomic_load_n(&_rc_ivar, __ATOMIC_SEQ_CST); \
if (_prev == -2) { \
return 1; \
} else if (_prev < -2) { \
__builtin_trap(); /* BUG: over-release elsewhere */ \
} \
return _prev & 1; \
}
#define _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, _dealloc2main) \
_OBJC_SUPPORTED_INLINE_REFCNT_LOGIC_STATEMENT(_rc_ivar, ( (_dealloc2main && !pthread_main_np()) ? _OBJC_DEALLOC_OBJECT_LATER : _OBJC_DEALLOC_OBJECT_NOW ))
#define _OBJC_SUPPORTED_INLINE_REFCNT(_rc_ivar) _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, 0)
#define _OBJC_SUPPORTED_INLINE_REFCNT_WITH_DEALLOC2MAIN(_rc_ivar) _OBJC_SUPPORTED_INLINE_REFCNT_LOGIC(_rc_ivar, 1)
__END_DECLS
#endif

View File

@@ -26,6 +26,7 @@
#import "ASInsetLayoutSpec.h"
#import "ASCenterLayoutSpec.h"
#import "ASBackgroundLayoutSpec.h"
#import "ASInternalHelpers.h"
// Conveniences for making nodes named a certain way
#define DeclareNodeNamed(n) ASDisplayNode *n = [[ASDisplayNode alloc] init]; n.debugName = @#n
@@ -90,6 +91,11 @@ for (ASDisplayNode *n in @[ nodes ]) {\
@property (nonatomic, copy) CGSize(^calculateSizeBlock)(ASTestDisplayNode *node, CGSize size);
@property (nonatomic) BOOL hasFetchedData;
@property (nonatomic, nullable) UIGestureRecognizer *gestureRecognizer;
@property (nonatomic, nullable) id idGestureRecognizer;
@property (nonatomic, nullable) UIImage *bigImage;
@property (nonatomic, nullable) NSArray *randomProperty;
@property (nonatomic) BOOL displayRangeStateChangedToYES;
@property (nonatomic) BOOL displayRangeStateChangedToNO;
@@ -1046,23 +1052,47 @@ static inline BOOL _CGPointEqualToPointWithEpsilon(CGPoint point1, CGPoint point
XCTAssertNil(weakSubnode);
}
- (void)testMainThreadDealloc
- (void)testThatUIKitDeallocationTrampoliningWorks
{
__block BOOL didDealloc = NO;
NS_VALID_UNTIL_END_OF_SCOPE __weak UIGestureRecognizer *weakRecognizer = nil;
NS_VALID_UNTIL_END_OF_SCOPE __weak UIGestureRecognizer *weakIdRecognizer = nil;
NS_VALID_UNTIL_END_OF_SCOPE __weak UIView *weakView = nil;
NS_VALID_UNTIL_END_OF_SCOPE __weak CALayer *weakLayer = nil;
NS_VALID_UNTIL_END_OF_SCOPE __weak UIImage *weakImage = nil;
NS_VALID_UNTIL_END_OF_SCOPE __weak NSArray *weakArray = nil;
__block NS_VALID_UNTIL_END_OF_SCOPE ASTestDisplayNode *node = nil;
@autoreleasepool {
node = [[ASTestDisplayNode alloc] init];
node.gestureRecognizer = [[UIGestureRecognizer alloc] init];
node.idGestureRecognizer = [[UIGestureRecognizer alloc] init];
UIGraphicsBeginImageContextWithOptions(CGSizeMake(1000, 1000), YES, 1);
node.bigImage = UIGraphicsGetImageFromCurrentImageContext();
node.randomProperty = @[ @"Hello, world!" ];
UIGraphicsEndImageContext();
weakImage = node.bigImage;
weakView = node.view;
weakLayer = node.layer;
weakArray = node.randomProperty;
weakIdRecognizer = node.idGestureRecognizer;
weakRecognizer = node.gestureRecognizer;
}
[self executeOffThread:^{
@autoreleasepool {
ASTestDisplayNode *node = [[ASTestDisplayNode alloc] init];
node.willDeallocBlock = ^(__unsafe_unretained ASDisplayNode *n){
XCTAssertTrue([NSThread isMainThread], @"unexpected node dealloc %@ %@", n, [NSThread currentThread]);
didDealloc = YES;
};
}
node = nil;
}];
// deallocation should be queued on the main runloop; give it a chance
ASDisplayNodeRunRunLoopUntilBlockIsTrue(^BOOL{ return didDealloc; });
XCTAssertTrue(didDealloc, @"unexpected node lifetime");
XCTAssertNotNil(weakRecognizer, @"UIGestureRecognizer ivars should be deallocated on main.");
XCTAssertNotNil(weakIdRecognizer, @"UIGestureRecognizer-backed 'id' ivars should be deallocated on main.");
XCTAssertNotNil(weakView, @"UIView ivars should be deallocated on main.");
XCTAssertNotNil(weakLayer, @"CALayer ivars should be deallocated on main.");
XCTAssertNil(weakImage, @"UIImage ivars should be deallocated normally.");
XCTAssertNil(weakArray, @"NSArray ivars should be deallocated normally.");
XCTAssertNil(node);
[self expectationForPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nonnull evaluatedObject, NSDictionary<NSString *,id> * _Nullable bindings) {
return (weakRecognizer == nil && weakIdRecognizer == nil && weakView == nil);
}] evaluatedWithObject:(id)kCFNull handler:nil];
[self waitForExpectationsWithTimeout:10 handler:nil];
}
- (void)testSubnodes