mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 22:55:00 +00:00
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:
@@ -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 */,
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 */
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user