FLEXMetadataSection.m 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. //
  2. // FLEXMetadataSection.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 9/19/19.
  6. // Copyright © 2019 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXMetadataSection.h"
  9. #import "FLEXTableView.h"
  10. #import "FLEXTableViewCell.h"
  11. #import "FLEXObjectExplorerFactory.h"
  12. #import "FLEXFieldEditorViewController.h"
  13. #import "FLEXMethodCallingViewController.h"
  14. #import "FLEXIvar.h"
  15. #import "NSArray+Functional.h"
  16. #import "FLEXRuntime+UIKitHelpers.h"
  17. @interface FLEXMetadataSection ()
  18. @property (nonatomic, readonly) FLEXObjectExplorer *explorer;
  19. /// Filtered
  20. @property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *metadata;
  21. /// Unfiltered
  22. @property (nonatomic, copy) NSArray<id<FLEXRuntimeMetadata>> *allMetadata;
  23. @end
  24. @implementation FLEXMetadataSection
  25. #pragma mark - Initialization
  26. + (instancetype)explorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
  27. return [[self alloc] initWithExplorer:explorer kind:metadataKind];
  28. }
  29. - (id)initWithExplorer:(FLEXObjectExplorer *)explorer kind:(FLEXMetadataKind)metadataKind {
  30. self = [super init];
  31. if (self) {
  32. _explorer = explorer;
  33. _metadataKind = metadataKind;
  34. [self reloadData];
  35. }
  36. return self;
  37. }
  38. #pragma mark - Private
  39. - (NSString *)titleWithBaseName:(NSString *)baseName {
  40. unsigned long totalCount = self.allMetadata.count;
  41. unsigned long filteredCount = self.metadata.count;
  42. if (totalCount == filteredCount) {
  43. return [baseName stringByAppendingFormat:@" (%lu)", totalCount];
  44. } else {
  45. return [baseName stringByAppendingFormat:@" (%lu of %lu)", filteredCount, totalCount];
  46. }
  47. }
  48. - (__kindof UIViewController *)detailScreenForMetadata:(id)metadata {
  49. id target = self.explorer.object;
  50. switch (self.metadataKind) {
  51. case FLEXMetadataKindClassProperties:
  52. if (self.explorer.objectIsInstance) {
  53. target = [target class];
  54. }
  55. // Fallthrough
  56. case FLEXMetadataKindProperties:
  57. case FLEXMetadataKindIvars: {
  58. // This works for both properties and ivars, we just
  59. // cast so the compiler knows what return type to expect since
  60. // this method has the same name as -[NSValue getValue:] (void *)
  61. id currentValue = [(FLEXIvar *)metadata getPotentiallyUnboxedValue:target];
  62. return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
  63. }
  64. case FLEXMetadataKindClassMethods:
  65. if (self.explorer.objectIsInstance) {
  66. target = [target class];
  67. }
  68. // Fallthrough
  69. case FLEXMetadataKindMethods: {
  70. return [FLEXMethodCallingViewController target:target method:metadata];
  71. }
  72. }
  73. }
  74. // Only for properties or ivars
  75. - (id)valueForRow:(NSInteger)row {
  76. id metadata = self.metadata[row];
  77. // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
  78. // because we want to "preview" what object is being stored if this is
  79. // a void * or something and we're given an NSValue back from getValue:
  80. switch (self.metadataKind) {
  81. case FLEXMetadataKindProperties:
  82. return [self.explorer valueForProperty:metadata];
  83. case FLEXMetadataKindClassProperties:
  84. return [self.explorer valueForProperty:metadata];
  85. case FLEXMetadataKindIvars:
  86. return [self.explorer valueForIvar:metadata];
  87. // Methods: nil
  88. case FLEXMetadataKindMethods:
  89. case FLEXMetadataKindClassMethods:
  90. return nil;
  91. }
  92. }
  93. - (UITableViewCellAccessoryType)accessoryTypeForRow:(NSInteger)row {
  94. return [self.metadata[row] suggestedAccessoryTypeWithTarget:self.explorer.object];
  95. }
  96. #pragma mark - Public
  97. - (void)setExcludedMetadata:(NSSet<NSString *> *)excludedMetadata {
  98. _excludedMetadata = excludedMetadata;
  99. [self reloadData];
  100. }
  101. #pragma mark - Overrides
  102. - (NSString *)titleForRow:(NSInteger)row {
  103. return [self.metadata[row] description];
  104. }
  105. - (NSString *)subtitleForRow:(NSInteger)row {
  106. id metadata = self.metadata[row];
  107. // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
  108. // because we want to "preview" what object is being stored if this is
  109. // a void * or something and we're given an NSValue back from getValue:
  110. switch (self.metadataKind) {
  111. case FLEXMetadataKindClassProperties:
  112. return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
  113. case FLEXMetadataKindProperties:
  114. case FLEXMetadataKindIvars:
  115. if (!self.explorer.objectIsInstance) {
  116. return nil;
  117. }
  118. return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
  119. case FLEXMetadataKindMethods:
  120. case FLEXMetadataKindClassMethods:
  121. return [metadata selectorString];
  122. }
  123. }
  124. - (NSString *)title {
  125. switch (self.metadataKind) {
  126. case FLEXMetadataKindProperties:
  127. return [self titleWithBaseName:@"Properties"];
  128. case FLEXMetadataKindClassProperties:
  129. return [self titleWithBaseName:@"Class Properties"];
  130. case FLEXMetadataKindIvars:
  131. return [self titleWithBaseName:@"Ivars"];
  132. case FLEXMetadataKindMethods:
  133. return [self titleWithBaseName:@"Methods"];
  134. case FLEXMetadataKindClassMethods:
  135. return [self titleWithBaseName:@"Class Methods"];
  136. }
  137. }
  138. - (NSInteger)numberOfRows {
  139. return self.metadata.count;
  140. }
  141. - (void)setFilterText:(NSString *)filterText {
  142. super.filterText = filterText;
  143. if (!self.filterText.length) {
  144. self.metadata = self.allMetadata;
  145. } else {
  146. self.metadata = [self.allMetadata flex_filtered:^BOOL(FLEXProperty *obj, NSUInteger idx) {
  147. return [obj.description localizedCaseInsensitiveContainsString:self.filterText];
  148. }];
  149. }
  150. }
  151. - (void)reloadData {
  152. switch (self.metadataKind) {
  153. case FLEXMetadataKindProperties:
  154. self.allMetadata = self.explorer.properties;
  155. break;
  156. case FLEXMetadataKindClassProperties:
  157. self.allMetadata = self.explorer.classProperties;
  158. break;
  159. case FLEXMetadataKindIvars:
  160. self.allMetadata = self.explorer.ivars;
  161. break;
  162. case FLEXMetadataKindMethods:
  163. self.allMetadata = self.explorer.methods;
  164. break;
  165. case FLEXMetadataKindClassMethods:
  166. self.allMetadata = self.explorer.classMethods;
  167. break;
  168. }
  169. // Remove excluded metadata
  170. if (self.excludedMetadata.count) {
  171. id filterBlock = ^BOOL(id obj, NSUInteger idx) {
  172. return ![self.excludedMetadata containsObject:[obj name]];
  173. };
  174. // Filter exclusions and sort
  175. self.allMetadata = [[self.allMetadata flex_filtered:filterBlock]
  176. sortedArrayUsingSelector:@selector(compare:)
  177. ];
  178. }
  179. // Re-filter data
  180. self.filterText = self.filterText;
  181. }
  182. - (BOOL)canSelectRow:(NSInteger)row {
  183. switch (self.metadataKind) {
  184. case FLEXMetadataKindProperties:
  185. case FLEXMetadataKindIvars:
  186. if (![self valueForRow:row]) {
  187. return NO;
  188. }
  189. case FLEXMetadataKindMethods:
  190. return self.explorer.objectIsInstance;
  191. case FLEXMetadataKindClassMethods:
  192. case FLEXMetadataKindClassProperties:
  193. return YES;
  194. }
  195. }
  196. - (NSString *)reuseIdentifierForRow:(NSInteger)row {
  197. return kFLEXDetailCell;
  198. }
  199. - (UIViewController *)viewControllerToPushForRow:(NSInteger)row {
  200. return [self detailScreenForMetadata:self.metadata[row]];
  201. }
  202. - (void (^)(UIViewController *))didPressInfoButtonAction:(NSInteger)row {
  203. return ^(UIViewController *host) {
  204. [host.navigationController pushViewController:[self editorForRow:row] animated:YES];
  205. };
  206. }
  207. - (UIViewController *)editorForRow:(NSInteger)row {
  208. NSAssert(
  209. self.metadataKind == FLEXMetadataKindIvars ||
  210. self.metadataKind == FLEXMetadataKindProperties ||
  211. self.metadataKind == FLEXMetadataKindClassProperties,
  212. @"Only ivars or properties can be edited"
  213. );
  214. id metadata = self.metadata[row];
  215. // Nil editor means unsupported ivar or property type, or nil value
  216. switch (self.metadataKind) {
  217. case FLEXMetadataKindProperties:
  218. NSAssert(self.explorer.objectIsInstance, @"Unreachable state: class object editing instance property");
  219. return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
  220. case FLEXMetadataKindClassProperties:
  221. if (self.explorer.objectIsInstance) {
  222. return [FLEXFieldEditorViewController target:[self.explorer.object class] property:metadata];
  223. } else {
  224. return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
  225. }
  226. case FLEXMetadataKindIvars:
  227. NSAssert(self.explorer.objectIsInstance, @"Unreachable state: class object editing ivar");
  228. return [FLEXFieldEditorViewController target:self.explorer.object ivar:metadata];
  229. default:
  230. break;
  231. }
  232. // Methods can't be edited
  233. @throw NSInternalInconsistencyException;
  234. return nil;
  235. }
  236. - (void)configureCell:(__kindof FLEXTableViewCell *)cell forRow:(NSInteger)row {
  237. cell.titleLabel.text = [self titleForRow:row];
  238. cell.subtitleLabel.text = [self subtitleForRow:row];
  239. cell.accessoryType = [self accessoryTypeForRow:row];
  240. }
  241. @end