diff --git a/CHANGELOG.md b/CHANGELOG.md index 150f8fb3fc..96419cc59c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -54,6 +54,7 @@ - [ASCollectionNode/ASTableNode] Fix a crash occurs while remeasuring cell nodes. [Huy Nguyen](https://github.com/nguyenhuy) [#917](https://github.com/TextureGroup/Texture/pull/917) - Fix an issue where ASConfigurationDelegate would not call out for "control" users. If set, it now receives events whenever an experimental feature decision point occurs, whether it's enabled or not. [Adlai Holler](https://github.com/Adlai-Holler) - [ASDisplayNode] Fix an issue that causes a node to sometimes return an outdated calculated size or size range. [Huy Nguyen](https://github.com/nguyenhuy) [#808](https://github.com/TextureGroup/Texture/pull/808) +- Add an experimental deallocation queue implementation that's more efficient. [Adlai Holler](https://github.com/Adlai-Holler) ## 2.6 - [Xcode 9] Updated to require Xcode 9 (to fix warnings) [Garrett Moon](https://github.com/garrettmoon) diff --git a/Schemas/configuration.json b/Schemas/configuration.json index 08eb64d830..ab1d592b4b 100644 --- a/Schemas/configuration.json +++ b/Schemas/configuration.json @@ -18,7 +18,8 @@ "exp_interface_state_coalesce", "exp_unfair_lock", "exp_infer_layer_defaults", - "exp_network_image_queue" + "exp_network_image_queue", + "exp_dealloc_queue_v2" ] } } diff --git a/Source/ASExperimentalFeatures.h b/Source/ASExperimentalFeatures.h index 382415ab3b..6b418d3b44 100644 --- a/Source/ASExperimentalFeatures.h +++ b/Source/ASExperimentalFeatures.h @@ -26,6 +26,7 @@ typedef NS_OPTIONS(NSUInteger, ASExperimentalFeatures) { ASExperimentalUnfairLock = 1 << 3, // exp_unfair_lock ASExperimentalLayerDefaults = 1 << 4, // exp_infer_layer_defaults ASExperimentalNetworkImageQueue = 1 << 5, // exp_network_image_queue + ASExperimentalDeallocQueue = 1 << 6, // exp_dealloc_queue_v2 ASExperimentalFeatureAll = 0xFFFFFFFF }; diff --git a/Source/ASExperimentalFeatures.m b/Source/ASExperimentalFeatures.m index 3636cd0e8e..90772f4311 100644 --- a/Source/ASExperimentalFeatures.m +++ b/Source/ASExperimentalFeatures.m @@ -19,7 +19,8 @@ NSArray *ASExperimentalFeaturesGetNames(ASExperimentalFeatures flags @"exp_interface_state_coalesce", @"exp_unfair_lock", @"exp_infer_layer_defaults", - @"exp_network_image_queue"])); + @"exp_network_image_queue", + @"exp_dealloc_queue_v2"])); if (flags == ASExperimentalFeatureAll) { return allNames; diff --git a/Source/ASRunLoopQueue.h b/Source/ASRunLoopQueue.h index 291cc4abf4..c434d3c95a 100644 --- a/Source/ASRunLoopQueue.h +++ b/Source/ASRunLoopQueue.h @@ -76,14 +76,12 @@ AS_SUBCLASSING_RESTRICTED @end - -AS_SUBCLASSING_RESTRICTED @interface ASDeallocQueue : NSObject @property (class, atomic, readonly) ASDeallocQueue *sharedDeallocationQueue; + (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED; -- (void)test_drain; +- (void)drain; - (void)releaseObjectInBackground:(id __strong _Nullable * _Nonnull)objectPtr; diff --git a/Source/ASRunLoopQueue.mm b/Source/ASRunLoopQueue.mm index d30a15a390..607f985e65 100644 --- a/Source/ASRunLoopQueue.mm +++ b/Source/ASRunLoopQueue.mm @@ -39,23 +39,41 @@ static void runLoopSourceCallback(void *info) { #pragma mark - ASDeallocQueue -@implementation ASDeallocQueue { - NSThread *_thread; - NSCondition *_condition; - std::deque _queue; - ASDN::RecursiveMutex _queueLock; -} +@interface ASDeallocQueueV1 : ASDeallocQueue +@end +@interface ASDeallocQueueV2 : ASDeallocQueue +@end + +@implementation ASDeallocQueue + (ASDeallocQueue *)sharedDeallocationQueue NS_RETURNS_RETAINED { static ASDeallocQueue *deallocQueue = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ - deallocQueue = [[ASDeallocQueue alloc] init]; + if (ASActivateExperimentalFeature(ASExperimentalDeallocQueue)) { + deallocQueue = [[ASDeallocQueueV2 alloc] init]; + } else { + deallocQueue = [[ASDeallocQueueV1 alloc] init]; + } }); return deallocQueue; } +- (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr +{ + ASDisplayNodeFailAssert(@"Abstract method."); +} + +@end + +@implementation ASDeallocQueueV1 { + NSThread *_thread; + NSCondition *_condition; + std::deque _queue; + ASDN::RecursiveMutex _queueLock; +} + - (void)releaseObjectInBackground:(id _Nullable __strong *)objectPtr { if (objectPtr != NULL && *objectPtr != nil) { @@ -147,12 +165,12 @@ static void runLoopSourceCallback(void *info) { _thread = nil; } -- (void)test_drain +- (void)drain { - [self performSelector:@selector(_test_drain) onThread:_thread withObject:nil waitUntilDone:YES]; + [self performSelector:@selector(_drain) onThread:_thread withObject:nil waitUntilDone:YES]; } -- (void)_test_drain +- (void)_drain { while (true) { @autoreleasepool { @@ -182,6 +200,57 @@ static void runLoopSourceCallback(void *info) { @end +@implementation ASDeallocQueueV2 { + std::vector _queue; + ASDN::Mutex _lock; +} + +- (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. + auto cfPtr = (CFTypeRef *)(void *)objectPtr; + if (!cfPtr || !*cfPtr) { + return; + } + + _lock.lock(); + 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 +{ + @autoreleasepool { + _lock.lock(); + auto q = std::move(_queue); + _lock.unlock(); + for (auto ref : q) { + // NOTE: Could check that retain count is 1 and retry later if not. + CFRelease(ref); + } + } +} + +@end + #if AS_KDEBUG_ENABLE /** * This is real, private CA API. Valid as of iOS 10. diff --git a/Tests/Common/ASTestCase.m b/Tests/Common/ASTestCase.m index fb059e7ed2..9a2f7d1d71 100644 --- a/Tests/Common/ASTestCase.m +++ b/Tests/Common/ASTestCase.m @@ -95,7 +95,7 @@ static __weak ASTestCase *currentTestCase; } // Now that the autorelease pool is drained, drain the dealloc queue also. - [[ASDeallocQueue sharedDeallocationQueue] test_drain]; + [[ASDeallocQueue sharedDeallocationQueue] drain]; } + (ASTestCase *)currentTestCase diff --git a/examples/CatDealsCollectionView/Sample/AppDelegate.m b/examples/CatDealsCollectionView/Sample/AppDelegate.m index 564ba05b5d..b30facbbb5 100644 --- a/examples/CatDealsCollectionView/Sample/AppDelegate.m +++ b/examples/CatDealsCollectionView/Sample/AppDelegate.m @@ -1,18 +1,18 @@ // // AppDelegate.m -// Sample +// Texture // // Copyright (c) 2014-present, Facebook, Inc. All rights reserved. // This source code is licensed under the BSD-style license found in the -// LICENSE file in the root directory of this source tree. An additional grant -// of patent rights can be found in the PATENTS file in the same directory. +// LICENSE file in the /ASDK-Licenses directory of this source tree. An additional +// grant of patent rights can be found in the PATENTS file in the same directory. // -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// FACEBOOK BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN -// ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -// CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. +// Modifications to this file made after 4/13/2017 are: Copyright (c) through the present, +// Pinterest, Inc. Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 // #import "AppDelegate.h" @@ -55,3 +55,14 @@ } @end + +@implementation ASConfiguration (UserProvided) + ++ (ASConfiguration *)textureConfiguration +{ + ASConfiguration *cfg = [[ASConfiguration alloc] init]; + cfg.experimentalFeatures = ASExperimentalDeallocQueue; + return cfg; +} + +@end