FLEXRuntime+UIKitHelpers.m 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515
  1. //
  2. // FLEXRuntime+UIKitHelpers.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 12/16/19.
  6. // Copyright © 2019 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXRuntime+UIKitHelpers.h"
  9. #import "FLEXRuntimeUtility.h"
  10. #import "FLEXPropertyAttributes.h"
  11. #import "FLEXArgumentInputViewFactory.h"
  12. #import "FLEXObjectExplorerFactory.h"
  13. #import "FLEXFieldEditorViewController.h"
  14. #import "FLEXMethodCallingViewController.h"
  15. #import "FLEXTableView.h"
  16. #import "FLEXUtility.h"
  17. #import "NSArray+Functional.h"
  18. #import "NSString+FLEX.h"
  19. #pragma mark FLEXProperty
  20. @implementation FLEXProperty (UIKitHelpers)
  21. /// Decide whether to use potentialTarget or [potentialTarget class] to get or set property
  22. - (id)appropriateTargetForPropertyType:(id)potentialTarget {
  23. if (!object_isClass(potentialTarget)) {
  24. if (self.isClassProperty) {
  25. return [potentialTarget class];
  26. } else {
  27. return potentialTarget;
  28. }
  29. } else {
  30. if (self.isClassProperty) {
  31. return potentialTarget;
  32. } else {
  33. // Instance property with a class object
  34. return nil;
  35. }
  36. }
  37. }
  38. - (BOOL)isEditable {
  39. if (self.attributes.isReadOnly) {
  40. return self.likelySetterExists;
  41. }
  42. const FLEXTypeEncoding *typeEncoding = self.attributes.typeEncoding.UTF8String;
  43. return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
  44. }
  45. - (BOOL)isCallable {
  46. return YES;
  47. }
  48. - (id)currentValueWithTarget:(id)object {
  49. return [self getPotentiallyUnboxedValue:
  50. [self appropriateTargetForPropertyType:object]
  51. ];
  52. }
  53. - (id)currentValueBeforeUnboxingWithTarget:(id)object {
  54. return [self getValue:
  55. [self appropriateTargetForPropertyType:object]
  56. ];
  57. }
  58. - (NSString *)previewWithTarget:(id)object {
  59. if (object_isClass(object) && !self.isClassProperty) {
  60. return self.attributes.fullDeclaration;
  61. } else {
  62. return [FLEXRuntimeUtility
  63. summaryForObject:[self currentValueWithTarget:object]
  64. ];
  65. }
  66. }
  67. - (UIViewController *)viewerWithTarget:(id)object {
  68. id value = [self currentValueWithTarget:object];
  69. return [FLEXObjectExplorerFactory explorerViewControllerForObject:value];
  70. }
  71. - (UIViewController *)editorWithTarget:(id)object {
  72. id target = [self appropriateTargetForPropertyType:object];
  73. return [FLEXFieldEditorViewController target:target property:self];
  74. }
  75. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  76. id targetForValueCheck = [self appropriateTargetForPropertyType:object];
  77. if (!targetForValueCheck) {
  78. // Instance property with a class object
  79. return UITableViewCellAccessoryNone;
  80. }
  81. // We use .tag to store the cached value of .isEditable that is
  82. // initialized by FLEXObjectExplorer in -reloadMetada
  83. if ([self getPotentiallyUnboxedValue:targetForValueCheck]) {
  84. if (self.tag) {
  85. // Editable non-nil value, both
  86. return UITableViewCellAccessoryDetailDisclosureButton;
  87. } else {
  88. // Uneditable non-nil value, chevron only
  89. return UITableViewCellAccessoryDisclosureIndicator;
  90. }
  91. } else {
  92. if (self.tag) {
  93. // Editable nil value, just (i)
  94. return UITableViewCellAccessoryDetailButton;
  95. } else {
  96. // Non-editable nil value, neither
  97. return UITableViewCellAccessoryNone;
  98. }
  99. }
  100. }
  101. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  102. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  103. BOOL returnsObject = self.attributes.typeEncoding.typeIsObjectOrClass;
  104. id value = [self currentValueBeforeUnboxingWithTarget:object];
  105. return @[
  106. @"Name", self.name ?: @"",
  107. @"Type", self.attributes.typeEncoding ?: @"",
  108. @"Declaration", self.fullDescription ?: @"",
  109. @"Value Preview", [self previewWithTarget:object],
  110. @"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
  111. @"Getter", NSStringFromSelector(self.likelyGetter) ?: @"",
  112. @"Setter", self.likelySetterExists ? NSStringFromSelector(self.likelySetter) : @"",
  113. @"Image Name", self.imageName ?: @"",
  114. @"Attributes", self.attributes.string ?: @"",
  115. @"objc_property", [FLEXUtility pointerToString:self.objc_property],
  116. @"objc_property_attribute_t", [FLEXUtility pointerToString:self.attributes.list],
  117. ];
  118. }
  119. - (NSString *)contextualSubtitleWithTarget:(id)object {
  120. id target = [self appropriateTargetForPropertyType:object];
  121. if (target && self.attributes.typeEncoding.typeIsObjectOrClass) {
  122. return [FLEXUtility addressOfObject:[self currentValueBeforeUnboxingWithTarget:target]];
  123. }
  124. return nil;
  125. }
  126. @end
  127. #pragma mark FLEXIvar
  128. @implementation FLEXIvar (UIKitHelpers)
  129. - (BOOL)isEditable {
  130. const FLEXTypeEncoding *typeEncoding = self.typeEncoding.UTF8String;
  131. return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
  132. }
  133. - (BOOL)isCallable {
  134. return NO;
  135. }
  136. - (id)currentValueWithTarget:(id)object {
  137. if (!object_isClass(object)) {
  138. return [self getPotentiallyUnboxedValue:object];
  139. }
  140. return nil;
  141. }
  142. - (NSString *)previewWithTarget:(id)object {
  143. if (object_isClass(object)) {
  144. return self.details;
  145. }
  146. return [FLEXRuntimeUtility
  147. summaryForObject:[self currentValueWithTarget:object]
  148. ];
  149. }
  150. - (UIViewController *)viewerWithTarget:(id)object {
  151. NSAssert(!object_isClass(object), @"Unreachable state: viewing ivar on class object");
  152. id value = [self currentValueWithTarget:object];
  153. return [FLEXObjectExplorerFactory explorerViewControllerForObject:value];
  154. }
  155. - (UIViewController *)editorWithTarget:(id)object {
  156. NSAssert(!object_isClass(object), @"Unreachable state: editing ivar on class object");
  157. return [FLEXFieldEditorViewController target:object ivar:self];
  158. }
  159. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  160. if (object_isClass(object)) {
  161. return UITableViewCellAccessoryNone;
  162. }
  163. // Could use .isEditable here, but we use .tag for speed since it is cached
  164. if ([self getPotentiallyUnboxedValue:object]) {
  165. if (self.tag) {
  166. // Editable non-nil value, both
  167. return UITableViewCellAccessoryDetailDisclosureButton;
  168. } else {
  169. // Uneditable non-nil value, chevron only
  170. return UITableViewCellAccessoryDisclosureIndicator;
  171. }
  172. } else {
  173. if (self.tag) {
  174. // Editable nil value, just (i)
  175. return UITableViewCellAccessoryDetailButton;
  176. } else {
  177. // Non-editable nil value, neither
  178. return UITableViewCellAccessoryNone;
  179. }
  180. }
  181. }
  182. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  183. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  184. BOOL returnsObject = self.typeEncoding.typeIsObjectOrClass;
  185. id value = [self getValue:object];
  186. return @[
  187. @"Name", self.name ?: @"",
  188. @"Type", self.typeEncoding ?: @"",
  189. @"Declaration", self.description ?: @"",
  190. @"Value Preview", [self previewWithTarget:object],
  191. @"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
  192. @"Size", @(self.size).stringValue,
  193. @"Offset", @(self.offset).stringValue,
  194. @"objc_ivar", [FLEXUtility pointerToString:self.objc_ivar],
  195. ];
  196. }
  197. - (NSString *)contextualSubtitleWithTarget:(id)object {
  198. if (!object_isClass(object) && self.typeEncoding.typeIsObjectOrClass) {
  199. return [FLEXUtility addressOfObject:[self getValue:object]];
  200. }
  201. return nil;
  202. }
  203. @end
  204. #pragma mark FLEXMethod*
  205. @implementation FLEXMethodBase (UIKitHelpers)
  206. - (BOOL)isEditable {
  207. return NO;
  208. }
  209. - (BOOL)isCallable {
  210. return NO;
  211. }
  212. - (id)currentValueWithTarget:(id)object {
  213. // Methods can't be "edited" and have no "value"
  214. return nil;
  215. }
  216. - (NSString *)previewWithTarget:(id)object {
  217. return [self.selectorString stringByAppendingFormat:@" — %@", self.typeEncoding];
  218. }
  219. - (UIViewController *)viewerWithTarget:(id)object {
  220. // We disallow calling of FLEXMethodBase methods
  221. @throw NSInternalInconsistencyException;
  222. return nil;
  223. }
  224. - (UIViewController *)editorWithTarget:(id)object {
  225. // Methods cannot be edited
  226. @throw NSInternalInconsistencyException;
  227. return nil;
  228. }
  229. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  230. // We shouldn't be using any FLEXMethodBase objects for this
  231. @throw NSInternalInconsistencyException;
  232. return UITableViewCellAccessoryNone;
  233. }
  234. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  235. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  236. return @[
  237. @"Selector", self.name ?: @"",
  238. @"Type Encoding", self.typeEncoding ?: @"",
  239. @"Declaration", self.description ?: @"",
  240. ];
  241. }
  242. - (NSString *)contextualSubtitleWithTarget:(id)object {
  243. return nil;
  244. }
  245. @end
  246. @implementation FLEXMethod (UIKitHelpers)
  247. - (BOOL)isCallable {
  248. return self.signature != nil;
  249. }
  250. - (UIViewController *)viewerWithTarget:(id)object {
  251. object = self.isInstanceMethod ? object : (object_isClass(object) ? object : [object class]);
  252. return [FLEXMethodCallingViewController target:object method:self];
  253. }
  254. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  255. if (self.isInstanceMethod) {
  256. if (object_isClass(object)) {
  257. // Instance method from class, can't call
  258. return UITableViewCellAccessoryNone;
  259. } else {
  260. // Instance method from instance, can call
  261. return UITableViewCellAccessoryDisclosureIndicator;
  262. }
  263. } else {
  264. return UITableViewCellAccessoryDisclosureIndicator;
  265. }
  266. }
  267. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  268. return [[super copiableMetadataWithTarget:object] arrayByAddingObjectsFromArray:@[
  269. @"NSMethodSignature *", [FLEXUtility addressOfObject:self.signature],
  270. @"Signature String", self.signatureString ?: @"",
  271. @"Number of Arguments", @(self.numberOfArguments).stringValue,
  272. @"Return Type", @(self.returnType ?: ""),
  273. @"Return Size", @(self.returnSize).stringValue,
  274. @"objc_method", [FLEXUtility pointerToString:self.objc_method],
  275. ]];
  276. }
  277. @end
  278. #pragma mark FLEXProtocol
  279. @implementation FLEXProtocol (UIKitHelpers)
  280. - (BOOL)isEditable {
  281. return NO;
  282. }
  283. - (BOOL)isCallable {
  284. return NO;
  285. }
  286. - (id)currentValueWithTarget:(id)object {
  287. return nil;
  288. }
  289. - (NSString *)previewWithTarget:(id)object {
  290. return nil;
  291. }
  292. - (UIViewController *)viewerWithTarget:(id)object {
  293. return [FLEXObjectExplorerFactory explorerViewControllerForObject:self];
  294. }
  295. - (UIViewController *)editorWithTarget:(id)object {
  296. return nil;
  297. }
  298. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  299. return UITableViewCellAccessoryDisclosureIndicator;
  300. }
  301. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  302. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  303. NSArray<NSString *> *conformanceNames = [self.protocols valueForKeyPath:@"name"];
  304. NSString *conformances = [conformanceNames componentsJoinedByString:@"\n"];
  305. return @[
  306. @"Name", self.name ?: @"",
  307. @"Conformances", conformances,
  308. ];
  309. }
  310. - (NSString *)contextualSubtitleWithTarget:(id)object {
  311. return nil;
  312. }
  313. @end
  314. #pragma mark FLEXStaticMetadata
  315. @interface FLEXStaticMetadata () {
  316. @protected
  317. NSString *_name;
  318. }
  319. @property (nonatomic) FLEXTableViewCellReuseIdentifier reuse;
  320. @property (nonatomic) NSString *subtitle;
  321. @property (nonatomic) id metadata;
  322. @end
  323. @interface FLEXStaticMetadata_Class : FLEXStaticMetadata
  324. + (instancetype)withClass:(Class)cls;
  325. @end
  326. @implementation FLEXStaticMetadata
  327. @synthesize name = _name;
  328. @synthesize tag = _tag;
  329. + (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes {
  330. return [classes flex_mapped:^id(Class cls, NSUInteger idx) {
  331. return [FLEXStaticMetadata_Class withClass:cls];
  332. }];
  333. }
  334. + (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title string:(NSString *)string {
  335. return [[self alloc] initWithStyle:style title:title subtitle:string];
  336. }
  337. + (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title number:(NSNumber *)number {
  338. return [[self alloc] initWithStyle:style title:title subtitle:number.stringValue];
  339. }
  340. - (id)initWithStyle:(FLEXStaticMetadataRowStyle)style title:(NSString *)title subtitle:(NSString *)subtitle {
  341. self = [super init];
  342. if (self) {
  343. if (style == FLEXStaticMetadataRowStyleKeyValue) {
  344. _reuse = kFLEXKeyValueCell;
  345. } else {
  346. _reuse = kFLEXMultilineDetailCell;
  347. }
  348. _name = title;
  349. _subtitle = subtitle;
  350. }
  351. return self;
  352. }
  353. - (NSString *)description {
  354. return self.name;
  355. }
  356. - (NSString *)reuseIdentifierWithTarget:(id)object {
  357. return self.reuse;
  358. }
  359. - (BOOL)isEditable {
  360. return NO;
  361. }
  362. - (BOOL)isCallable {
  363. return NO;
  364. }
  365. - (id)currentValueWithTarget:(id)object {
  366. return nil;
  367. }
  368. - (NSString *)previewWithTarget:(id)object {
  369. return self.subtitle;
  370. }
  371. - (UIViewController *)viewerWithTarget:(id)object {
  372. return nil;
  373. }
  374. - (UIViewController *)editorWithTarget:(id)object {
  375. return nil;
  376. }
  377. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  378. return UITableViewCellAccessoryNone;
  379. }
  380. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  381. return @[self.name, self.subtitle];
  382. }
  383. - (NSString *)contextualSubtitleWithTarget:(id)object {
  384. return nil;
  385. }
  386. @end
  387. #pragma mark FLEXStaticMetadata_Class
  388. @implementation FLEXStaticMetadata_Class
  389. + (instancetype)withClass:(Class)cls {
  390. NSParameterAssert(cls);
  391. FLEXStaticMetadata_Class *metadata = [self new];
  392. metadata.metadata = cls;
  393. metadata->_name = NSStringFromClass(cls);
  394. metadata.reuse = kFLEXDefaultCell;
  395. return metadata;
  396. }
  397. - (id)initWithStyle:(FLEXStaticMetadataRowStyle)style title:(NSString *)title subtitle:(NSString *)subtitle {
  398. @throw NSInternalInconsistencyException;
  399. return nil;
  400. }
  401. - (UIViewController *)viewerWithTarget:(id)object {
  402. return [FLEXObjectExplorerFactory explorerViewControllerForObject:self.metadata];
  403. }
  404. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  405. return UITableViewCellAccessoryDisclosureIndicator;
  406. }
  407. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  408. return @[
  409. @"Class Name", self.name,
  410. @"Class", [FLEXUtility addressOfObject:self.metadata]
  411. ];
  412. }
  413. - (NSString *)contextualSubtitleWithTarget:(id)object {
  414. return [FLEXUtility addressOfObject:self.metadata];
  415. }
  416. @end