-
-
Notifications
You must be signed in to change notification settings - Fork 1.8k
Expand file tree
/
Copy pathFLEXObjectExplorer.m
More file actions
319 lines (270 loc) · 11.9 KB
/
FLEXObjectExplorer.m
File metadata and controls
319 lines (270 loc) · 11.9 KB
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
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
//
// FLEXObjectExplorer.m
// FLEX
//
// Created by Tanner Bennett on 8/28/19.
// Copyright © 2019 Flipboard. All rights reserved.
//
#import "FLEXObjectExplorer.h"
#import "FLEXUtility.h"
#import "FLEXRuntimeUtility.h"
#import "NSObject+Reflection.h"
#import "FLEXRuntime+Compare.h"
#import "FLEXRuntime+UIKitHelpers.h"
#import "FLEXPropertyAttributes.h"
#import "NSObject+Reflection.h"
#import "FLEXMetadataSection.h"
#import "NSUserDefaults+FLEX.h"
#import "NSAttributedString+FLEX.h"
@interface FLEXObjectExplorer () {
NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
NSMutableArray<NSArray<FLEXProperty *> *> *_allClassProperties;
NSMutableArray<NSArray<FLEXIvar *> *> *_allIvars;
NSMutableArray<NSArray<FLEXMethod *> *> *_allMethods;
NSMutableArray<NSArray<FLEXMethod *> *> *_allClassMethods;
NSMutableArray<NSArray<FLEXProtocol *> *> *_allConformedProtocols;
NSMutableArray<FLEXStaticMetadata *> *_allInstanceSizes;
NSMutableArray<FLEXStaticMetadata *> *_allImageNames;
NSAttributedString *_objectDescription;
}
@end
@implementation FLEXObjectExplorer
#pragma mark - Initialization
+ (id)forObject:(id)objectOrClass {
return [[self alloc] initWithObject:objectOrClass];
}
- (id)initWithObject:(id)objectOrClass {
NSParameterAssert(objectOrClass);
self = [super init];
if (self) {
_object = objectOrClass;
_objectIsInstance = !object_isClass(objectOrClass);
[self reloadMetadata];
}
return self;
}
#pragma mark - Public
- (NSAttributedString *)objectDescription {
if (!_objectDescription) {
// Hard-code UIColor description
if ([self.object isKindOfClass:[UIColor class]]) {
CGFloat h, s, l, r, g, b, a;
[self.object getRed:&r green:&g blue:&b alpha:&a];
[self.object getHue:&h saturation:&s brightness:&l alpha:nil];
NSAttributedString *rString = [NSAttributedString stringWithAttributes:@{ NSForegroundColorAttributeName: UIColor.redColor } format:@"%.3f", r];
NSAttributedString *gString = [NSAttributedString stringWithAttributes:@{ NSForegroundColorAttributeName: UIColor.greenColor } format:@"%.3f", g];
NSAttributedString *bString = [NSAttributedString stringWithAttributes:@{ NSForegroundColorAttributeName: UIColor.blueColor } format:@"%.3f", b];
return [NSAttributedString stringWithFormat:@"HSL: (%.3f, %.3f, %.3f)\nRGB: (%@, %@, %@)\nAlpha: %.3f", h, s, l, rString, gString, bString, a];
}
NSAttributedString *description = [FLEXRuntimeUtility safeDescriptionForObject:self.object];
if (!description.length) {
NSAttributedString *address = [FLEXUtility addressOfObject:self.object];
return [NSAttributedString stringWithFormat:@"Object at %@ returned empty description", address];
}
if (description.length > 10000) {
description = [description attributedSubstringFromRange:NSMakeRange(0, 10000)];
}
_objectDescription = description;
}
return _objectDescription;
}
- (void)setClassScope:(NSInteger)classScope {
_classScope = classScope;
[self reloadScopedMetadata];
}
- (void)reloadMetadata {
_allProperties = [NSMutableArray new];
_allClassProperties = [NSMutableArray new];
_allIvars = [NSMutableArray new];
_allMethods = [NSMutableArray new];
_allClassMethods = [NSMutableArray new];
_allConformedProtocols = [NSMutableArray new];
_allInstanceSizes = [NSMutableArray new];
_allImageNames = [NSMutableArray new];
_objectDescription = nil;
[self reloadClassHierarchy];
NSUserDefaults *defaults = NSUserDefaults.standardUserDefaults;
BOOL hideBackingIvars = defaults.flex_explorerHidesPropertyIvars;
BOOL hidePropertyMethods = defaults.flex_explorerHidesPropertyMethods;
BOOL showMethodOverrides = defaults.flex_explorerShowsMethodOverrides;
// Loop over each class and each superclass, collect
// the fresh and unique metadata in each category
Class superclass = nil;
NSInteger count = self.classHierarchyClasses.count;
NSInteger rootIdx = count - 1;
for (NSInteger i = 0; i < count; i++) {
Class cls = self.classHierarchyClasses[i];
superclass = (i < rootIdx) ? self.classHierarchyClasses[i+1] : nil;
[_allProperties addObject:[self
metadataUniquedByName:[cls flex_allInstanceProperties]
superclass:superclass
kind:FLEXMetadataKindProperties
skip:showMethodOverrides
]];
[_allClassProperties addObject:[self
metadataUniquedByName:[cls flex_allClassProperties]
superclass:superclass
kind:FLEXMetadataKindClassProperties
skip:showMethodOverrides
]];
[_allIvars addObject:[self
metadataUniquedByName:[cls flex_allIvars]
superclass:nil
kind:FLEXMetadataKindIvars
skip:NO
]];
[_allMethods addObject:[self
metadataUniquedByName:[cls flex_allInstanceMethods]
superclass:superclass
kind:FLEXMetadataKindMethods
skip:showMethodOverrides
]];
[_allClassMethods addObject:[self
metadataUniquedByName:[cls flex_allClassMethods]
superclass:superclass
kind:FLEXMetadataKindClassMethods
skip:showMethodOverrides
]];
[_allConformedProtocols addObject:[self
metadataUniquedByName:[cls flex_protocols]
superclass:superclass
kind:FLEXMetadataKindProtocols
skip:NO
]];
// TODO: join instance size, image name, and class hierarchy into a single model object
// This would greatly reduce the laziness that has begun to manifest itself here
[_allInstanceSizes addObject:[FLEXStaticMetadata
style:FLEXStaticMetadataRowStyleKeyValue
title:@"Instance Size" number:@(class_getInstanceSize(cls))
]];
[_allImageNames addObject:[FLEXStaticMetadata
style:FLEXStaticMetadataRowStyleDefault
title:@"Image Name" string:@(class_getImageName(cls) ?: "Created at Runtime")
]];
}
_classHierarchy = [FLEXStaticMetadata classHierarchy:self.classHierarchyClasses];
NSArray<NSArray<FLEXProperty *> *> *properties = _allProperties;
// Potentially filter property-backing ivars
if (hideBackingIvars) {
NSArray<NSArray<FLEXIvar *> *> *ivars = _allIvars.copy;
_allIvars = [ivars flex_mapped:^id(NSArray<FLEXIvar *> *list, NSUInteger idx) {
// Get a set of all backing ivar names for the current class in the hierarchy
NSSet *ivarNames = [NSSet setWithArray:({
[properties[idx] flex_mapped:^id(FLEXProperty *p, NSUInteger idx) {
// Nil if no ivar, and array is flatted
return p.attributes.backingIvar;
}];
})];
// Remove ivars whose name is in the ivar names list
return [list flex_filtered:^BOOL(FLEXIvar *ivar, NSUInteger idx) {
return ![ivarNames containsObject:ivar.name];
}];
}];
}
// Potentially filter property-backing methods
if (hidePropertyMethods) {
NSArray<NSArray<FLEXMethod *> *> *methods = _allMethods.copy;
_allMethods = [methods flex_mapped:^id(NSArray<FLEXMethod *> *list, NSUInteger idx) {
// Get a set of all property method names for the current class in the hierarchy
NSSet *methodNames = [NSSet setWithArray:({
[properties[idx] flex_flatmapped:^NSArray *(FLEXProperty *p, NSUInteger idx) {
if (p.likelyGetterExists) {
if (p.likelySetterExists) {
return @[p.likelyGetterString, p.likelySetterString];
}
return @[p.likelyGetterString];
} else if (p.likelySetterExists) {
return @[p.likelySetterString];
}
return nil;
}];
})];
// Remove ivars whose name is in the ivar names list
return [list flex_filtered:^BOOL(FLEXMethod *method, NSUInteger idx) {
return ![methodNames containsObject:method.selectorString];
}];
}];
}
// Set up UIKit helper data
// Really, we only need to call this on properties and ivars
// because no other metadata types support editing.
for (NSArray *matrix in @[_allProperties, _allIvars, /* _allMethods, _allClassMethods, _allConformedProtocols */]) {
for (NSArray *metadataByClass in matrix) {
for (id<FLEXRuntimeMetadata> metadata in metadataByClass) {
metadata.tag = metadata.isEditable ? @YES : nil;
}
}
}
[self reloadScopedMetadata];
}
#pragma mark - Private
- (void)reloadScopedMetadata {
_properties = self.allProperties[self.classScope];
_classProperties = self.allClassProperties[self.classScope];
_ivars = self.allIvars[self.classScope];
_methods = self.allMethods[self.classScope];
_classMethods = self.allClassMethods[self.classScope];
_conformedProtocols = self.allConformedProtocols[self.classScope];
_instanceSize = self.allInstanceSizes[self.classScope];
_imageName = self.allImageNames[self.classScope];
}
/// Accepts an array of flex metadata objects and discards objects
/// with duplicate names, as well as properties and methods which
/// aren't "new" (i.e. those which the superclass responds to)
- (NSArray *)metadataUniquedByName:(NSArray *)list
superclass:(Class)superclass
kind:(FLEXMetadataKind)kind
skip:(BOOL)skip {
if (skip) {
return list;
}
// Remove items with same name and return filtered list
NSMutableSet *names = [NSMutableSet new];
return [list flex_filtered:^BOOL(id obj, NSUInteger idx) {
NSString *name = [obj name];
if ([names containsObject:name]) {
return NO;
} else {
[names addObject:name];
// Skip methods and properties which are just overrides,
// potentially skip ivars and methods associated with properties
switch (kind) {
case FLEXMetadataKindProperties:
if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
return NO;
}
break;
case FLEXMetadataKindClassProperties:
if ([superclass respondsToSelector:[obj likelyGetter]]) {
return NO;
}
break;
case FLEXMetadataKindMethods:
if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
return NO;
}
break;
case FLEXMetadataKindClassMethods:
if ([superclass respondsToSelector:NSSelectorFromString(name)]) {
return NO;
}
break;
case FLEXMetadataKindProtocols:
case FLEXMetadataKindClassHierarchy:
case FLEXMetadataKindOther:
return YES; // These types are already uniqued
break;
// Ivars cannot be overidden
case FLEXMetadataKindIvars: break;
}
return YES;
}
}];
}
#pragma mark - Superclasses
- (void)reloadClassHierarchy {
// The class hierarchy will never contain metaclass objects by this logic;
// it is always the same for a given class and instances of it
_classHierarchyClasses = [[self.object class] flex_classHierarchy];
}
@end