FLEXRuntime+UIKitHelpers.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. //
  2. // FLEXRuntime+UIKitHelpers.m
  3. // FLEX
  4. //
  5. // Created by Tanner Bennett on 12/16/19.
  6. // Copyright © 2020 FLEX Team. 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+FLEX.h"
  18. #import "NSString+FLEX.h"
  19. #if TARGET_OS_TV
  20. #import "fakes.h"
  21. #endif
  22. #define FLEXObjectExplorerDefaultsImpl \
  23. - (FLEXObjectExplorerDefaults *)defaults { \
  24. return self.tag; \
  25. } \
  26. \
  27. - (void)setDefaults:(FLEXObjectExplorerDefaults *)defaults { \
  28. self.tag = defaults; \
  29. }
  30. #pragma mark FLEXProperty
  31. @implementation FLEXProperty (UIKitHelpers)
  32. FLEXObjectExplorerDefaultsImpl
  33. /// Decide whether to use potentialTarget or [potentialTarget class] to get or set property
  34. - (id)appropriateTargetForPropertyType:(id)potentialTarget {
  35. if (!object_isClass(potentialTarget)) {
  36. if (self.isClassProperty) {
  37. return [potentialTarget class];
  38. } else {
  39. return potentialTarget;
  40. }
  41. } else {
  42. if (self.isClassProperty) {
  43. return potentialTarget;
  44. } else {
  45. // Instance property with a class object
  46. return nil;
  47. }
  48. }
  49. }
  50. - (BOOL)isEditable {
  51. if (self.attributes.isReadOnly) {
  52. return self.likelySetterExists;
  53. }
  54. const FLEXTypeEncoding *typeEncoding = self.attributes.typeEncoding.UTF8String;
  55. return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
  56. }
  57. - (BOOL)isCallable {
  58. return YES;
  59. }
  60. - (id)currentValueWithTarget:(id)object {
  61. return [self getPotentiallyUnboxedValue:
  62. [self appropriateTargetForPropertyType:object]
  63. ];
  64. }
  65. - (id)currentValueBeforeUnboxingWithTarget:(id)object {
  66. return [self getValue:
  67. [self appropriateTargetForPropertyType:object]
  68. ];
  69. }
  70. - (NSString *)previewWithTarget:(id)object {
  71. if (object_isClass(object) && !self.isClassProperty) {
  72. return self.attributes.fullDeclaration;
  73. } else if (self.defaults.wantsDynamicPreviews) {
  74. return [FLEXRuntimeUtility
  75. summaryForObject:[self currentValueWithTarget:object]
  76. ];
  77. }
  78. return nil;
  79. }
  80. - (UIViewController *)viewerWithTarget:(id)object {
  81. id value = [self currentValueWithTarget:object];
  82. return [FLEXObjectExplorerFactory explorerViewControllerForObject:value];
  83. }
  84. - (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
  85. id target = [self appropriateTargetForPropertyType:object];
  86. return [FLEXFieldEditorViewController target:target property:self commitHandler:^{
  87. [section reloadData:YES];
  88. }];
  89. }
  90. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  91. id targetForValueCheck = [self appropriateTargetForPropertyType:object];
  92. if (!targetForValueCheck) {
  93. // Instance property with a class object
  94. return UITableViewCellAccessoryNone;
  95. }
  96. // We use .tag to store the cached value of .isEditable that is
  97. // initialized by FLEXObjectExplorer in -reloadMetada
  98. if ([self getPotentiallyUnboxedValue:targetForValueCheck]) {
  99. if (self.defaults.isEditable) {
  100. // Editable non-nil value, both
  101. #if !TARGET_OS_TV
  102. return UITableViewCellAccessoryDetailDisclosureButton;
  103. #else
  104. return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailDisclosureButton;
  105. #endif
  106. } else {
  107. // Uneditable non-nil value, chevron only
  108. return UITableViewCellAccessoryDisclosureIndicator;
  109. }
  110. } else {
  111. if (self.defaults.isEditable) {
  112. // Editable nil value, just (i)
  113. #if !TARGET_OS_TV
  114. return UITableViewCellAccessoryDetailButton;
  115. #else
  116. return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailButton;
  117. #endif
  118. } else {
  119. // Non-editable nil value, neither
  120. return UITableViewCellAccessoryNone;
  121. }
  122. }
  123. }
  124. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  125. #if FLEX_AT_LEAST_IOS13_SDK
  126. - (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
  127. Class propertyClass = self.attributes.typeEncoding.flex_typeClass;
  128. // "Explore PropertyClass" for properties with a concrete class name
  129. if (propertyClass) {
  130. NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(propertyClass)];
  131. return @[[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
  132. UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:propertyClass];
  133. [sender.navigationController pushViewController:explorer animated:YES];
  134. }]];
  135. }
  136. return nil;
  137. }
  138. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  139. BOOL returnsObject = self.attributes.typeEncoding.flex_typeIsObjectOrClass;
  140. BOOL targetNotNil = [self appropriateTargetForPropertyType:object] != nil;
  141. NSMutableArray *items = [NSMutableArray arrayWithArray:@[
  142. @"Name", self.name ?: @"",
  143. @"Type", self.attributes.typeEncoding ?: @"",
  144. @"Declaration", self.fullDescription ?: @"",
  145. ]];
  146. if (targetNotNil) {
  147. id value = [self currentValueBeforeUnboxingWithTarget:object];
  148. [items addObjectsFromArray:@[
  149. @"Value Preview", [self previewWithTarget:object] ?: @"",
  150. @"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
  151. ]];
  152. }
  153. [items addObjectsFromArray:@[
  154. @"Getter", NSStringFromSelector(self.likelyGetter) ?: @"",
  155. @"Setter", self.likelySetterExists ? NSStringFromSelector(self.likelySetter) : @"",
  156. @"Image Name", self.imageName ?: @"",
  157. @"Attributes", self.attributes.string ?: @"",
  158. @"objc_property", [FLEXUtility pointerToString:self.objc_property],
  159. @"objc_property_attribute_t", [FLEXUtility pointerToString:self.attributes.list],
  160. ]];
  161. return items;
  162. }
  163. - (NSString *)contextualSubtitleWithTarget:(id)object {
  164. id target = [self appropriateTargetForPropertyType:object];
  165. if (target && self.attributes.typeEncoding.flex_typeIsObjectOrClass) {
  166. return [FLEXUtility addressOfObject:[self currentValueBeforeUnboxingWithTarget:target]];
  167. }
  168. return nil;
  169. }
  170. #endif
  171. @end
  172. #pragma mark FLEXIvar
  173. @implementation FLEXIvar (UIKitHelpers)
  174. FLEXObjectExplorerDefaultsImpl
  175. - (BOOL)isEditable {
  176. const FLEXTypeEncoding *typeEncoding = self.typeEncoding.UTF8String;
  177. return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
  178. }
  179. - (BOOL)isCallable {
  180. return NO;
  181. }
  182. - (id)currentValueWithTarget:(id)object {
  183. if (!object_isClass(object)) {
  184. return [self getPotentiallyUnboxedValue:object];
  185. }
  186. return nil;
  187. }
  188. - (NSString *)previewWithTarget:(id)object {
  189. if (object_isClass(object)) {
  190. return self.details;
  191. } else if (self.defaults.wantsDynamicPreviews) {
  192. return [FLEXRuntimeUtility
  193. summaryForObject:[self currentValueWithTarget:object]
  194. ];
  195. }
  196. return nil;
  197. }
  198. - (UIViewController *)viewerWithTarget:(id)object {
  199. NSAssert(!object_isClass(object), @"Unreachable state: viewing ivar on class object");
  200. id value = [self currentValueWithTarget:object];
  201. return [FLEXObjectExplorerFactory explorerViewControllerForObject:value];
  202. }
  203. - (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
  204. NSAssert(!object_isClass(object), @"Unreachable state: editing ivar on class object");
  205. return [FLEXFieldEditorViewController target:object ivar:self commitHandler:^{
  206. [section reloadData:YES];
  207. }];
  208. }
  209. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  210. if (object_isClass(object)) {
  211. return UITableViewCellAccessoryNone;
  212. }
  213. // Could use .isEditable here, but we use .tag for speed since it is cached
  214. if ([self getPotentiallyUnboxedValue:object]) {
  215. if (self.defaults.isEditable) {
  216. // Editable non-nil value, both
  217. #if !TARGET_OS_TV
  218. return UITableViewCellAccessoryDetailDisclosureButton;
  219. #else
  220. return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailDisclosureButton;
  221. #endif
  222. } else {
  223. // Uneditable non-nil value, chevron only
  224. return UITableViewCellAccessoryDisclosureIndicator;
  225. }
  226. } else {
  227. if (self.defaults.isEditable) {
  228. // Editable nil value, just (i)
  229. #if !TARGET_OS_TV
  230. return UITableViewCellAccessoryDetailButton;
  231. #else
  232. return (UITableViewCellAccessoryType)TVTableViewCellAccessoryDetailButton;
  233. #endif
  234. } else {
  235. // Non-editable nil value, neither
  236. return UITableViewCellAccessoryNone;
  237. }
  238. }
  239. }
  240. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  241. #if FLEX_AT_LEAST_IOS13_SDK
  242. - (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
  243. Class ivarClass = self.typeEncoding.flex_typeClass;
  244. // "Explore PropertyClass" for properties with a concrete class name
  245. if (ivarClass) {
  246. NSString *title = [NSString stringWithFormat:@"Explore %@", NSStringFromClass(ivarClass)];
  247. return @[[UIAction actionWithTitle:title image:nil identifier:nil handler:^(UIAction *action) {
  248. UIViewController *explorer = [FLEXObjectExplorerFactory explorerViewControllerForObject:ivarClass];
  249. [sender.navigationController pushViewController:explorer animated:YES];
  250. }]];
  251. }
  252. return nil;
  253. }
  254. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  255. BOOL isInstance = !object_isClass(object);
  256. BOOL returnsObject = self.typeEncoding.flex_typeIsObjectOrClass;
  257. id value = isInstance ? [self getValue:object] : nil;
  258. NSMutableArray *items = [NSMutableArray arrayWithArray:@[
  259. @"Name", self.name ?: @"",
  260. @"Type", self.typeEncoding ?: @"",
  261. @"Declaration", self.description ?: @"",
  262. ]];
  263. if (isInstance) {
  264. [items addObjectsFromArray:@[
  265. @"Value Preview", isInstance ? [self previewWithTarget:object] : @"",
  266. @"Value Address", returnsObject ? [FLEXUtility addressOfObject:value] : @"",
  267. ]];
  268. }
  269. [items addObjectsFromArray:@[
  270. @"Size", @(self.size).stringValue,
  271. @"Offset", @(self.offset).stringValue,
  272. @"objc_ivar", [FLEXUtility pointerToString:self.objc_ivar],
  273. ]];
  274. return items;
  275. }
  276. - (NSString *)contextualSubtitleWithTarget:(id)object {
  277. if (!object_isClass(object) && self.typeEncoding.flex_typeIsObjectOrClass) {
  278. return [FLEXUtility addressOfObject:[self getValue:object]];
  279. }
  280. return nil;
  281. }
  282. #endif
  283. @end
  284. #pragma mark FLEXMethod
  285. @implementation FLEXMethodBase (UIKitHelpers)
  286. FLEXObjectExplorerDefaultsImpl
  287. - (BOOL)isEditable {
  288. return NO;
  289. }
  290. - (BOOL)isCallable {
  291. return NO;
  292. }
  293. - (id)currentValueWithTarget:(id)object {
  294. // Methods can't be "edited" and have no "value"
  295. return nil;
  296. }
  297. - (NSString *)previewWithTarget:(id)object {
  298. return [self.selectorString stringByAppendingFormat:@" — %@", self.typeEncoding];
  299. }
  300. - (UIViewController *)viewerWithTarget:(id)object {
  301. // We disallow calling of FLEXMethodBase methods
  302. @throw NSInternalInconsistencyException;
  303. return nil;
  304. }
  305. - (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
  306. // Methods cannot be edited
  307. @throw NSInternalInconsistencyException;
  308. return nil;
  309. }
  310. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  311. // We shouldn't be using any FLEXMethodBase objects for this
  312. @throw NSInternalInconsistencyException;
  313. return UITableViewCellAccessoryNone;
  314. }
  315. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  316. #if FLEX_AT_LEAST_IOS13_SDK
  317. - (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
  318. return nil;
  319. }
  320. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  321. return @[
  322. @"Selector", self.name ?: @"",
  323. @"Type Encoding", self.typeEncoding ?: @"",
  324. @"Declaration", self.description ?: @"",
  325. ];
  326. }
  327. - (NSString *)contextualSubtitleWithTarget:(id)object {
  328. return nil;
  329. }
  330. #endif
  331. @end
  332. @implementation FLEXMethod (UIKitHelpers)
  333. - (BOOL)isCallable {
  334. return self.signature != nil;
  335. }
  336. - (UIViewController *)viewerWithTarget:(id)object {
  337. object = self.isInstanceMethod ? object : (object_isClass(object) ? object : [object class]);
  338. return [FLEXMethodCallingViewController target:object method:self];
  339. }
  340. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  341. if (self.isInstanceMethod) {
  342. if (object_isClass(object)) {
  343. // Instance method from class, can't call
  344. return UITableViewCellAccessoryNone;
  345. } else {
  346. // Instance method from instance, can call
  347. return UITableViewCellAccessoryDisclosureIndicator;
  348. }
  349. } else {
  350. return UITableViewCellAccessoryDisclosureIndicator;
  351. }
  352. }
  353. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  354. return [[super copiableMetadataWithTarget:object] arrayByAddingObjectsFromArray:@[
  355. @"NSMethodSignature *", [FLEXUtility addressOfObject:self.signature],
  356. @"Signature String", self.signatureString ?: @"",
  357. @"Number of Arguments", @(self.numberOfArguments).stringValue,
  358. @"Return Type", @(self.returnType ?: ""),
  359. @"Return Size", @(self.returnSize).stringValue,
  360. @"objc_method", [FLEXUtility pointerToString:self.objc_method],
  361. ]];
  362. }
  363. @end
  364. #pragma mark FLEXProtocol
  365. @implementation FLEXProtocol (UIKitHelpers)
  366. FLEXObjectExplorerDefaultsImpl
  367. - (BOOL)isEditable {
  368. return NO;
  369. }
  370. - (BOOL)isCallable {
  371. return NO;
  372. }
  373. - (id)currentValueWithTarget:(id)object {
  374. return nil;
  375. }
  376. - (NSString *)previewWithTarget:(id)object {
  377. return nil;
  378. }
  379. - (UIViewController *)viewerWithTarget:(id)object {
  380. return [FLEXObjectExplorerFactory explorerViewControllerForObject:self];
  381. }
  382. - (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
  383. // Protocols cannot be edited
  384. @throw NSInternalInconsistencyException;
  385. return nil;
  386. }
  387. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  388. return UITableViewCellAccessoryDisclosureIndicator;
  389. }
  390. - (NSString *)reuseIdentifierWithTarget:(id)object { return nil; }
  391. #if FLEX_AT_LEAST_IOS13_SDK
  392. - (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
  393. return nil;
  394. }
  395. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  396. NSArray<NSString *> *conformanceNames = [self.protocols valueForKeyPath:@"name"];
  397. NSString *conformances = [conformanceNames componentsJoinedByString:@"\n"];
  398. return @[
  399. @"Name", self.name ?: @"",
  400. @"Conformances", conformances ?: @"",
  401. ];
  402. }
  403. - (NSString *)contextualSubtitleWithTarget:(id)object {
  404. return nil;
  405. }
  406. #endif
  407. @end
  408. #pragma mark FLEXStaticMetadata
  409. @interface FLEXStaticMetadata () {
  410. @protected
  411. NSString *_name;
  412. }
  413. @property (nonatomic) FLEXTableViewCellReuseIdentifier reuse;
  414. @property (nonatomic) NSString *subtitle;
  415. @property (nonatomic) id metadata;
  416. @end
  417. @interface FLEXStaticMetadata_Class : FLEXStaticMetadata
  418. + (instancetype)withClass:(Class)cls;
  419. @end
  420. @implementation FLEXStaticMetadata
  421. @synthesize name = _name;
  422. @synthesize tag = _tag;
  423. FLEXObjectExplorerDefaultsImpl
  424. + (NSArray<FLEXStaticMetadata *> *)classHierarchy:(NSArray<Class> *)classes {
  425. return [classes flex_mapped:^id(Class cls, NSUInteger idx) {
  426. return [FLEXStaticMetadata_Class withClass:cls];
  427. }];
  428. }
  429. + (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title string:(NSString *)string {
  430. return [[self alloc] initWithStyle:style title:title subtitle:string];
  431. }
  432. + (instancetype)style:(FLEXStaticMetadataRowStyle)style title:(NSString *)title number:(NSNumber *)number {
  433. return [[self alloc] initWithStyle:style title:title subtitle:number.stringValue];
  434. }
  435. - (id)initWithStyle:(FLEXStaticMetadataRowStyle)style title:(NSString *)title subtitle:(NSString *)subtitle {
  436. self = [super init];
  437. if (self) {
  438. if (style == FLEXStaticMetadataRowStyleKeyValue) {
  439. _reuse = kFLEXKeyValueCell;
  440. } else {
  441. _reuse = kFLEXMultilineDetailCell;
  442. }
  443. _name = title;
  444. _subtitle = subtitle;
  445. }
  446. return self;
  447. }
  448. - (NSString *)description {
  449. return self.name;
  450. }
  451. - (NSString *)reuseIdentifierWithTarget:(id)object {
  452. return self.reuse;
  453. }
  454. - (BOOL)isEditable {
  455. return NO;
  456. }
  457. - (BOOL)isCallable {
  458. return NO;
  459. }
  460. - (id)currentValueWithTarget:(id)object {
  461. return nil;
  462. }
  463. - (NSString *)previewWithTarget:(id)object {
  464. return self.subtitle;
  465. }
  466. - (UIViewController *)viewerWithTarget:(id)object {
  467. return nil;
  468. }
  469. - (UIViewController *)editorWithTarget:(id)object section:(FLEXTableViewSection *)section {
  470. // Static metadata cannot be edited
  471. @throw NSInternalInconsistencyException;
  472. return nil;
  473. }
  474. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  475. return UITableViewCellAccessoryNone;
  476. }
  477. #if FLEX_AT_LEAST_IOS13_SDK
  478. - (NSArray<UIAction *> *)additionalActionsWithTarget:(id)object sender:(UIViewController *)sender __IOS_AVAILABLE(13.0) {
  479. return nil;
  480. }
  481. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  482. return @[self.name, self.subtitle];
  483. }
  484. - (NSString *)contextualSubtitleWithTarget:(id)object {
  485. return nil;
  486. }
  487. #endif
  488. @end
  489. #pragma mark FLEXStaticMetadata_Class
  490. @implementation FLEXStaticMetadata_Class
  491. + (instancetype)withClass:(Class)cls {
  492. NSParameterAssert(cls);
  493. FLEXStaticMetadata_Class *metadata = [self new];
  494. metadata.metadata = cls;
  495. metadata->_name = NSStringFromClass(cls);
  496. metadata.reuse = kFLEXDefaultCell;
  497. return metadata;
  498. }
  499. - (id)initWithStyle:(FLEXStaticMetadataRowStyle)style title:(NSString *)title subtitle:(NSString *)subtitle {
  500. @throw NSInternalInconsistencyException;
  501. return nil;
  502. }
  503. - (UIViewController *)viewerWithTarget:(id)object {
  504. return [FLEXObjectExplorerFactory explorerViewControllerForObject:self.metadata];
  505. }
  506. - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
  507. return UITableViewCellAccessoryDisclosureIndicator;
  508. }
  509. - (NSArray<NSString *> *)copiableMetadataWithTarget:(id)object {
  510. return @[
  511. @"Class Name", self.name,
  512. @"Class", [FLEXUtility addressOfObject:self.metadata]
  513. ];
  514. }
  515. - (NSString *)contextualSubtitleWithTarget:(id)object {
  516. return [FLEXUtility addressOfObject:self.metadata];
  517. }
  518. @end