From f6c5dc31e2176f1f346693f6368db94b282ad663 Mon Sep 17 00:00:00 2001 From: Adlai Holler Date: Thu, 7 Jun 2018 09:41:58 -0700 Subject: [PATCH] Generalize the main thread ivar dealloc stuff (#959) --- AsyncDisplayKit.xcodeproj/project.pbxproj | 8 + CHANGELOG.md | 1 + Source/ASDisplayNode.mm | 115 +------------ Source/ASDisplayNodeExtras.mm | 3 + Source/ASMainThreadDeallocation.h | 47 +++++ Source/ASMainThreadDeallocation.mm | 201 ++++++++++++++++++++++ Source/AsyncDisplayKit.h | 1 + Source/Private/ASInternalHelpers.h | 2 - Source/Private/ASInternalHelpers.m | 27 --- Source/TextKit/ASTextKitComponents.mm | 6 + 10 files changed, 269 insertions(+), 142 deletions(-) create mode 100644 Source/ASMainThreadDeallocation.h create mode 100644 Source/ASMainThreadDeallocation.mm diff --git a/AsyncDisplayKit.xcodeproj/project.pbxproj b/AsyncDisplayKit.xcodeproj/project.pbxproj index 24a5d473f..4e8a7a19a 100644 --- a/AsyncDisplayKit.xcodeproj/project.pbxproj +++ b/AsyncDisplayKit.xcodeproj/project.pbxproj @@ -393,6 +393,8 @@ CCB1F95C1EFB6350009C7475 /* ASSignpost.h in Headers */ = {isa = PBXBuildFile; fileRef = CCB1F95B1EFB6316009C7475 /* ASSignpost.h */; }; CCB2F34D1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m in Sources */ = {isa = PBXBuildFile; fileRef = CCB2F34C1D63CCC6004E6DE9 /* ASDisplayNodeSnapshotTests.m */; }; CCBBBF5D1EB161760069AA91 /* ASRangeManagingNode.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCBDDD0520C62A2D00CBA922 /* ASMainThreadDeallocation.h in Headers */ = {isa = PBXBuildFile; fileRef = CCBDDD0320C62A2D00CBA922 /* ASMainThreadDeallocation.h */; settings = {ATTRIBUTES = (Public, ); }; }; + CCBDDD0620C62A2D00CBA922 /* ASMainThreadDeallocation.mm in Sources */ = {isa = PBXBuildFile; fileRef = CCBDDD0420C62A2D00CBA922 /* ASMainThreadDeallocation.mm */; }; CCCCCCD51EC3EF060087FE10 /* ASTextDebugOption.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */; }; CCCCCCD61EC3EF060087FE10 /* ASTextDebugOption.m in Sources */ = {isa = PBXBuildFile; fileRef = CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.m */; }; CCCCCCD71EC3EF060087FE10 /* ASTextInput.h in Headers */ = {isa = PBXBuildFile; fileRef = CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */; }; @@ -907,6 +909,8 @@ CCBBBF5C1EB161760069AA91 /* ASRangeManagingNode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASRangeManagingNode.h; sourceTree = ""; }; CCBD05DE1E4147B000D18509 /* ASIGListAdapterBasedDataSource.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASIGListAdapterBasedDataSource.m; sourceTree = ""; }; CCBD05DF1E4147B000D18509 /* ASIGListAdapterBasedDataSource.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASIGListAdapterBasedDataSource.h; sourceTree = ""; }; + CCBDDD0320C62A2D00CBA922 /* ASMainThreadDeallocation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ASMainThreadDeallocation.h; sourceTree = ""; }; + CCBDDD0420C62A2D00CBA922 /* ASMainThreadDeallocation.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = ASMainThreadDeallocation.mm; sourceTree = ""; }; CCCCCCC31EC3EF060087FE10 /* ASTextDebugOption.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextDebugOption.h; sourceTree = ""; }; CCCCCCC41EC3EF060087FE10 /* ASTextDebugOption.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ASTextDebugOption.m; sourceTree = ""; }; CCCCCCC51EC3EF060087FE10 /* ASTextInput.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ASTextInput.h; sourceTree = ""; }; @@ -1165,6 +1169,8 @@ 058D09DE195D050800B7D73C /* ASImageNode.mm */, 68355B2E1CB5799E001D4E68 /* ASImageNode+AnimatedImage.mm */, CCF1FF5D20C4785000AAD8FC /* ASLocking.h */, + CCBDDD0320C62A2D00CBA922 /* ASMainThreadDeallocation.h */, + CCBDDD0420C62A2D00CBA922 /* ASMainThreadDeallocation.mm */, 92DD2FE11BF4B97E0074C9DD /* ASMapNode.h */, 92DD2FE21BF4B97E0074C9DD /* ASMapNode.mm */, 0516FA3E1A1563D200B4EBED /* ASMultiplexImageNode.h */, @@ -1822,6 +1828,7 @@ 693A1DCA1ECC944E00D0C9D2 /* IGListAdapter+AsyncDisplayKit.h in Headers */, E5E2D72E1EA780C4005C24C6 /* ASCollectionGalleryLayoutDelegate.h in Headers */, E58E9E461E941D74004CFC59 /* ASCollectionLayoutDelegate.h in Headers */, + CCBDDD0520C62A2D00CBA922 /* ASMainThreadDeallocation.h in Headers */, CCAA0B7F206ADBF30057B336 /* ASRecursiveUnfairLock.h in Headers */, E5E281741E71C833006B67C2 /* ASCollectionLayoutState.h in Headers */, E5B077FF1E69F4EB00C24B5B /* ASElementMap.h in Headers */, @@ -2377,6 +2384,7 @@ B35061F91B010EFD0018CF92 /* ASControlNode.mm in Sources */, 8021EC1F1D2B00B100799119 /* UIImage+ASConvenience.m in Sources */, CCAA0B80206ADBF30057B336 /* ASRecursiveUnfairLock.m in Sources */, + CCBDDD0620C62A2D00CBA922 /* ASMainThreadDeallocation.mm in Sources */, B35062181B010EFD0018CF92 /* ASDataController.mm in Sources */, CCB1F95A1EFB60A5009C7475 /* ASLog.m in Sources */, 767E7F8E1C90191D0066C000 /* AsyncDisplayKit+Debug.m in Sources */, diff --git a/CHANGELOG.md b/CHANGELOG.md index 17a6e7684..aba12d810 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ - Add snapshot test for astextnode2. [Max Wang](https://github.com/wsdwsd0829) [#935](https://github.com/TextureGroup/Texture/pull/935) - Internal housekeeping on the async transaction (rendering) system. [Adlai Holler](https://github.com/Adlai-Holler) - Add new protocol `ASLocking` that extends `NSLocking` with `tryLock`, and allows taking multiple locks safely. [Adlai Holler](https://github.com/Adlai-Holler) +- Make the main thread ivar deallocation system available to other classes. Plus a little optimization. See `ASMainThreadDeallocation.h`. [Adlai Holler](https://github.com/Adlai-Holler) [#959](https://github.com/TextureGroup/Texture/pull/959) ## 2.7 - Fix pager node for interface coalescing. [Max Wang](https://github.com/wsdwsd0829) [#877](https://github.com/TextureGroup/Texture/pull/877) diff --git a/Source/ASDisplayNode.mm b/Source/ASDisplayNode.mm index 0b3dc3709..e70552284 100644 --- a/Source/ASDisplayNode.mm +++ b/Source/ASDisplayNode.mm @@ -44,6 +44,7 @@ #import #import #import +#import #import #import #import @@ -428,124 +429,12 @@ - (void)dealloc for (ASDisplayNode *subnode in _subnodes) [subnode _setSupernode:nil]; - // Trampoline any UIKit ivars' deallocation to main - if (ASDisplayNodeThreadIsMain() == NO) { - [self _scheduleIvarsForMainDeallocation]; - } + [self scheduleIvarsForMainThreadDeallocation]; // TODO: Remove this? If supernode isn't already nil, this method isn't dealloc-safe anyway. [self _setSupernode:nil]; } -- (void)_scheduleIvarsForMainDeallocation -{ - 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 (value == nil) { - continue; - } - - if (ASClassRequiresMainThreadDeallocation(object_getClass(value))) { - as_log_debug(ASMainThreadDeallocationLog(), "%@: Trampolining ivar '%s' value %@ for main deallocation.", self, ivar_getName(ivar), value); - - // Before scheduling the ivar for main thread deallocation we have clear out the ivar, otherwise we can run - // into a race condition where the main queue is drained earlier than this node is deallocated and the ivar - // is still deallocated on a background thread - object_setIvar(self, ivar, nil); - - ASPerformMainThreadDeallocation(&value); - } else { - as_log_debug(ASMainThreadDeallocationLog(), "%@: Not trampolining ivar '%s' value %@.", self, 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. - * - * Result is of type NSValue<[Ivar]> - */ -+ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation NS_RETURNS_RETAINED -{ - static NSCache *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 (type != NULL && strcmp(type, @encode(id)) == 0) { - // If it's `id` we have to include it just in case. - resultIvars[resultCount] = ivar; - resultCount += 1; - as_log_debug(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for possible main deallocation due to type id", self, ivar_getName(ivar)); - } else { - // If it's an ivar with a static type, check the type. - Class c = ASGetClassFromType(type); - if (ASClassRequiresMainThreadDeallocation(c)) { - resultIvars[resultCount] = ivar; - resultCount += 1; - as_log_debug(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for main deallocation due to class %@", self, ivar_getName(ivar), c); - } else { - as_log_debug(ASMainThreadDeallocationLog(), "%@: Skipping ivar '%s' for main deallocation.", self, 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 - Loading - (BOOL)_locked_shouldLoadViewOrLayer diff --git a/Source/ASDisplayNodeExtras.mm b/Source/ASDisplayNodeExtras.mm index 385343ae6..480aa1cf9 100644 --- a/Source/ASDisplayNodeExtras.mm +++ b/Source/ASDisplayNodeExtras.mm @@ -36,6 +36,9 @@ extern void ASPerformMainThreadDeallocation(id _Nullable __strong * _Nonnull obj }); if (objectPtr != NULL && *objectPtr != nil) { + // TODO: If ASRunLoopQueue supported an "unsafe_unretained" mode, we could + // transfer the caller's +1 into it and save the retain/release pair. + // Lock queue while enqueuing and releasing, so that there's no risk // that the queue will release before we get a chance to release. [queue lock]; diff --git a/Source/ASMainThreadDeallocation.h b/Source/ASMainThreadDeallocation.h new file mode 100644 index 000000000..622705f58 --- /dev/null +++ b/Source/ASMainThreadDeallocation.h @@ -0,0 +1,47 @@ +// +// ASMainThreadDeallocation.h +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface NSObject (ASMainThreadIvarTeardown) + +/** + * Call this from -dealloc to schedule this instance's + * ivars for main thread deallocation as needed. + * + * This method includes a check for whether it's on the main thread, + * and it will do nothing in that case. + */ +- (void)scheduleIvarsForMainThreadDeallocation; + +@end + +@interface NSObject (ASNeedsMainThreadDeallocation) + +/** + * Override this property to indicate that instances of this + * class need to be deallocated on the main thread. + * You do not access this property yourself. + * + * The NSObject implementation returns NO if the class name has + * a prefix UI, AV, or CA. This property is also overridden to + * return fixed values for other common classes, such as UIImage, + * UIGestureRecognizer, and UIResponder. + */ +@property (class, readonly) BOOL needsMainThreadDeallocation; + +@end + + +NS_ASSUME_NONNULL_END diff --git a/Source/ASMainThreadDeallocation.mm b/Source/ASMainThreadDeallocation.mm new file mode 100644 index 000000000..22763d820 --- /dev/null +++ b/Source/ASMainThreadDeallocation.mm @@ -0,0 +1,201 @@ +// +// ASMainThreadDeallocation.mm +// Texture +// +// Copyright (c) 2018-present, Pinterest, Inc. All rights reserved. +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// + +#import + +#import +#import +#import +#import +#import + +#import +#import + +@implementation NSObject (ASMainThreadIvarTeardown) + +- (void)scheduleIvarsForMainThreadDeallocation +{ + if (ASDisplayNodeThreadIsMain()) { + return; + } + + 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 (value == nil) { + continue; + } + + if ([object_getClass(value) needsMainThreadDeallocation]) { + as_log_debug(ASMainThreadDeallocationLog(), "%@: Trampolining ivar '%s' value %@ for main deallocation.", self, ivar_getName(ivar), value); + + // Release the ivar's reference before handing the object to the queue so we + // don't risk holding onto it longer than the queue does. + object_setIvar(self, ivar, nil); + + ASPerformMainThreadDeallocation(&value); + } else { + as_log_debug(ASMainThreadDeallocationLog(), "%@: Not trampolining ivar '%s' value %@.", self, 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. + * + * Result is of type NSValue<[Ivar]> + */ ++ (NSValue * _Nonnull)_ivarsThatMayNeedMainDeallocation NS_RETURNS_RETAINED +{ + static NSCache *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]; + + // NOTE: Would be great to exclude weak/unowned ivars, since we don't + // release them. Unfortunately the objc_ivar_management access is private and + // class_getWeakIvarLayout does not have a well-defined structure. + + const char *type = ivar_getTypeEncoding(ivar); + + if (type != NULL && strcmp(type, @encode(id)) == 0) { + // If it's `id` we have to include it just in case. + resultIvars[resultCount] = ivar; + resultCount += 1; + as_log_verbose(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for possible main deallocation due to type id", self, ivar_getName(ivar)); + } else { + // If it's an ivar with a static type, check the type. + Class c = ASGetClassFromType(type); + if ([c needsMainThreadDeallocation]) { + resultIvars[resultCount] = ivar; + resultCount += 1; + as_log_verbose(ASMainThreadDeallocationLog(), "%@: Marking ivar '%s' for main deallocation due to class %@", self, ivar_getName(ivar), c); + } else { + as_log_verbose(ASMainThreadDeallocationLog(), "%@: Skipping ivar '%s' for main deallocation.", self, 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; +} + +@end + +@implementation NSObject (ASNeedsMainThreadDeallocation) + ++ (BOOL)needsMainThreadDeallocation +{ + auto name = class_getName(self); + if (0 == strncmp(name, "AV", 2) || 0 == strncmp(name, "UI", 2) || 0 == strncmp(name, "CA", 2)) { + return YES; + } + return NO; +} + +@end + +@implementation CALayer (ASNeedsMainThreadDeallocation) + ++ (BOOL)needsMainThreadDeallocation +{ + return YES; +} + +@end + +@implementation UIColor (ASNeedsMainThreadDeallocation) + ++ (BOOL)needsMainThreadDeallocation +{ + return NO; +} + +@end + +@implementation UIGestureRecognizer (ASNeedsMainThreadDeallocation) + ++ (BOOL)needsMainThreadDeallocation +{ + return YES; +} + +@end + +@implementation UIImage (ASNeedsMainThreadDeallocation) + ++ (BOOL)needsMainThreadDeallocation +{ + return NO; +} + +@end + +@implementation UIResponder (ASNeedsMainThreadDeallocation) + ++ (BOOL)needsMainThreadDeallocation +{ + return YES; +} + +@end diff --git a/Source/AsyncDisplayKit.h b/Source/AsyncDisplayKit.h index f96084001..8a1c73bf4 100644 --- a/Source/AsyncDisplayKit.h +++ b/Source/AsyncDisplayKit.h @@ -112,6 +112,7 @@ #import #import #import +#import #import #import #import diff --git a/Source/Private/ASInternalHelpers.h b/Source/Private/ASInternalHelpers.h index d5c1e035b..7bfca7242 100644 --- a/Source/Private/ASInternalHelpers.h +++ b/Source/Private/ASInternalHelpers.h @@ -59,8 +59,6 @@ CGFloat ASCeilPixelValue(CGFloat f); CGFloat ASRoundPixelValue(CGFloat f); -BOOL ASClassRequiresMainThreadDeallocation(Class _Nullable c); - Class _Nullable ASGetClassFromType(const char * _Nullable type); ASDISPLAYNODE_EXTERN_C_END diff --git a/Source/Private/ASInternalHelpers.m b/Source/Private/ASInternalHelpers.m index 34b9bd586..d34f15d35 100644 --- a/Source/Private/ASInternalHelpers.m +++ b/Source/Private/ASInternalHelpers.m @@ -127,33 +127,6 @@ void ASPerformBackgroundDeallocation(id __strong _Nullable * _Nonnull object) [[ASDeallocQueue sharedDeallocationQueue] releaseObjectInBackground:object]; } -BOOL ASClassRequiresMainThreadDeallocation(Class c) -{ - // Specific classes - if (c == [UIImage class] || c == [UIColor class]) { - return NO; - } - - if ([c isSubclassOfClass:[UIResponder class]] - || [c isSubclassOfClass:[CALayer class]] - || [c isSubclassOfClass:[UIGestureRecognizer class]]) { - return YES; - } - - // Apple classes with prefix - const char *name = class_getName(c); - if (strncmp(name, "UI", 2) == 0 || strncmp(name, "AV", 2) == 0 || strncmp(name, "CA", 2) == 0) { - return YES; - } - - // Specific Texture classes - if (strncmp(name, "ASTextKitComponents", 19) == 0) { - return YES; - } - - return NO; -} - Class _Nullable ASGetClassFromType(const char * _Nullable type) { // Class types all start with @" diff --git a/Source/TextKit/ASTextKitComponents.mm b/Source/TextKit/ASTextKitComponents.mm index 5a9b6644a..23bf1f5f1 100644 --- a/Source/TextKit/ASTextKitComponents.mm +++ b/Source/TextKit/ASTextKitComponents.mm @@ -17,6 +17,7 @@ #import #import +#import #import @@ -111,6 +112,11 @@ + (instancetype)componentsWithTextStorage:(NSTextStorage *)textStorage return components; } ++ (BOOL)needsMainThreadDeallocation +{ + return YES; +} + #pragma mark - Lifecycle - (void)dealloc