FLEXObjectExplorerViewController.m 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225
  1. //
  2. // FLEXObjectExplorerViewController.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 2014-05-03.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXObjectExplorerViewController.h"
  9. #import "FLEXUtility.h"
  10. #import "FLEXRuntimeUtility.h"
  11. #import "FLEXMultilineTableViewCell.h"
  12. #import "FLEXObjectExplorerFactory.h"
  13. #import "FLEXPropertyEditorViewController.h"
  14. #import "FLEXIvarEditorViewController.h"
  15. #import "FLEXMethodCallingViewController.h"
  16. #import "FLEXInstancesTableViewController.h"
  17. #import "FLEXTableView.h"
  18. #import <objc/runtime.h>
  19. typedef NS_ENUM(NSUInteger, FLEXObjectExplorerScope) {
  20. FLEXObjectExplorerScopeNoInheritance,
  21. FLEXObjectExplorerScopeWithParent,
  22. FLEXObjectExplorerScopeAllButNSObject,
  23. FLEXObjectExplorerScopeNSObjectOnly
  24. };
  25. typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
  26. FLEXMetadataKindProperties,
  27. FLEXMetadataKindIvars,
  28. FLEXMetadataKindMethods,
  29. FLEXMetadataKindClassMethods
  30. };
  31. // Convenience boxes to keep runtime properties, ivars, and methods in foundation collections.
  32. @interface FLEXPropertyBox : NSObject
  33. @property (nonatomic, assign) objc_property_t property;
  34. @end
  35. @implementation FLEXPropertyBox
  36. @end
  37. @interface FLEXIvarBox : NSObject
  38. @property (nonatomic, assign) Ivar ivar;
  39. @end
  40. @implementation FLEXIvarBox
  41. @end
  42. @interface FLEXMethodBox : NSObject
  43. @property (nonatomic, assign) Method method;
  44. @end
  45. @implementation FLEXMethodBox
  46. @end
  47. @interface FLEXObjectExplorerViewController () <UISearchBarDelegate>
  48. @property (nonatomic, strong) NSArray<FLEXPropertyBox *> *properties;
  49. @property (nonatomic, strong) NSArray<FLEXPropertyBox *> *propertiesWithParent;
  50. @property (nonatomic, strong) NSArray<FLEXPropertyBox *> *inheritedProperties;
  51. @property (nonatomic, strong) NSArray<FLEXPropertyBox *> *NSObjectProperties;
  52. @property (nonatomic, strong) NSArray<FLEXPropertyBox *> *filteredProperties;
  53. @property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivars;
  54. @property (nonatomic, strong) NSArray<FLEXIvarBox *> *ivarsWithParent;
  55. @property (nonatomic, strong) NSArray<FLEXIvarBox *> *inheritedIvars;
  56. @property (nonatomic, strong) NSArray<FLEXIvarBox *> *NSObjectIvars;
  57. @property (nonatomic, strong) NSArray<FLEXIvarBox *> *filteredIvars;
  58. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *methods;
  59. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *methodsWithParent;
  60. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedMethods;
  61. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectMethods;
  62. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredMethods;
  63. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethods;
  64. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *classMethodsWithParent;
  65. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *inheritedClassMethods;
  66. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *NSObjectClassMethods;
  67. @property (nonatomic, strong) NSArray<FLEXMethodBox *> *filteredClassMethods;
  68. @property (nonatomic, strong) NSArray<Class> *superclasses;
  69. @property (nonatomic, strong) NSArray<Class> *filteredSuperclasses;
  70. @property (nonatomic, strong) NSArray *cachedCustomSectionRowCookies;
  71. @property (nonatomic, strong) NSIndexSet *customSectionVisibleIndexes;
  72. @property (nonatomic, strong) UISearchBar *searchBar;
  73. @property (nonatomic, strong) NSString *filterText;
  74. @property (nonatomic, assign) FLEXObjectExplorerScope scope;
  75. @end
  76. @implementation FLEXObjectExplorerViewController
  77. + (void)initialize
  78. {
  79. if (self == [FLEXObjectExplorerViewController class]) {
  80. // Initialize custom menu items for entire app
  81. UIMenuItem *copyObjectAddress = [[UIMenuItem alloc] initWithTitle:@"Copy Address" action:@selector(copyObjectAddress:)];
  82. [UIMenuController sharedMenuController].menuItems = @[copyObjectAddress];
  83. [[UIMenuController sharedMenuController] update];
  84. }
  85. }
  86. - (void)loadView
  87. {
  88. self.tableView = [[FLEXTableView alloc] initWithFrame:CGRectZero style:UITableViewStyleGrouped];
  89. }
  90. - (void)viewDidLoad
  91. {
  92. [super viewDidLoad];
  93. self.searchBar = [[UISearchBar alloc] init];
  94. self.searchBar.placeholder = [FLEXUtility searchBarPlaceholderText];
  95. self.searchBar.delegate = self;
  96. self.searchBar.showsScopeBar = YES;
  97. [self refreshScopeTitles];
  98. self.tableView.tableHeaderView = self.searchBar;
  99. self.refreshControl = [[UIRefreshControl alloc] init];
  100. [self.refreshControl addTarget:self action:@selector(refreshControlDidRefresh:) forControlEvents:UIControlEventValueChanged];
  101. }
  102. - (void)viewWillAppear:(BOOL)animated
  103. {
  104. [super viewWillAppear:animated];
  105. // Reload the entire table view rather than just the visible cells because the filtered rows
  106. // may have changed (i.e. a change in the description row that causes it to get filtered out).
  107. [self updateTableData];
  108. }
  109. - (void)scrollViewDidScroll:(UIScrollView *)scrollView
  110. {
  111. [self.searchBar endEditing:YES];
  112. }
  113. - (void)refreshControlDidRefresh:(id)sender
  114. {
  115. [self updateTableData];
  116. [self.refreshControl endRefreshing];
  117. }
  118. #pragma mark - Search
  119. - (void)refreshScopeTitles
  120. {
  121. if (!self.searchBar) return;
  122. Class parent = [self.object superclass];
  123. Class parentSuper = [parent superclass];
  124. NSMutableArray *scopes = [NSMutableArray arrayWithObject:@"Base"];
  125. if (parent) {
  126. [scopes addObject:@"+ Parent"];
  127. }
  128. if (parentSuper && parentSuper != [NSObject class]) {
  129. [scopes addObject:@"+ Inherited"];
  130. }
  131. if ([self.object isKindOfClass:[NSObject class]]) {
  132. [scopes addObject:@"NSObject"];
  133. }
  134. self.searchBar.scopeButtonTitles = scopes;
  135. [self.searchBar sizeToFit];
  136. [self updateTableData];
  137. }
  138. - (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
  139. {
  140. self.filterText = searchText;
  141. }
  142. - (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
  143. {
  144. [searchBar resignFirstResponder];
  145. }
  146. - (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
  147. {
  148. self.scope = selectedScope;
  149. [self updateDisplayedData];
  150. }
  151. - (NSArray *)metadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope
  152. {
  153. switch (metadataKind) {
  154. case FLEXMetadataKindProperties:
  155. switch (self.scope) {
  156. case FLEXObjectExplorerScopeNoInheritance:
  157. return self.properties;
  158. case FLEXObjectExplorerScopeWithParent:
  159. return self.propertiesWithParent;
  160. case FLEXObjectExplorerScopeAllButNSObject:
  161. return self.inheritedProperties;
  162. case FLEXObjectExplorerScopeNSObjectOnly:
  163. return self.NSObjectProperties;
  164. }
  165. case FLEXMetadataKindIvars:
  166. switch (self.scope) {
  167. case FLEXObjectExplorerScopeNoInheritance:
  168. return self.ivars;
  169. case FLEXObjectExplorerScopeWithParent:
  170. return self.ivarsWithParent;
  171. case FLEXObjectExplorerScopeAllButNSObject:
  172. return self.inheritedIvars;
  173. case FLEXObjectExplorerScopeNSObjectOnly:
  174. return self.NSObjectIvars;
  175. }
  176. case FLEXMetadataKindMethods:
  177. switch (self.scope) {
  178. case FLEXObjectExplorerScopeNoInheritance:
  179. return self.methods;
  180. case FLEXObjectExplorerScopeWithParent:
  181. return self.methodsWithParent;
  182. case FLEXObjectExplorerScopeAllButNSObject:
  183. return self.inheritedMethods;
  184. case FLEXObjectExplorerScopeNSObjectOnly:
  185. return self.NSObjectMethods;
  186. }
  187. case FLEXMetadataKindClassMethods:
  188. switch (self.scope) {
  189. case FLEXObjectExplorerScopeNoInheritance:
  190. return self.classMethods;
  191. case FLEXObjectExplorerScopeWithParent:
  192. return self.classMethodsWithParent;
  193. case FLEXObjectExplorerScopeAllButNSObject:
  194. return self.inheritedClassMethods;
  195. case FLEXObjectExplorerScopeNSObjectOnly:
  196. return self.NSObjectClassMethods;
  197. }
  198. }
  199. }
  200. - (NSInteger)totalCountOfMetadata:(FLEXMetadataKind)metadataKind forScope:(FLEXObjectExplorerScope)scope
  201. {
  202. return [self metadata:metadataKind forScope:scope].count;
  203. }
  204. #pragma mark - Setter overrides
  205. - (void)setObject:(id)object
  206. {
  207. _object = object;
  208. // Use [object class] here rather than object_getClass because we don't want to show the KVO prefix for observed objects.
  209. self.title = [[object class] description];
  210. [self refreshScopeTitles];
  211. }
  212. - (void)setFilterText:(NSString *)filterText
  213. {
  214. if (_filterText != filterText || ![_filterText isEqual:filterText]) {
  215. _filterText = filterText;
  216. [self updateDisplayedData];
  217. }
  218. }
  219. #pragma mark - Reloading
  220. - (void)updateTableData
  221. {
  222. [self updateCustomData];
  223. [self updateProperties];
  224. [self updateIvars];
  225. [self updateMethods];
  226. [self updateClassMethods];
  227. [self updateSuperclasses];
  228. [self updateDisplayedData];
  229. }
  230. - (void)updateDisplayedData
  231. {
  232. [self updateFilteredCustomData];
  233. [self updateFilteredProperties];
  234. [self updateFilteredIvars];
  235. [self updateFilteredMethods];
  236. [self updateFilteredClassMethods];
  237. [self updateFilteredSuperclasses];
  238. if (self.isViewLoaded) {
  239. [self.tableView reloadData];
  240. }
  241. }
  242. - (BOOL)shouldShowDescription
  243. {
  244. // Not if we have filter text that doesn't match the desctiption.
  245. if (self.filterText.length) {
  246. NSString *description = [self displayedObjectDescription];
  247. return [description rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
  248. }
  249. return YES;
  250. }
  251. - (NSString *)displayedObjectDescription {
  252. NSString *desc = [FLEXUtility safeDescriptionForObject:self.object];
  253. if (!desc.length) {
  254. NSString *address = [FLEXUtility addressOfObject:self.object];
  255. desc = [NSString stringWithFormat:@"Object at %@ returned empty description", address];
  256. }
  257. return desc;
  258. }
  259. #pragma mark - Properties
  260. - (void)updateProperties
  261. {
  262. Class class = [self.object class];
  263. self.properties = [[self class] propertiesForClass:class];
  264. self.propertiesWithParent = [self.properties arrayByAddingObjectsFromArray:[[self class] propertiesForClass:[class superclass]]];
  265. self.inheritedProperties = [self.properties arrayByAddingObjectsFromArray:[[self class] inheritedPropertiesForClass:class]];
  266. self.NSObjectProperties = [[self class] propertiesForClass:[NSObject class]];
  267. }
  268. + (NSArray<FLEXPropertyBox *> *)propertiesForClass:(Class)class
  269. {
  270. if (!class) {
  271. return @[];
  272. }
  273. NSMutableArray<FLEXPropertyBox *> *boxedProperties = [NSMutableArray array];
  274. unsigned int propertyCount = 0;
  275. objc_property_t *propertyList = class_copyPropertyList(class, &propertyCount);
  276. if (propertyList) {
  277. for (unsigned int i = 0; i < propertyCount; i++) {
  278. FLEXPropertyBox *propertyBox = [[FLEXPropertyBox alloc] init];
  279. propertyBox.property = propertyList[i];
  280. [boxedProperties addObject:propertyBox];
  281. }
  282. free(propertyList);
  283. }
  284. return boxedProperties;
  285. }
  286. /// Skips NSObject
  287. + (NSArray<FLEXPropertyBox *> *)inheritedPropertiesForClass:(Class)class
  288. {
  289. NSMutableArray<FLEXPropertyBox *> *inheritedProperties = [NSMutableArray array];
  290. while ((class = [class superclass]) && class != [NSObject class]) {
  291. [inheritedProperties addObjectsFromArray:[self propertiesForClass:class]];
  292. }
  293. return inheritedProperties;
  294. }
  295. - (void)updateFilteredProperties
  296. {
  297. NSArray<FLEXPropertyBox *> *candidateProperties = [self metadata:FLEXMetadataKindProperties forScope:self.scope];
  298. NSArray<FLEXPropertyBox *> *unsortedFilteredProperties = nil;
  299. if ([self.filterText length] > 0) {
  300. NSMutableArray<FLEXPropertyBox *> *mutableUnsortedFilteredProperties = [NSMutableArray array];
  301. for (FLEXPropertyBox *propertyBox in candidateProperties) {
  302. NSString *prettyName = [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  303. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  304. [mutableUnsortedFilteredProperties addObject:propertyBox];
  305. }
  306. }
  307. unsortedFilteredProperties = mutableUnsortedFilteredProperties;
  308. } else {
  309. unsortedFilteredProperties = candidateProperties;
  310. }
  311. self.filteredProperties = [unsortedFilteredProperties sortedArrayUsingComparator:^NSComparisonResult(FLEXPropertyBox *propertyBox1, FLEXPropertyBox *propertyBox2) {
  312. NSString *name1 = [NSString stringWithUTF8String:property_getName(propertyBox1.property)];
  313. NSString *name2 = [NSString stringWithUTF8String:property_getName(propertyBox2.property)];
  314. return [name1 caseInsensitiveCompare:name2];
  315. }];
  316. }
  317. - (NSString *)titleForPropertyAtIndex:(NSInteger)index
  318. {
  319. FLEXPropertyBox *propertyBox = self.filteredProperties[index];
  320. return [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  321. }
  322. - (id)valueForPropertyAtIndex:(NSInteger)index
  323. {
  324. id value = nil;
  325. if ([self canHaveInstanceState]) {
  326. FLEXPropertyBox *propertyBox = self.filteredProperties[index];
  327. NSString *typeString = [FLEXRuntimeUtility typeEncodingForProperty:propertyBox.property];
  328. const FLEXTypeEncoding *encoding = [typeString cStringUsingEncoding:NSUTF8StringEncoding];
  329. value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
  330. value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
  331. }
  332. return value;
  333. }
  334. #pragma mark - Ivars
  335. - (void)updateIvars
  336. {
  337. Class class = [self.object class];
  338. self.ivars = [[self class] ivarsForClass:class];
  339. self.ivarsWithParent = [self.ivars arrayByAddingObjectsFromArray:[[self class] ivarsForClass:[class superclass]]];
  340. self.inheritedIvars = [self.ivars arrayByAddingObjectsFromArray:[[self class] inheritedIvarsForClass:class]];
  341. self.NSObjectIvars = [[self class] ivarsForClass:[NSObject class]];
  342. }
  343. + (NSArray<FLEXIvarBox *> *)ivarsForClass:(Class)class
  344. {
  345. if (!class) {
  346. return @[];
  347. }
  348. NSMutableArray<FLEXIvarBox *> *boxedIvars = [NSMutableArray array];
  349. unsigned int ivarCount = 0;
  350. Ivar *ivarList = class_copyIvarList(class, &ivarCount);
  351. if (ivarList) {
  352. for (unsigned int i = 0; i < ivarCount; i++) {
  353. FLEXIvarBox *ivarBox = [[FLEXIvarBox alloc] init];
  354. ivarBox.ivar = ivarList[i];
  355. [boxedIvars addObject:ivarBox];
  356. }
  357. free(ivarList);
  358. }
  359. return boxedIvars;
  360. }
  361. /// Skips NSObject
  362. + (NSArray<FLEXIvarBox *> *)inheritedIvarsForClass:(Class)class
  363. {
  364. NSMutableArray<FLEXIvarBox *> *inheritedIvars = [NSMutableArray array];
  365. while ((class = [class superclass]) && class != [NSObject class]) {
  366. [inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
  367. }
  368. return inheritedIvars;
  369. }
  370. - (void)updateFilteredIvars
  371. {
  372. NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forScope:self.scope];
  373. NSArray<FLEXIvarBox *> *unsortedFilteredIvars = nil;
  374. if ([self.filterText length] > 0) {
  375. NSMutableArray<FLEXIvarBox *> *mutableUnsortedFilteredIvars = [NSMutableArray array];
  376. for (FLEXIvarBox *ivarBox in candidateIvars) {
  377. NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  378. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  379. [mutableUnsortedFilteredIvars addObject:ivarBox];
  380. }
  381. }
  382. unsortedFilteredIvars = mutableUnsortedFilteredIvars;
  383. } else {
  384. unsortedFilteredIvars = candidateIvars;
  385. }
  386. self.filteredIvars = [unsortedFilteredIvars sortedArrayUsingComparator:^NSComparisonResult(FLEXIvarBox *ivarBox1, FLEXIvarBox *ivarBox2) {
  387. NSString *name1 = [NSString stringWithUTF8String:ivar_getName(ivarBox1.ivar)];
  388. NSString *name2 = [NSString stringWithUTF8String:ivar_getName(ivarBox2.ivar)];
  389. return [name1 caseInsensitiveCompare:name2];
  390. }];
  391. }
  392. - (NSString *)titleForIvarAtIndex:(NSInteger)index
  393. {
  394. FLEXIvarBox *ivarBox = self.filteredIvars[index];
  395. return [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  396. }
  397. - (id)valueForIvarAtIndex:(NSInteger)index
  398. {
  399. id value = nil;
  400. if ([self canHaveInstanceState]) {
  401. FLEXIvarBox *ivarBox = self.filteredIvars[index];
  402. const FLEXTypeEncoding *encoding = ivar_getTypeEncoding(ivarBox.ivar);
  403. value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
  404. value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
  405. }
  406. return value;
  407. }
  408. #pragma mark - Methods
  409. - (void)updateMethods
  410. {
  411. Class class = [self.object class];
  412. self.methods = [[self class] methodsForClass:class];
  413. self.methodsWithParent = [self.methods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[class superclass]]];
  414. self.inheritedMethods = [self.methods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:class]];
  415. self.NSObjectMethods = [[self class] methodsForClass:[NSObject class]];
  416. }
  417. - (void)updateFilteredMethods
  418. {
  419. NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forScope:self.scope];
  420. self.filteredMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:NO];
  421. }
  422. - (void)updateClassMethods
  423. {
  424. const char *className = [NSStringFromClass([self.object class]) UTF8String];
  425. Class metaClass = objc_getMetaClass(className);
  426. self.classMethods = [[self class] methodsForClass:metaClass];
  427. self.classMethodsWithParent = [self.classMethods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[metaClass superclass]]];
  428. self.inheritedClassMethods = [self.classMethods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:metaClass]];
  429. self.NSObjectClassMethods = [[self class] methodsForClass:[NSObject class]];
  430. }
  431. - (void)updateFilteredClassMethods
  432. {
  433. NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forScope:self.scope];
  434. self.filteredClassMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:YES];
  435. }
  436. + (NSArray<FLEXMethodBox *> *)methodsForClass:(Class)class
  437. {
  438. if (!class) {
  439. return @[];
  440. }
  441. NSMutableArray<FLEXMethodBox *> *boxedMethods = [NSMutableArray array];
  442. unsigned int methodCount = 0;
  443. Method *methodList = class_copyMethodList(class, &methodCount);
  444. if (methodList) {
  445. for (unsigned int i = 0; i < methodCount; i++) {
  446. FLEXMethodBox *methodBox = [[FLEXMethodBox alloc] init];
  447. methodBox.method = methodList[i];
  448. [boxedMethods addObject:methodBox];
  449. }
  450. free(methodList);
  451. }
  452. return boxedMethods;
  453. }
  454. /// Skips NSObject
  455. + (NSArray<FLEXMethodBox *> *)inheritedMethodsForClass:(Class)class
  456. {
  457. NSMutableArray<FLEXMethodBox *> *inheritedMethods = [NSMutableArray array];
  458. while ((class = [class superclass]) && class != [NSObject class]) {
  459. [inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
  460. }
  461. return inheritedMethods;
  462. }
  463. - (NSArray<FLEXMethodBox *> *)filteredMethodsFromMethods:(NSArray<FLEXMethodBox *> *)methods areClassMethods:(BOOL)areClassMethods
  464. {
  465. NSArray<FLEXMethodBox *> *candidateMethods = methods;
  466. NSArray<FLEXMethodBox *> *unsortedFilteredMethods = nil;
  467. if ([self.filterText length] > 0) {
  468. NSMutableArray<FLEXMethodBox *> *mutableUnsortedFilteredMethods = [NSMutableArray array];
  469. for (FLEXMethodBox *methodBox in candidateMethods) {
  470. NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
  471. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  472. [mutableUnsortedFilteredMethods addObject:methodBox];
  473. }
  474. }
  475. unsortedFilteredMethods = mutableUnsortedFilteredMethods;
  476. } else {
  477. unsortedFilteredMethods = candidateMethods;
  478. }
  479. NSArray<FLEXMethodBox *> *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
  480. NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
  481. NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
  482. return [name1 caseInsensitiveCompare:name2];
  483. }];
  484. return sortedFilteredMethods;
  485. }
  486. - (NSString *)titleForMethodAtIndex:(NSInteger)index
  487. {
  488. FLEXMethodBox *methodBox = self.filteredMethods[index];
  489. return [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:NO];
  490. }
  491. - (NSString *)titleForClassMethodAtIndex:(NSInteger)index
  492. {
  493. FLEXMethodBox *classMethodBox = self.filteredClassMethods[index];
  494. return [FLEXRuntimeUtility prettyNameForMethod:classMethodBox.method isClassMethod:YES];
  495. }
  496. - (objc_property_t)viewPropertyForName:(NSString *)propertyName
  497. {
  498. return class_getProperty([self.object class], propertyName.UTF8String);
  499. }
  500. #pragma mark - Superclasses
  501. + (NSArray<Class> *)superclassesForClass:(Class)class
  502. {
  503. NSMutableArray<Class> *superClasses = [NSMutableArray array];
  504. while ((class = [class superclass])) {
  505. [superClasses addObject:class];
  506. }
  507. return superClasses;
  508. }
  509. - (void)updateSuperclasses
  510. {
  511. self.superclasses = [[self class] superclassesForClass:[self.object class]];
  512. }
  513. - (void)updateFilteredSuperclasses
  514. {
  515. if ([self.filterText length] > 0) {
  516. NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
  517. for (Class superclass in self.superclasses) {
  518. if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
  519. [filteredSuperclasses addObject:superclass];
  520. }
  521. }
  522. self.filteredSuperclasses = filteredSuperclasses;
  523. } else {
  524. self.filteredSuperclasses = self.superclasses;
  525. }
  526. }
  527. #pragma mark - Table View Data Helpers
  528. - (NSArray<NSNumber *> *)possibleExplorerSections
  529. {
  530. static NSArray<NSNumber *> *possibleSections = nil;
  531. static dispatch_once_t onceToken;
  532. dispatch_once(&onceToken, ^{
  533. possibleSections = @[@(FLEXObjectExplorerSectionDescription),
  534. @(FLEXObjectExplorerSectionCustom),
  535. @(FLEXObjectExplorerSectionProperties),
  536. @(FLEXObjectExplorerSectionIvars),
  537. @(FLEXObjectExplorerSectionMethods),
  538. @(FLEXObjectExplorerSectionClassMethods),
  539. @(FLEXObjectExplorerSectionSuperclasses),
  540. @(FLEXObjectExplorerSectionReferencingInstances)];
  541. });
  542. return possibleSections;
  543. }
  544. - (NSArray<NSNumber *> *)visibleExplorerSections
  545. {
  546. NSMutableArray<NSNumber *> *visibleSections = [NSMutableArray array];
  547. for (NSNumber *possibleSection in [self possibleExplorerSections]) {
  548. FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
  549. if ([self numberOfRowsForExplorerSection:explorerSection] > 0) {
  550. [visibleSections addObject:possibleSection];
  551. }
  552. }
  553. return visibleSections;
  554. }
  555. - (NSString *)sectionTitleWithBaseName:(NSString *)baseName totalCount:(NSUInteger)totalCount filteredCount:(NSUInteger)filteredCount
  556. {
  557. NSString *sectionTitle = nil;
  558. if (totalCount == filteredCount) {
  559. sectionTitle = [baseName stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
  560. } else {
  561. sectionTitle = [baseName stringByAppendingFormat:@" (%lu of %lu)", (unsigned long)filteredCount, (unsigned long)totalCount];
  562. }
  563. return sectionTitle;
  564. }
  565. - (FLEXObjectExplorerSection)explorerSectionAtIndex:(NSInteger)sectionIndex
  566. {
  567. return [[[self visibleExplorerSections] objectAtIndex:sectionIndex] unsignedIntegerValue];
  568. }
  569. - (NSInteger)numberOfRowsForExplorerSection:(FLEXObjectExplorerSection)section
  570. {
  571. NSInteger numberOfRows = 0;
  572. switch (section) {
  573. case FLEXObjectExplorerSectionDescription:
  574. numberOfRows = [self shouldShowDescription] ? 1 : 0;
  575. break;
  576. case FLEXObjectExplorerSectionCustom:
  577. numberOfRows = [self.customSectionVisibleIndexes count];
  578. break;
  579. case FLEXObjectExplorerSectionProperties:
  580. numberOfRows = [self.filteredProperties count];
  581. break;
  582. case FLEXObjectExplorerSectionIvars:
  583. numberOfRows = [self.filteredIvars count];
  584. break;
  585. case FLEXObjectExplorerSectionMethods:
  586. numberOfRows = [self.filteredMethods count];
  587. break;
  588. case FLEXObjectExplorerSectionClassMethods:
  589. numberOfRows = [self.filteredClassMethods count];
  590. break;
  591. case FLEXObjectExplorerSectionSuperclasses:
  592. numberOfRows = [self.filteredSuperclasses count];
  593. break;
  594. case FLEXObjectExplorerSectionReferencingInstances:
  595. // Hide this section if there is fliter text since there's nothing searchable (only 1 row, always the same).
  596. numberOfRows = [self.filterText length] == 0 ? 1 : 0;
  597. break;
  598. }
  599. return numberOfRows;
  600. }
  601. - (NSString *)titleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  602. {
  603. NSString *title = nil;
  604. switch (section) {
  605. case FLEXObjectExplorerSectionDescription:
  606. title = [self displayedObjectDescription];
  607. break;
  608. case FLEXObjectExplorerSectionCustom:
  609. title = [self customSectionTitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  610. break;
  611. case FLEXObjectExplorerSectionProperties:
  612. title = [self titleForPropertyAtIndex:row];
  613. break;
  614. case FLEXObjectExplorerSectionIvars:
  615. title = [self titleForIvarAtIndex:row];
  616. break;
  617. case FLEXObjectExplorerSectionMethods:
  618. title = [self titleForMethodAtIndex:row];
  619. break;
  620. case FLEXObjectExplorerSectionClassMethods:
  621. title = [self titleForClassMethodAtIndex:row];
  622. break;
  623. case FLEXObjectExplorerSectionSuperclasses:
  624. title = NSStringFromClass(self.filteredSuperclasses[row]);
  625. break;
  626. case FLEXObjectExplorerSectionReferencingInstances:
  627. title = @"Other objects with ivars referencing this object";
  628. break;
  629. }
  630. return title;
  631. }
  632. - (NSString *)subtitleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  633. {
  634. NSString *subtitle = nil;
  635. switch (section) {
  636. case FLEXObjectExplorerSectionDescription:
  637. break;
  638. case FLEXObjectExplorerSectionCustom:
  639. subtitle = [self customSectionSubtitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  640. break;
  641. case FLEXObjectExplorerSectionProperties:
  642. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForPropertyAtIndex:row]] : nil;
  643. break;
  644. case FLEXObjectExplorerSectionIvars:
  645. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForIvarAtIndex:row]] : nil;
  646. break;
  647. case FLEXObjectExplorerSectionMethods:
  648. break;
  649. case FLEXObjectExplorerSectionClassMethods:
  650. break;
  651. case FLEXObjectExplorerSectionSuperclasses:
  652. break;
  653. case FLEXObjectExplorerSectionReferencingInstances:
  654. break;
  655. }
  656. return subtitle;
  657. }
  658. - (BOOL)canDrillInToRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  659. {
  660. BOOL canDrillIn = NO;
  661. switch (section) {
  662. case FLEXObjectExplorerSectionDescription:
  663. break;
  664. case FLEXObjectExplorerSectionCustom:
  665. canDrillIn = [self customSectionCanDrillIntoRowWithCookie:[self customSectionRowCookieForVisibleRow:row]];
  666. break;
  667. case FLEXObjectExplorerSectionProperties: {
  668. if ([self canHaveInstanceState]) {
  669. FLEXPropertyBox *propertyBox = self.filteredProperties[row];
  670. objc_property_t property = propertyBox.property;
  671. id currentValue = [self valueForPropertyAtIndex:row];
  672. BOOL canEdit = [FLEXPropertyEditorViewController canEditProperty:property onObject:self.object currentValue:currentValue];
  673. BOOL canExplore = currentValue != nil;
  674. canDrillIn = canEdit || canExplore;
  675. }
  676. } break;
  677. case FLEXObjectExplorerSectionIvars: {
  678. if ([self canHaveInstanceState]) {
  679. FLEXIvarBox *ivarBox = self.filteredIvars[row];
  680. Ivar ivar = ivarBox.ivar;
  681. id currentValue = [self valueForIvarAtIndex:row];
  682. BOOL canEdit = [FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue];
  683. BOOL canExplore = currentValue != nil;
  684. canDrillIn = canEdit || canExplore;
  685. }
  686. } break;
  687. case FLEXObjectExplorerSectionMethods:
  688. canDrillIn = [self canCallInstanceMethods];
  689. break;
  690. case FLEXObjectExplorerSectionClassMethods:
  691. canDrillIn = YES;
  692. break;
  693. case FLEXObjectExplorerSectionSuperclasses:
  694. canDrillIn = YES;
  695. break;
  696. case FLEXObjectExplorerSectionReferencingInstances:
  697. canDrillIn = YES;
  698. break;
  699. }
  700. return canDrillIn;
  701. }
  702. - (BOOL)sectionHasActions:(NSInteger)section
  703. {
  704. return [self explorerSectionAtIndex:section] == FLEXObjectExplorerSectionDescription;
  705. }
  706. - (NSString *)titleForExplorerSection:(FLEXObjectExplorerSection)section
  707. {
  708. NSString *title = nil;
  709. switch (section) {
  710. case FLEXObjectExplorerSectionDescription: {
  711. title = @"Description";
  712. } break;
  713. case FLEXObjectExplorerSectionCustom: {
  714. title = [self customSectionTitle];
  715. } break;
  716. case FLEXObjectExplorerSectionProperties: {
  717. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forScope:self.scope];
  718. title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
  719. } break;
  720. case FLEXObjectExplorerSectionIvars: {
  721. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forScope:self.scope];
  722. title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
  723. } break;
  724. case FLEXObjectExplorerSectionMethods: {
  725. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forScope:self.scope];
  726. title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
  727. } break;
  728. case FLEXObjectExplorerSectionClassMethods: {
  729. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forScope:self.scope];
  730. title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
  731. } break;
  732. case FLEXObjectExplorerSectionSuperclasses: {
  733. title = [self sectionTitleWithBaseName:@"Superclasses" totalCount:[self.superclasses count] filteredCount:[self.filteredSuperclasses count]];
  734. } break;
  735. case FLEXObjectExplorerSectionReferencingInstances: {
  736. title = @"Object Graph";
  737. } break;
  738. }
  739. return title;
  740. }
  741. - (UIViewController *)drillInViewControllerForRow:(NSUInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  742. {
  743. UIViewController *viewController = nil;
  744. switch (section) {
  745. case FLEXObjectExplorerSectionDescription:
  746. break;
  747. case FLEXObjectExplorerSectionCustom:
  748. viewController = [self customSectionDrillInViewControllerForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  749. break;
  750. case FLEXObjectExplorerSectionProperties: {
  751. FLEXPropertyBox *propertyBox = self.filteredProperties[row];
  752. objc_property_t property = propertyBox.property;
  753. id currentValue = [self valueForPropertyAtIndex:row];
  754. if ([FLEXPropertyEditorViewController canEditProperty:property onObject:self.object currentValue:currentValue]) {
  755. viewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.object property:property];
  756. } else if (currentValue) {
  757. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  758. }
  759. } break;
  760. case FLEXObjectExplorerSectionIvars: {
  761. FLEXIvarBox *ivarBox = self.filteredIvars[row];
  762. Ivar ivar = ivarBox.ivar;
  763. id currentValue = [self valueForIvarAtIndex:row];
  764. if ([FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue]) {
  765. viewController = [[FLEXIvarEditorViewController alloc] initWithTarget:self.object ivar:ivar];
  766. } else if (currentValue) {
  767. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  768. }
  769. } break;
  770. case FLEXObjectExplorerSectionMethods: {
  771. FLEXMethodBox *methodBox = self.filteredMethods[row];
  772. Method method = methodBox.method;
  773. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:self.object method:method];
  774. } break;
  775. case FLEXObjectExplorerSectionClassMethods: {
  776. FLEXMethodBox *methodBox = self.filteredClassMethods[row];
  777. Method method = methodBox.method;
  778. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:[self.object class] method:method];
  779. } break;
  780. case FLEXObjectExplorerSectionSuperclasses: {
  781. Class superclass = self.filteredSuperclasses[row];
  782. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:superclass];
  783. } break;
  784. case FLEXObjectExplorerSectionReferencingInstances: {
  785. viewController = [FLEXInstancesTableViewController instancesTableViewControllerForInstancesReferencingObject:self.object];
  786. } break;
  787. }
  788. return viewController;
  789. }
  790. #pragma mark - Table View Data Source
  791. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  792. {
  793. return [[self visibleExplorerSections] count];
  794. }
  795. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  796. {
  797. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  798. return [self numberOfRowsForExplorerSection:explorerSection];
  799. }
  800. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  801. {
  802. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  803. return [self titleForExplorerSection:explorerSection];
  804. }
  805. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  806. {
  807. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  808. BOOL isCustomSection = explorerSection == FLEXObjectExplorerSectionCustom;
  809. BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
  810. NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineTableViewCellIdentifier : @"cell";
  811. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  812. if (!cell) {
  813. if (useDescriptionCell) {
  814. cell = [[FLEXMultilineTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
  815. cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
  816. } else {
  817. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
  818. UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
  819. cell.textLabel.font = cellFont;
  820. cell.detailTextLabel.font = cellFont;
  821. cell.detailTextLabel.textColor = [UIColor grayColor];
  822. }
  823. }
  824. UIView *customView;
  825. if (isCustomSection) {
  826. customView = [self customViewForRowCookie:[self customSectionRowCookieForVisibleRow:indexPath.row]];
  827. if (customView) {
  828. [cell.contentView addSubview:customView];
  829. }
  830. }
  831. cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  832. cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  833. cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
  834. return cell;
  835. }
  836. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  837. {
  838. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  839. CGFloat height = self.tableView.rowHeight;
  840. if (explorerSection == FLEXObjectExplorerSectionDescription) {
  841. NSString *text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  842. NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont] }];
  843. CGFloat preferredHeight = [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.frame.size.width style:tableView.style showsAccessory:NO];
  844. height = MAX(height, preferredHeight);
  845. } else if (explorerSection == FLEXObjectExplorerSectionCustom) {
  846. id cookie = [self customSectionRowCookieForVisibleRow:indexPath.row];
  847. height = [self heightForCustomViewRowForRowCookie:cookie];
  848. }
  849. return height;
  850. }
  851. #pragma mark - Table View Delegate
  852. - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
  853. {
  854. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  855. return [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection];
  856. }
  857. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  858. {
  859. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  860. UIViewController *detailViewController = [self drillInViewControllerForRow:indexPath.row inExplorerSection:explorerSection];
  861. if (detailViewController) {
  862. [self.navigationController pushViewController:detailViewController animated:YES];
  863. } else {
  864. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  865. }
  866. }
  867. - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
  868. {
  869. return [self sectionHasActions:indexPath.section];
  870. }
  871. - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  872. {
  873. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  874. switch (explorerSection) {
  875. case FLEXObjectExplorerSectionDescription:
  876. return action == @selector(copy:) || action == @selector(copyObjectAddress:);
  877. default:
  878. return NO;
  879. }
  880. }
  881. - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  882. {
  883. #pragma clang diagnostic push
  884. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  885. [self performSelector:action withObject:indexPath];
  886. #pragma clang diagnostic pop
  887. }
  888. #pragma mark - UIMenuController
  889. /// Prevent the search bar from trying to use us as a responder
  890. ///
  891. /// Our table cells will use the UITableViewDelegate methods
  892. /// to make sure we can perform the actions we want to
  893. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
  894. {
  895. return NO;
  896. }
  897. - (void)copy:(NSIndexPath *)indexPath
  898. {
  899. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  900. NSString *stringToCopy = @"";
  901. NSString *title = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  902. if (title.length) {
  903. stringToCopy = [stringToCopy stringByAppendingString:title];
  904. }
  905. NSString *subtitle = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  906. if (subtitle.length) {
  907. if (stringToCopy.length) {
  908. stringToCopy = [stringToCopy stringByAppendingString:@"\n\n"];
  909. }
  910. stringToCopy = [stringToCopy stringByAppendingString:subtitle];
  911. }
  912. [UIPasteboard generalPasteboard].string = stringToCopy;
  913. }
  914. - (void)copyObjectAddress:(NSIndexPath *)indexPath
  915. {
  916. [UIPasteboard generalPasteboard].string = [FLEXUtility addressOfObject:self.object];
  917. }
  918. #pragma mark - Custom Section
  919. - (void)updateCustomData
  920. {
  921. self.cachedCustomSectionRowCookies = [self customSectionRowCookies];
  922. }
  923. - (void)updateFilteredCustomData
  924. {
  925. NSIndexSet *filteredIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self.cachedCustomSectionRowCookies count])];
  926. if ([self.filterText length] > 0) {
  927. filteredIndexSet = [filteredIndexSet indexesPassingTest:^BOOL(NSUInteger index, BOOL *stop) {
  928. BOOL matches = NO;
  929. NSString *rowTitle = [self customSectionTitleForRowCookie:self.cachedCustomSectionRowCookies[index]];
  930. if ([rowTitle rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  931. matches = YES;
  932. }
  933. return matches;
  934. }];
  935. }
  936. self.customSectionVisibleIndexes = filteredIndexSet;
  937. }
  938. - (id)customSectionRowCookieForVisibleRow:(NSUInteger)row
  939. {
  940. return [[self.cachedCustomSectionRowCookies objectsAtIndexes:self.customSectionVisibleIndexes] objectAtIndex:row];
  941. }
  942. #pragma mark - Subclasses Can Override
  943. - (NSString *)customSectionTitle
  944. {
  945. return self.shortcutPropertyNames.count ? @"Shortcuts" : nil;
  946. }
  947. - (NSArray *)customSectionRowCookies
  948. {
  949. return self.shortcutPropertyNames;
  950. }
  951. - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
  952. {
  953. if ([rowCookie isKindOfClass:[NSString class]]) {
  954. objc_property_t property = [self viewPropertyForName:rowCookie];
  955. if (property) {
  956. NSString *prettyPropertyName = [FLEXRuntimeUtility prettyNameForProperty:property];
  957. // Since we're outside of the "properties" section, prepend @property for clarity.
  958. return [@"@property " stringByAppendingString:prettyPropertyName];
  959. } else if ([rowCookie respondsToSelector:@selector(description)]) {
  960. return [@"No property found for object: " stringByAppendingString:[rowCookie description]];
  961. } else {
  962. NSString *cls = NSStringFromClass([rowCookie class]);
  963. return [@"No property found for object of class " stringByAppendingString:cls];
  964. }
  965. }
  966. return nil;
  967. }
  968. - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
  969. {
  970. if ([rowCookie isKindOfClass:[NSString class]]) {
  971. objc_property_t property = [self viewPropertyForName:rowCookie];
  972. if (property) {
  973. id value = [FLEXRuntimeUtility valueForProperty:property onObject:self.object];
  974. return [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:value];
  975. } else {
  976. return nil;
  977. }
  978. }
  979. return nil;
  980. }
  981. - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
  982. {
  983. return YES;
  984. }
  985. - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
  986. {
  987. if ([rowCookie isKindOfClass:[NSString class]]) {
  988. objc_property_t property = [self viewPropertyForName:rowCookie];
  989. if (property) {
  990. id currentValue = [FLEXRuntimeUtility valueForProperty:property onObject:self.object];
  991. if ([FLEXPropertyEditorViewController canEditProperty:property onObject:self.object currentValue:currentValue]) {
  992. return [[FLEXPropertyEditorViewController alloc] initWithTarget:self.object property:property];
  993. } else {
  994. return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  995. }
  996. } else {
  997. [NSException raise:NSInternalInconsistencyException
  998. format:@"Cannot drill into row for cookie: %@", rowCookie];
  999. return nil;
  1000. }
  1001. }
  1002. return nil;
  1003. }
  1004. - (UIView *)customViewForRowCookie:(id)rowCookie
  1005. {
  1006. return nil;
  1007. }
  1008. - (CGFloat)heightForCustomViewRowForRowCookie:(id)rowCookie
  1009. {
  1010. return self.tableView.rowHeight;
  1011. }
  1012. - (BOOL)canHaveInstanceState
  1013. {
  1014. return YES;
  1015. }
  1016. - (BOOL)canCallInstanceMethods
  1017. {
  1018. return YES;
  1019. }
  1020. @end
  1021. @implementation FLEXObjectExplorerViewController (Shortcuts)
  1022. - (NSArray<NSString *> *)shortcutPropertyNames { return @[]; }
  1023. @end