-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
ASMainThreadDeallocation.mm
202 lines (159 loc) · 5.82 KB
/
ASMainThreadDeallocation.mm
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//
// ASMainThreadDeallocation.mm
// Texture
//
// Copyright (c) Pinterest, Inc. All rights reserved.
// Licensed under Apache 2.0: http://www.apache.org/licenses/LICENSE-2.0
//
#import <AsyncDisplayKit/ASMainThreadDeallocation.h>
#import <AsyncDisplayKit/ASDisplayNodeExtras.h>
#import <AsyncDisplayKit/ASInternalHelpers.h>
#import <AsyncDisplayKit/ASLog.h>
#import <AsyncDisplayKit/ASThread.h>
@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]) {
os_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 {
os_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<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];
// 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
{
const 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
@implementation NSProxy (ASNeedsMainThreadDeallocation)
+ (BOOL)needsMainThreadDeallocation
{
return NO;
}
@end