FLEXObjectExplorerViewController.m 45 KB

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