FLEXObjectExplorerViewController.m 43 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169
  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. BOOL showDescription = YES;
  245. // Not if it's empty or nil.
  246. NSString *descripition = [FLEXUtility safeDescriptionForObject:self.object];
  247. if (showDescription) {
  248. showDescription = [descripition length] > 0;
  249. }
  250. // Not if we have filter text that doesn't match the desctiption.
  251. if (showDescription && [self.filterText length] > 0) {
  252. showDescription = [descripition rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0;
  253. }
  254. return showDescription;
  255. }
  256. #pragma mark - Properties
  257. - (void)updateProperties
  258. {
  259. Class class = [self.object class];
  260. self.properties = [[self class] propertiesForClass:class];
  261. self.propertiesWithParent = [self.properties arrayByAddingObjectsFromArray:[[self class] propertiesForClass:[class superclass]]];
  262. self.inheritedProperties = [self.properties arrayByAddingObjectsFromArray:[[self class] inheritedPropertiesForClass:class]];
  263. self.NSObjectProperties = [[self class] propertiesForClass:[NSObject class]];
  264. }
  265. + (NSArray<FLEXPropertyBox *> *)propertiesForClass:(Class)class
  266. {
  267. if (!class) {
  268. return @[];
  269. }
  270. NSMutableArray<FLEXPropertyBox *> *boxedProperties = [NSMutableArray array];
  271. unsigned int propertyCount = 0;
  272. objc_property_t *propertyList = class_copyPropertyList(class, &propertyCount);
  273. if (propertyList) {
  274. for (unsigned int i = 0; i < propertyCount; i++) {
  275. FLEXPropertyBox *propertyBox = [[FLEXPropertyBox alloc] init];
  276. propertyBox.property = propertyList[i];
  277. [boxedProperties addObject:propertyBox];
  278. }
  279. free(propertyList);
  280. }
  281. return boxedProperties;
  282. }
  283. /// Skips NSObject
  284. + (NSArray<FLEXPropertyBox *> *)inheritedPropertiesForClass:(Class)class
  285. {
  286. NSMutableArray<FLEXPropertyBox *> *inheritedProperties = [NSMutableArray array];
  287. while ((class = [class superclass]) && class != [NSObject class]) {
  288. [inheritedProperties addObjectsFromArray:[self propertiesForClass:class]];
  289. }
  290. return inheritedProperties;
  291. }
  292. - (void)updateFilteredProperties
  293. {
  294. NSArray<FLEXPropertyBox *> *candidateProperties = [self metadata:FLEXMetadataKindProperties forScope:self.scope];
  295. NSArray<FLEXPropertyBox *> *unsortedFilteredProperties = nil;
  296. if ([self.filterText length] > 0) {
  297. NSMutableArray<FLEXPropertyBox *> *mutableUnsortedFilteredProperties = [NSMutableArray array];
  298. for (FLEXPropertyBox *propertyBox in candidateProperties) {
  299. NSString *prettyName = [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  300. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  301. [mutableUnsortedFilteredProperties addObject:propertyBox];
  302. }
  303. }
  304. unsortedFilteredProperties = mutableUnsortedFilteredProperties;
  305. } else {
  306. unsortedFilteredProperties = candidateProperties;
  307. }
  308. self.filteredProperties = [unsortedFilteredProperties sortedArrayUsingComparator:^NSComparisonResult(FLEXPropertyBox *propertyBox1, FLEXPropertyBox *propertyBox2) {
  309. NSString *name1 = [NSString stringWithUTF8String:property_getName(propertyBox1.property)];
  310. NSString *name2 = [NSString stringWithUTF8String:property_getName(propertyBox2.property)];
  311. return [name1 caseInsensitiveCompare:name2];
  312. }];
  313. }
  314. - (NSString *)titleForPropertyAtIndex:(NSInteger)index
  315. {
  316. FLEXPropertyBox *propertyBox = self.filteredProperties[index];
  317. return [FLEXRuntimeUtility prettyNameForProperty:propertyBox.property];
  318. }
  319. - (id)valueForPropertyAtIndex:(NSInteger)index
  320. {
  321. id value = nil;
  322. if ([self canHaveInstanceState]) {
  323. FLEXPropertyBox *propertyBox = self.filteredProperties[index];
  324. NSString *typeString = [FLEXRuntimeUtility typeEncodingForProperty:propertyBox.property];
  325. const FLEXTypeEncoding *encoding = [typeString cStringUsingEncoding:NSUTF8StringEncoding];
  326. value = [FLEXRuntimeUtility valueForProperty:propertyBox.property onObject:self.object];
  327. value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
  328. }
  329. return value;
  330. }
  331. #pragma mark - Ivars
  332. - (void)updateIvars
  333. {
  334. Class class = [self.object class];
  335. self.ivars = [[self class] ivarsForClass:class];
  336. self.ivarsWithParent = [self.ivars arrayByAddingObjectsFromArray:[[self class] ivarsForClass:[class superclass]]];
  337. self.inheritedIvars = [self.ivars arrayByAddingObjectsFromArray:[[self class] inheritedIvarsForClass:class]];
  338. self.NSObjectIvars = [[self class] ivarsForClass:[NSObject class]];
  339. }
  340. + (NSArray<FLEXIvarBox *> *)ivarsForClass:(Class)class
  341. {
  342. if (!class) {
  343. return @[];
  344. }
  345. NSMutableArray<FLEXIvarBox *> *boxedIvars = [NSMutableArray array];
  346. unsigned int ivarCount = 0;
  347. Ivar *ivarList = class_copyIvarList(class, &ivarCount);
  348. if (ivarList) {
  349. for (unsigned int i = 0; i < ivarCount; i++) {
  350. FLEXIvarBox *ivarBox = [[FLEXIvarBox alloc] init];
  351. ivarBox.ivar = ivarList[i];
  352. [boxedIvars addObject:ivarBox];
  353. }
  354. free(ivarList);
  355. }
  356. return boxedIvars;
  357. }
  358. /// Skips NSObject
  359. + (NSArray<FLEXIvarBox *> *)inheritedIvarsForClass:(Class)class
  360. {
  361. NSMutableArray<FLEXIvarBox *> *inheritedIvars = [NSMutableArray array];
  362. while ((class = [class superclass]) && class != [NSObject class]) {
  363. [inheritedIvars addObjectsFromArray:[self ivarsForClass:class]];
  364. }
  365. return inheritedIvars;
  366. }
  367. - (void)updateFilteredIvars
  368. {
  369. NSArray<FLEXIvarBox *> *candidateIvars = [self metadata:FLEXMetadataKindIvars forScope:self.scope];
  370. NSArray<FLEXIvarBox *> *unsortedFilteredIvars = nil;
  371. if ([self.filterText length] > 0) {
  372. NSMutableArray<FLEXIvarBox *> *mutableUnsortedFilteredIvars = [NSMutableArray array];
  373. for (FLEXIvarBox *ivarBox in candidateIvars) {
  374. NSString *prettyName = [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  375. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  376. [mutableUnsortedFilteredIvars addObject:ivarBox];
  377. }
  378. }
  379. unsortedFilteredIvars = mutableUnsortedFilteredIvars;
  380. } else {
  381. unsortedFilteredIvars = candidateIvars;
  382. }
  383. self.filteredIvars = [unsortedFilteredIvars sortedArrayUsingComparator:^NSComparisonResult(FLEXIvarBox *ivarBox1, FLEXIvarBox *ivarBox2) {
  384. NSString *name1 = [NSString stringWithUTF8String:ivar_getName(ivarBox1.ivar)];
  385. NSString *name2 = [NSString stringWithUTF8String:ivar_getName(ivarBox2.ivar)];
  386. return [name1 caseInsensitiveCompare:name2];
  387. }];
  388. }
  389. - (NSString *)titleForIvarAtIndex:(NSInteger)index
  390. {
  391. FLEXIvarBox *ivarBox = self.filteredIvars[index];
  392. return [FLEXRuntimeUtility prettyNameForIvar:ivarBox.ivar];
  393. }
  394. - (id)valueForIvarAtIndex:(NSInteger)index
  395. {
  396. id value = nil;
  397. if ([self canHaveInstanceState]) {
  398. FLEXIvarBox *ivarBox = self.filteredIvars[index];
  399. const FLEXTypeEncoding *encoding = ivar_getTypeEncoding(ivarBox.ivar);
  400. value = [FLEXRuntimeUtility valueForIvar:ivarBox.ivar onObject:self.object];
  401. value = [FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:value type:encoding];
  402. }
  403. return value;
  404. }
  405. #pragma mark - Methods
  406. - (void)updateMethods
  407. {
  408. Class class = [self.object class];
  409. self.methods = [[self class] methodsForClass:class];
  410. self.methodsWithParent = [self.methods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[class superclass]]];
  411. self.inheritedMethods = [self.methods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:class]];
  412. self.NSObjectMethods = [[self class] methodsForClass:[NSObject class]];
  413. }
  414. - (void)updateFilteredMethods
  415. {
  416. NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindMethods forScope:self.scope];
  417. self.filteredMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:NO];
  418. }
  419. - (void)updateClassMethods
  420. {
  421. const char *className = [NSStringFromClass([self.object class]) UTF8String];
  422. Class metaClass = objc_getMetaClass(className);
  423. self.classMethods = [[self class] methodsForClass:metaClass];
  424. self.classMethodsWithParent = [self.classMethods arrayByAddingObjectsFromArray:[[self class] methodsForClass:[metaClass superclass]]];
  425. self.inheritedClassMethods = [self.classMethods arrayByAddingObjectsFromArray:[[self class] inheritedMethodsForClass:metaClass]];
  426. self.NSObjectClassMethods = [[self class] methodsForClass:[NSObject class]];
  427. }
  428. - (void)updateFilteredClassMethods
  429. {
  430. NSArray<FLEXMethodBox *> *candidateMethods = [self metadata:FLEXMetadataKindClassMethods forScope:self.scope];
  431. self.filteredClassMethods = [self filteredMethodsFromMethods:candidateMethods areClassMethods:YES];
  432. }
  433. + (NSArray<FLEXMethodBox *> *)methodsForClass:(Class)class
  434. {
  435. if (!class) {
  436. return @[];
  437. }
  438. NSMutableArray<FLEXMethodBox *> *boxedMethods = [NSMutableArray array];
  439. unsigned int methodCount = 0;
  440. Method *methodList = class_copyMethodList(class, &methodCount);
  441. if (methodList) {
  442. for (unsigned int i = 0; i < methodCount; i++) {
  443. FLEXMethodBox *methodBox = [[FLEXMethodBox alloc] init];
  444. methodBox.method = methodList[i];
  445. [boxedMethods addObject:methodBox];
  446. }
  447. free(methodList);
  448. }
  449. return boxedMethods;
  450. }
  451. /// Skips NSObject
  452. + (NSArray<FLEXMethodBox *> *)inheritedMethodsForClass:(Class)class
  453. {
  454. NSMutableArray<FLEXMethodBox *> *inheritedMethods = [NSMutableArray array];
  455. while ((class = [class superclass]) && class != [NSObject class]) {
  456. [inheritedMethods addObjectsFromArray:[self methodsForClass:class]];
  457. }
  458. return inheritedMethods;
  459. }
  460. - (NSArray<FLEXMethodBox *> *)filteredMethodsFromMethods:(NSArray<FLEXMethodBox *> *)methods areClassMethods:(BOOL)areClassMethods
  461. {
  462. NSArray<FLEXMethodBox *> *candidateMethods = methods;
  463. NSArray<FLEXMethodBox *> *unsortedFilteredMethods = nil;
  464. if ([self.filterText length] > 0) {
  465. NSMutableArray<FLEXMethodBox *> *mutableUnsortedFilteredMethods = [NSMutableArray array];
  466. for (FLEXMethodBox *methodBox in candidateMethods) {
  467. NSString *prettyName = [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:areClassMethods];
  468. if ([prettyName rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  469. [mutableUnsortedFilteredMethods addObject:methodBox];
  470. }
  471. }
  472. unsortedFilteredMethods = mutableUnsortedFilteredMethods;
  473. } else {
  474. unsortedFilteredMethods = candidateMethods;
  475. }
  476. NSArray<FLEXMethodBox *> *sortedFilteredMethods = [unsortedFilteredMethods sortedArrayUsingComparator:^NSComparisonResult(FLEXMethodBox *methodBox1, FLEXMethodBox *methodBox2) {
  477. NSString *name1 = NSStringFromSelector(method_getName(methodBox1.method));
  478. NSString *name2 = NSStringFromSelector(method_getName(methodBox2.method));
  479. return [name1 caseInsensitiveCompare:name2];
  480. }];
  481. return sortedFilteredMethods;
  482. }
  483. - (NSString *)titleForMethodAtIndex:(NSInteger)index
  484. {
  485. FLEXMethodBox *methodBox = self.filteredMethods[index];
  486. return [FLEXRuntimeUtility prettyNameForMethod:methodBox.method isClassMethod:NO];
  487. }
  488. - (NSString *)titleForClassMethodAtIndex:(NSInteger)index
  489. {
  490. FLEXMethodBox *classMethodBox = self.filteredClassMethods[index];
  491. return [FLEXRuntimeUtility prettyNameForMethod:classMethodBox.method isClassMethod:YES];
  492. }
  493. #pragma mark - Superclasses
  494. + (NSArray<Class> *)superclassesForClass:(Class)class
  495. {
  496. NSMutableArray<Class> *superClasses = [NSMutableArray array];
  497. while ((class = [class superclass])) {
  498. [superClasses addObject:class];
  499. }
  500. return superClasses;
  501. }
  502. - (void)updateSuperclasses
  503. {
  504. self.superclasses = [[self class] superclassesForClass:[self.object class]];
  505. }
  506. - (void)updateFilteredSuperclasses
  507. {
  508. if ([self.filterText length] > 0) {
  509. NSMutableArray<Class> *filteredSuperclasses = [NSMutableArray array];
  510. for (Class superclass in self.superclasses) {
  511. if ([NSStringFromClass(superclass) rangeOfString:self.filterText options:NSCaseInsensitiveSearch].length > 0) {
  512. [filteredSuperclasses addObject:superclass];
  513. }
  514. }
  515. self.filteredSuperclasses = filteredSuperclasses;
  516. } else {
  517. self.filteredSuperclasses = self.superclasses;
  518. }
  519. }
  520. #pragma mark - Table View Data Helpers
  521. - (NSArray<NSNumber *> *)possibleExplorerSections
  522. {
  523. static NSArray<NSNumber *> *possibleSections = nil;
  524. static dispatch_once_t onceToken;
  525. dispatch_once(&onceToken, ^{
  526. possibleSections = @[@(FLEXObjectExplorerSectionDescription),
  527. @(FLEXObjectExplorerSectionCustom),
  528. @(FLEXObjectExplorerSectionProperties),
  529. @(FLEXObjectExplorerSectionIvars),
  530. @(FLEXObjectExplorerSectionMethods),
  531. @(FLEXObjectExplorerSectionClassMethods),
  532. @(FLEXObjectExplorerSectionSuperclasses),
  533. @(FLEXObjectExplorerSectionReferencingInstances)];
  534. });
  535. return possibleSections;
  536. }
  537. - (NSArray<NSNumber *> *)visibleExplorerSections
  538. {
  539. NSMutableArray<NSNumber *> *visibleSections = [NSMutableArray array];
  540. for (NSNumber *possibleSection in [self possibleExplorerSections]) {
  541. FLEXObjectExplorerSection explorerSection = [possibleSection unsignedIntegerValue];
  542. if ([self numberOfRowsForExplorerSection:explorerSection] > 0) {
  543. [visibleSections addObject:possibleSection];
  544. }
  545. }
  546. return visibleSections;
  547. }
  548. - (NSString *)sectionTitleWithBaseName:(NSString *)baseName totalCount:(NSUInteger)totalCount filteredCount:(NSUInteger)filteredCount
  549. {
  550. NSString *sectionTitle = nil;
  551. if (totalCount == filteredCount) {
  552. sectionTitle = [baseName stringByAppendingFormat:@" (%lu)", (unsigned long)totalCount];
  553. } else {
  554. sectionTitle = [baseName stringByAppendingFormat:@" (%lu of %lu)", (unsigned long)filteredCount, (unsigned long)totalCount];
  555. }
  556. return sectionTitle;
  557. }
  558. - (FLEXObjectExplorerSection)explorerSectionAtIndex:(NSInteger)sectionIndex
  559. {
  560. return [[[self visibleExplorerSections] objectAtIndex:sectionIndex] unsignedIntegerValue];
  561. }
  562. - (NSInteger)numberOfRowsForExplorerSection:(FLEXObjectExplorerSection)section
  563. {
  564. NSInteger numberOfRows = 0;
  565. switch (section) {
  566. case FLEXObjectExplorerSectionDescription:
  567. numberOfRows = [self shouldShowDescription] ? 1 : 0;
  568. break;
  569. case FLEXObjectExplorerSectionCustom:
  570. numberOfRows = [self.customSectionVisibleIndexes count];
  571. break;
  572. case FLEXObjectExplorerSectionProperties:
  573. numberOfRows = [self.filteredProperties count];
  574. break;
  575. case FLEXObjectExplorerSectionIvars:
  576. numberOfRows = [self.filteredIvars count];
  577. break;
  578. case FLEXObjectExplorerSectionMethods:
  579. numberOfRows = [self.filteredMethods count];
  580. break;
  581. case FLEXObjectExplorerSectionClassMethods:
  582. numberOfRows = [self.filteredClassMethods count];
  583. break;
  584. case FLEXObjectExplorerSectionSuperclasses:
  585. numberOfRows = [self.filteredSuperclasses count];
  586. break;
  587. case FLEXObjectExplorerSectionReferencingInstances:
  588. // Hide this section if there is fliter text since there's nothing searchable (only 1 row, always the same).
  589. numberOfRows = [self.filterText length] == 0 ? 1 : 0;
  590. break;
  591. }
  592. return numberOfRows;
  593. }
  594. - (NSString *)titleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  595. {
  596. NSString *title = nil;
  597. switch (section) {
  598. case FLEXObjectExplorerSectionDescription:
  599. title = [FLEXUtility safeDescriptionForObject:self.object];
  600. break;
  601. case FLEXObjectExplorerSectionCustom:
  602. title = [self customSectionTitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  603. break;
  604. case FLEXObjectExplorerSectionProperties:
  605. title = [self titleForPropertyAtIndex:row];
  606. break;
  607. case FLEXObjectExplorerSectionIvars:
  608. title = [self titleForIvarAtIndex:row];
  609. break;
  610. case FLEXObjectExplorerSectionMethods:
  611. title = [self titleForMethodAtIndex:row];
  612. break;
  613. case FLEXObjectExplorerSectionClassMethods:
  614. title = [self titleForClassMethodAtIndex:row];
  615. break;
  616. case FLEXObjectExplorerSectionSuperclasses:
  617. title = NSStringFromClass(self.filteredSuperclasses[row]);
  618. break;
  619. case FLEXObjectExplorerSectionReferencingInstances:
  620. title = @"Other objects with ivars referencing this object";
  621. break;
  622. }
  623. return title;
  624. }
  625. - (NSString *)subtitleForRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  626. {
  627. NSString *subtitle = nil;
  628. switch (section) {
  629. case FLEXObjectExplorerSectionDescription:
  630. break;
  631. case FLEXObjectExplorerSectionCustom:
  632. subtitle = [self customSectionSubtitleForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  633. break;
  634. case FLEXObjectExplorerSectionProperties:
  635. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForPropertyAtIndex:row]] : nil;
  636. break;
  637. case FLEXObjectExplorerSectionIvars:
  638. subtitle = [self canHaveInstanceState] ? [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:[self valueForIvarAtIndex:row]] : nil;
  639. break;
  640. case FLEXObjectExplorerSectionMethods:
  641. break;
  642. case FLEXObjectExplorerSectionClassMethods:
  643. break;
  644. case FLEXObjectExplorerSectionSuperclasses:
  645. break;
  646. case FLEXObjectExplorerSectionReferencingInstances:
  647. break;
  648. }
  649. return subtitle;
  650. }
  651. - (BOOL)canDrillInToRow:(NSInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  652. {
  653. BOOL canDrillIn = NO;
  654. switch (section) {
  655. case FLEXObjectExplorerSectionDescription:
  656. break;
  657. case FLEXObjectExplorerSectionCustom:
  658. canDrillIn = [self customSectionCanDrillIntoRowWithCookie:[self customSectionRowCookieForVisibleRow:row]];
  659. break;
  660. case FLEXObjectExplorerSectionProperties: {
  661. if ([self canHaveInstanceState]) {
  662. FLEXPropertyBox *propertyBox = self.filteredProperties[row];
  663. objc_property_t property = propertyBox.property;
  664. id currentValue = [self valueForPropertyAtIndex:row];
  665. BOOL canEdit = [FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue];
  666. BOOL canExplore = currentValue != nil;
  667. canDrillIn = canEdit || canExplore;
  668. }
  669. } break;
  670. case FLEXObjectExplorerSectionIvars: {
  671. if ([self canHaveInstanceState]) {
  672. FLEXIvarBox *ivarBox = self.filteredIvars[row];
  673. Ivar ivar = ivarBox.ivar;
  674. id currentValue = [self valueForIvarAtIndex:row];
  675. BOOL canEdit = [FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue];
  676. BOOL canExplore = currentValue != nil;
  677. canDrillIn = canEdit || canExplore;
  678. }
  679. } break;
  680. case FLEXObjectExplorerSectionMethods:
  681. canDrillIn = [self canCallInstanceMethods];
  682. break;
  683. case FLEXObjectExplorerSectionClassMethods:
  684. canDrillIn = YES;
  685. break;
  686. case FLEXObjectExplorerSectionSuperclasses:
  687. canDrillIn = YES;
  688. break;
  689. case FLEXObjectExplorerSectionReferencingInstances:
  690. canDrillIn = YES;
  691. break;
  692. }
  693. return canDrillIn;
  694. }
  695. - (BOOL)sectionHasActions:(NSInteger)section
  696. {
  697. return [self explorerSectionAtIndex:section] == FLEXObjectExplorerSectionDescription;
  698. }
  699. - (NSString *)titleForExplorerSection:(FLEXObjectExplorerSection)section
  700. {
  701. NSString *title = nil;
  702. switch (section) {
  703. case FLEXObjectExplorerSectionDescription: {
  704. title = @"Description";
  705. } break;
  706. case FLEXObjectExplorerSectionCustom: {
  707. title = [self customSectionTitle];
  708. } break;
  709. case FLEXObjectExplorerSectionProperties: {
  710. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindProperties forScope:self.scope];
  711. title = [self sectionTitleWithBaseName:@"Properties" totalCount:totalCount filteredCount:[self.filteredProperties count]];
  712. } break;
  713. case FLEXObjectExplorerSectionIvars: {
  714. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindIvars forScope:self.scope];
  715. title = [self sectionTitleWithBaseName:@"Ivars" totalCount:totalCount filteredCount:[self.filteredIvars count]];
  716. } break;
  717. case FLEXObjectExplorerSectionMethods: {
  718. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindMethods forScope:self.scope];
  719. title = [self sectionTitleWithBaseName:@"Methods" totalCount:totalCount filteredCount:[self.filteredMethods count]];
  720. } break;
  721. case FLEXObjectExplorerSectionClassMethods: {
  722. NSUInteger totalCount = [self totalCountOfMetadata:FLEXMetadataKindClassMethods forScope:self.scope];
  723. title = [self sectionTitleWithBaseName:@"Class Methods" totalCount:totalCount filteredCount:[self.filteredClassMethods count]];
  724. } break;
  725. case FLEXObjectExplorerSectionSuperclasses: {
  726. title = [self sectionTitleWithBaseName:@"Superclasses" totalCount:[self.superclasses count] filteredCount:[self.filteredSuperclasses count]];
  727. } break;
  728. case FLEXObjectExplorerSectionReferencingInstances: {
  729. title = @"Object Graph";
  730. } break;
  731. }
  732. return title;
  733. }
  734. - (UIViewController *)drillInViewControllerForRow:(NSUInteger)row inExplorerSection:(FLEXObjectExplorerSection)section
  735. {
  736. UIViewController *viewController = nil;
  737. switch (section) {
  738. case FLEXObjectExplorerSectionDescription:
  739. break;
  740. case FLEXObjectExplorerSectionCustom:
  741. viewController = [self customSectionDrillInViewControllerForRowCookie:[self customSectionRowCookieForVisibleRow:row]];
  742. break;
  743. case FLEXObjectExplorerSectionProperties: {
  744. FLEXPropertyBox *propertyBox = self.filteredProperties[row];
  745. objc_property_t property = propertyBox.property;
  746. id currentValue = [self valueForPropertyAtIndex:row];
  747. if ([FLEXPropertyEditorViewController canEditProperty:property currentValue:currentValue]) {
  748. viewController = [[FLEXPropertyEditorViewController alloc] initWithTarget:self.object property:property];
  749. } else if (currentValue) {
  750. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  751. }
  752. } break;
  753. case FLEXObjectExplorerSectionIvars: {
  754. FLEXIvarBox *ivarBox = self.filteredIvars[row];
  755. Ivar ivar = ivarBox.ivar;
  756. id currentValue = [self valueForIvarAtIndex:row];
  757. if ([FLEXIvarEditorViewController canEditIvar:ivar currentValue:currentValue]) {
  758. viewController = [[FLEXIvarEditorViewController alloc] initWithTarget:self.object ivar:ivar];
  759. } else if (currentValue) {
  760. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  761. }
  762. } break;
  763. case FLEXObjectExplorerSectionMethods: {
  764. FLEXMethodBox *methodBox = self.filteredMethods[row];
  765. Method method = methodBox.method;
  766. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:self.object method:method];
  767. } break;
  768. case FLEXObjectExplorerSectionClassMethods: {
  769. FLEXMethodBox *methodBox = self.filteredClassMethods[row];
  770. Method method = methodBox.method;
  771. viewController = [[FLEXMethodCallingViewController alloc] initWithTarget:[self.object class] method:method];
  772. } break;
  773. case FLEXObjectExplorerSectionSuperclasses: {
  774. Class superclass = self.filteredSuperclasses[row];
  775. viewController = [FLEXObjectExplorerFactory explorerViewControllerForObject:superclass];
  776. } break;
  777. case FLEXObjectExplorerSectionReferencingInstances: {
  778. viewController = [FLEXInstancesTableViewController instancesTableViewControllerForInstancesReferencingObject:self.object];
  779. } break;
  780. }
  781. return viewController;
  782. }
  783. #pragma mark - Table View Data Source
  784. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  785. {
  786. return [[self visibleExplorerSections] count];
  787. }
  788. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  789. {
  790. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  791. return [self numberOfRowsForExplorerSection:explorerSection];
  792. }
  793. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section
  794. {
  795. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:section];
  796. return [self titleForExplorerSection:explorerSection];
  797. }
  798. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  799. {
  800. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  801. BOOL isCustomSection = explorerSection == FLEXObjectExplorerSectionCustom;
  802. BOOL useDescriptionCell = explorerSection == FLEXObjectExplorerSectionDescription;
  803. NSString *cellIdentifier = useDescriptionCell ? kFLEXMultilineTableViewCellIdentifier : @"cell";
  804. UITableViewCell *cell = [tableView dequeueReusableCellWithIdentifier:cellIdentifier];
  805. if (!cell) {
  806. if (useDescriptionCell) {
  807. cell = [[FLEXMultilineTableViewCell alloc] initWithStyle:UITableViewCellStyleDefault reuseIdentifier:cellIdentifier];
  808. cell.textLabel.font = [FLEXUtility defaultTableViewCellLabelFont];
  809. } else {
  810. cell = [[UITableViewCell alloc] initWithStyle:UITableViewCellStyleSubtitle reuseIdentifier:cellIdentifier];
  811. UIFont *cellFont = [FLEXUtility defaultTableViewCellLabelFont];
  812. cell.textLabel.font = cellFont;
  813. cell.detailTextLabel.font = cellFont;
  814. cell.detailTextLabel.textColor = [UIColor grayColor];
  815. }
  816. }
  817. UIView *customView;
  818. if (isCustomSection) {
  819. customView = [self customViewForRowCookie:[self customSectionRowCookieForVisibleRow:indexPath.row]];
  820. if (customView) {
  821. [cell.contentView addSubview:customView];
  822. }
  823. }
  824. cell.textLabel.text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  825. cell.detailTextLabel.text = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  826. cell.accessoryType = [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection] ? UITableViewCellAccessoryDisclosureIndicator : UITableViewCellAccessoryNone;
  827. return cell;
  828. }
  829. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath
  830. {
  831. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  832. CGFloat height = self.tableView.rowHeight;
  833. if (explorerSection == FLEXObjectExplorerSectionDescription) {
  834. NSString *text = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  835. NSAttributedString *attributedText = [[NSAttributedString alloc] initWithString:text attributes:@{ NSFontAttributeName : [FLEXUtility defaultTableViewCellLabelFont] }];
  836. CGFloat preferredHeight = [FLEXMultilineTableViewCell preferredHeightWithAttributedText:attributedText inTableViewWidth:self.tableView.frame.size.width style:tableView.style showsAccessory:NO];
  837. height = MAX(height, preferredHeight);
  838. } else if (explorerSection == FLEXObjectExplorerSectionCustom) {
  839. id cookie = [self customSectionRowCookieForVisibleRow:indexPath.row];
  840. height = [self heightForCustomViewRowForRowCookie:cookie];
  841. }
  842. return height;
  843. }
  844. #pragma mark - Table View Delegate
  845. - (BOOL)tableView:(UITableView *)tableView shouldHighlightRowAtIndexPath:(NSIndexPath *)indexPath
  846. {
  847. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  848. return [self canDrillInToRow:indexPath.row inExplorerSection:explorerSection];
  849. }
  850. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
  851. {
  852. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  853. UIViewController *detailViewController = [self drillInViewControllerForRow:indexPath.row inExplorerSection:explorerSection];
  854. if (detailViewController) {
  855. [self.navigationController pushViewController:detailViewController animated:YES];
  856. } else {
  857. [tableView deselectRowAtIndexPath:indexPath animated:YES];
  858. }
  859. }
  860. - (BOOL)tableView:(UITableView *)tableView shouldShowMenuForRowAtIndexPath:(NSIndexPath *)indexPath
  861. {
  862. return [self sectionHasActions:indexPath.section];
  863. }
  864. - (BOOL)tableView:(UITableView *)tableView canPerformAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  865. {
  866. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  867. switch (explorerSection) {
  868. case FLEXObjectExplorerSectionDescription:
  869. return action == @selector(copy:) || action == @selector(copyObjectAddress:);
  870. default:
  871. return NO;
  872. }
  873. }
  874. - (void)tableView:(UITableView *)tableView performAction:(SEL)action forRowAtIndexPath:(NSIndexPath *)indexPath withSender:(id)sender
  875. {
  876. #pragma clang diagnostic push
  877. #pragma clang diagnostic ignored "-Warc-performSelector-leaks"
  878. [self performSelector:action withObject:indexPath];
  879. #pragma clang diagnostic pop
  880. }
  881. #pragma mark - UIMenuController
  882. /// Prevent the search bar from trying to use us as a responder
  883. ///
  884. /// Our table cells will use the UITableViewDelegate methods
  885. /// to make sure we can perform the actions we want to
  886. - (BOOL)canPerformAction:(SEL)action withSender:(id)sender
  887. {
  888. return NO;
  889. }
  890. - (void)copy:(NSIndexPath *)indexPath
  891. {
  892. FLEXObjectExplorerSection explorerSection = [self explorerSectionAtIndex:indexPath.section];
  893. NSString *stringToCopy = @"";
  894. NSString *title = [self titleForRow:indexPath.row inExplorerSection:explorerSection];
  895. if (title.length) {
  896. stringToCopy = [stringToCopy stringByAppendingString:title];
  897. }
  898. NSString *subtitle = [self subtitleForRow:indexPath.row inExplorerSection:explorerSection];
  899. if (subtitle.length) {
  900. if (stringToCopy.length) {
  901. stringToCopy = [stringToCopy stringByAppendingString:@"\n\n"];
  902. }
  903. stringToCopy = [stringToCopy stringByAppendingString:subtitle];
  904. }
  905. [UIPasteboard generalPasteboard].string = stringToCopy;
  906. }
  907. - (void)copyObjectAddress:(NSIndexPath *)indexPath
  908. {
  909. [UIPasteboard generalPasteboard].string = [FLEXUtility addressOfObject:self.object];
  910. }
  911. #pragma mark - Custom Section
  912. - (void)updateCustomData
  913. {
  914. self.cachedCustomSectionRowCookies = [self customSectionRowCookies];
  915. }
  916. - (void)updateFilteredCustomData
  917. {
  918. NSIndexSet *filteredIndexSet = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(0, [self.cachedCustomSectionRowCookies count])];
  919. if ([self.filterText length] > 0) {
  920. filteredIndexSet = [filteredIndexSet indexesPassingTest:^BOOL(NSUInteger index, BOOL *stop) {
  921. BOOL matches = NO;
  922. NSString *rowTitle = [self customSectionTitleForRowCookie:self.cachedCustomSectionRowCookies[index]];
  923. if ([rowTitle rangeOfString:self.filterText options:NSCaseInsensitiveSearch].location != NSNotFound) {
  924. matches = YES;
  925. }
  926. return matches;
  927. }];
  928. }
  929. self.customSectionVisibleIndexes = filteredIndexSet;
  930. }
  931. - (id)customSectionRowCookieForVisibleRow:(NSUInteger)row
  932. {
  933. return [[self.cachedCustomSectionRowCookies objectsAtIndexes:self.customSectionVisibleIndexes] objectAtIndex:row];
  934. }
  935. #pragma mark - Subclasses Can Override
  936. - (NSString *)customSectionTitle
  937. {
  938. return nil;
  939. }
  940. - (NSArray *)customSectionRowCookies
  941. {
  942. return nil;
  943. }
  944. - (NSString *)customSectionTitleForRowCookie:(id)rowCookie
  945. {
  946. return nil;
  947. }
  948. - (NSString *)customSectionSubtitleForRowCookie:(id)rowCookie
  949. {
  950. return nil;
  951. }
  952. - (BOOL)customSectionCanDrillIntoRowWithCookie:(id)rowCookie
  953. {
  954. return NO;
  955. }
  956. - (UIViewController *)customSectionDrillInViewControllerForRowCookie:(id)rowCookie
  957. {
  958. return nil;
  959. }
  960. - (UIView *)customViewForRowCookie:(id)rowCookie
  961. {
  962. return nil;
  963. }
  964. - (CGFloat)heightForCustomViewRowForRowCookie:(id)rowCookie
  965. {
  966. return self.tableView.rowHeight;
  967. }
  968. - (BOOL)canHaveInstanceState
  969. {
  970. return YES;
  971. }
  972. - (BOOL)canCallInstanceMethods
  973. {
  974. return YES;
  975. }
  976. @end