From 9ef97f49657e6e690bb222ef4634bbdcc453c3c2 Mon Sep 17 00:00:00 2001 From: Greg Bolsinga Date: Thu, 30 Jul 2020 12:02:46 -0700 Subject: [PATCH] Remove background deallocation helper code (#1890) * Remove background deallocation helper code Last use removed in Texture with #1840, now PINS no longer uses it either. Less OOMs is so nice. * remove methods from docs --- Source/ASRunLoopQueue.h | 13 +---- Source/ASRunLoopQueue.mm | 61 ------------------------ Source/Private/ASInternalHelpers.h | 3 -- Source/Private/ASInternalHelpers.mm | 5 -- Tests/Common/ASTestCase.mm | 3 -- docs/_docs/development/how-to-debug.md | 4 -- docs/_docs/development/node-lifecycle.md | 6 --- docs/_docs/development/threading.md | 20 -------- 8 files changed, 1 insertion(+), 114 deletions(-) diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 298fe76ca..c66a5dbfb 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -32,8 +32,7 @@ AS_SUBCLASSING_RESTRICTED * * @discussion You may pass @c nil for the handler if you simply want the objects to * be retained at enqueue time, and released during the run loop step. This is useful - * for creating a "main deallocation queue", as @c ASDeallocQueue creates its own - * worker thread with its own run loop. + * for creating a "main deallocation queue". */ - (instancetype)initWithRunLoop:(CFRunLoopRef)runloop retainObjects:(BOOL)retainsObjects @@ -78,14 +77,4 @@ NS_INLINE ASCATransactionQueue *ASCATransactionQueueGet(void) { return _ASSharedCATransactionQueue; } -@interface ASDeallocQueue : NSObject - -+ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED; - -- (void)drain; - -- (void)releaseObjectInBackground:(id __strong _Nullable * _Nonnull)objectPtr; - -@end - NS_ASSUME_NONNULL_END diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index fd896af89..8c917b07a 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -27,67 +27,6 @@ static void runLoopSourceCallback(void *info) { #endif } -#pragma mark - ASDeallocQueue - -@implementation ASDeallocQueue { - std::vector _queue; - AS::Mutex _lock; -} - -+ (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED -{ - static ASDeallocQueue *deallocQueue = nil; - static dispatch_once_t onceToken; - dispatch_once(&onceToken, ^{ - deallocQueue = [[ASDeallocQueue alloc] init]; - }); - return deallocQueue; -} - -- (void)dealloc -{ - ASDisplayNodeFailAssert(@"Singleton should not dealloc."); -} - -- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr -{ - NSParameterAssert(objectPtr != NULL); - - // Cast to CFType so we can manipulate retain count manually. - const auto cfPtr = (CFTypeRef *)(void *)objectPtr; - if (!cfPtr || !*cfPtr) { - return; - } - - _lock.lock(); - const auto isFirstEntry = _queue.empty(); - // Push the pointer into our queue and clear their pointer. - // This "steals" the +1 from ARC and nils their pointer so they can't - // access or release the object. - _queue.push_back(*cfPtr); - *cfPtr = NULL; - _lock.unlock(); - - if (isFirstEntry) { - dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.100 * NSEC_PER_SEC)), dispatch_get_global_queue(QOS_CLASS_DEFAULT, 0), ^{ - [self drain]; - }); - } -} - -- (void)drain -{ - _lock.lock(); - const auto q = std::move(_queue); - _lock.unlock(); - for (CFTypeRef ref : q) { - // NOTE: Could check that retain count is 1 and retry later if not. - CFRelease(ref); - } -} - -@end - @implementation ASAbstractRunLoopQueue - (instancetype)init diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index 69a8eabc2..29fa26897 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -34,9 +34,6 @@ ASDK_EXTERN void ASPerformBlockOnMainThread(void (^block)(void)); /// Dispatches the given block to a background queue with priority of DISPATCH_QUEUE_PRIORITY_DEFAULT if not already run on a background queue ASDK_EXTERN void ASPerformBlockOnBackgroundThread(void (^block)(void)); // DISPATCH_QUEUE_PRIORITY_DEFAULT -/// For deallocation of objects on a background thread without GCD overhead / thread explosion -ASDK_EXTERN void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object); - ASDK_EXTERN CGFloat ASScreenScale(void); ASDK_EXTERN CGSize ASFloorSizeValues(CGSize s); diff --git a/Source/Private/ASInternalHelpers.mm b/Source/Private/ASInternalHelpers.mm index 7811c3b6b..4d6e1fde8 100644 --- a/Source/Private/ASInternalHelpers.mm +++ b/Source/Private/ASInternalHelpers.mm @@ -140,11 +140,6 @@ void ASPerformBlockOnBackgroundThread(void (^block)(void)) } } -void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object) -{ - [[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object]; -} - Class _Nullable ASGetClassFromType(const char * _Nullable type) { // Class types all start with @" diff --git a/Tests/Common/ASTestCase.mm b/Tests/Common/ASTestCase.mm index 30b42bde4..81236d271 100644 --- a/Tests/Common/ASTestCase.mm +++ b/Tests/Common/ASTestCase.mm @@ -85,9 +85,6 @@ - (void)invokeTest @autoreleasepool { [super invokeTest]; } - - // Now that the autorelease pool is drained, drain the dealloc queue also. - [[ASDeallocQueue sharedDeallocationQueue] drain]; } + (ASTestCase *)currentTestCase diff --git a/docs/_docs/development/how-to-debug.md b/docs/_docs/development/how-to-debug.md index 8098cd4a8..75f8c2ec7 100644 --- a/docs/_docs/development/how-to-debug.md +++ b/docs/_docs/development/how-to-debug.md @@ -75,10 +75,6 @@ Here is the collection view's `dealloc` [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); } ``` diff --git a/docs/_docs/development/node-lifecycle.md b/docs/_docs/development/node-lifecycle.md index 6484eeed5..3725b8770 100644 --- a/docs/_docs/development/node-lifecycle.md +++ b/docs/_docs/development/node-lifecycle.md @@ -31,12 +31,6 @@ For more details, look into ASCollectionLayout, ASCollectionGalleryLayoutDelegat As mentioned above, since ASCellNodes are not meant to be reused, they have a longer lifecycle compared to the view or layer that they encapsulate or their corresponding UICollectionViewCell or UITableViewCell. ASCellNodes are deallocated when they are no longer used and removed from the container node. This can occur after a batch update that includes a reload data or deletion, or after the container node is no longer used and thus released. -For the latter case in which the container node is no longer used and thus released, their cell nodes are not released immediately. That is because collection and table nodes might hold a large number of cell nodes and releasing all of them (and their subnodes, more on this later) at the same time can cause a noticeable delay. To avoid that, ASCollectionNode and ASTableNode release their instance variables, most noticeably an ASDataController instance, on a background thread using a helper called ASDeallocQueue. Since the ASDataController instance is the true owner of all cell nodes -- it has a strong reference to all of them --, all of those cell nodes are deallocated off the main thread as well. As a result, you can expect a delay from which a collection or table view is released until all the cell nodes are fully released and their memory reclaimed. It's important to remember this when debugging memory leaks: objects referenced by the data controller may take a bit to show as dealloced by Instruments. - -## ASDeallocQueue - -As mentioned above, ASDeallocQueue helps to defer the deallocation of objects given to it by increasing the reference count of each object -- essentially retaining them and acting as their sole owner -- and then release them later on a background thread. - # Nodes that are not managed by containers These are nodes that are often directly created by client code, such as direct and indirect subnodes of cell nodes. When a node is added to a parent node, the parent node retains it until it's removed from the parent node, or until the parent node is deallocated. As a result, if the subnode is not retained by client code in any other way or if it's not removed from the parent node, the subnode's lifecycle is tied to the parent node's lifecycle. In addition, since nodes often live in a hierarchy, the entire node hierarchy has the same lifecycle as the root node's. Lastly, if the root node is managed by a node container -- directly in the case of ASDKViewController and the like, or indirectly as a cell node of a collection or table node --, then the entire node hierarchy is managed by the node container. diff --git a/docs/_docs/development/threading.md b/docs/_docs/development/threading.md index fad2e994c..06ea796cd 100644 --- a/docs/_docs/development/threading.md +++ b/docs/_docs/development/threading.md @@ -250,25 +250,6 @@ Since this lock is also shared, it will prevent other routines from entering unt __Method 2__ -An alternative method is to manage the duration of the lock hold manually rather than using the runtime and scope. You must remember to unlock. - -``` -@implementation ASDeallocQueue { - ASDN::Mutex _lock; -} - -- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr -{ - NSParameterAssert(objectPtr != NULL); // do I actually need to lock ? - // other conditions or non-shared tasks - _lock.lock(); - sharedObject.modify(newData); - _lock.unlock(); -} -``` - -__Method 3__ - `ASThread` provides `ASLockScopeSelf()`. This is a convenience over `ASLockScopeUnowned()`. This will unlock itself once the scope in which the lock was created is released. Only use this when you are confident that the lock should remain until scope is complete. You can only have one lock defined for `self`, thus it will block all other branches. ``` @@ -311,7 +292,6 @@ API | Description | `ASDisplayNodeAssertMainThread();` | Place this at the start of the every function definition that performs work synchronously on the main thread. `ASPerformBlockOnMainThread(block)` | If on main thread already, run block synchronously, otherwise use `dispatch_async(dispatch_get_main_queue(block))` `ASPerformMainThreadDeallocation(&object)` | Schedule async deallocation of UIKit components -`ASPerformBackgroundDeallocation(&object)` | Schedule async deallocation of __non-UIKit__ objects `ASPerformBlockOnBackgroundThread(block)` | Perform work on background