FLEXProperty.m 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  1. //
  2. // FLEXProperty.m
  3. // FLEX
  4. //
  5. // Derived from MirrorKit.
  6. // Created by Tanner on 6/30/15.
  7. // Copyright (c) 2020 FLEX Team. All rights reserved.
  8. //
  9. #import "FLEXProperty.h"
  10. #import "FLEXPropertyAttributes.h"
  11. #import "FLEXMethodBase.h"
  12. #import "FLEXRuntimeUtility.h"
  13. #include <dlfcn.h>
  14. @interface FLEXProperty () {
  15. NSString *_flex_description;
  16. }
  17. @property (nonatomic ) BOOL uniqueCheckFlag;
  18. @property (nonatomic, readonly) Class cls;
  19. @end
  20. @implementation FLEXProperty
  21. @synthesize multiple = _multiple;
  22. @synthesize imageName = _imageName;
  23. @synthesize imagePath = _imagePath;
  24. #pragma mark Initializers
  25. - (id)init {
  26. [NSException
  27. raise:NSInternalInconsistencyException
  28. format:@"Class instance should not be created with -init"
  29. ];
  30. return nil;
  31. }
  32. + (instancetype)property:(objc_property_t)property {
  33. return [[self alloc] initWithProperty:property onClass:nil];
  34. }
  35. + (instancetype)property:(objc_property_t)property onClass:(Class)cls {
  36. return [[self alloc] initWithProperty:property onClass:cls];
  37. }
  38. + (instancetype)named:(NSString *)name onClass:(Class)cls {
  39. objc_property_t _Nullable property = class_getProperty(cls, name.UTF8String);
  40. NSAssert(property, @"Cannot find property with name %@ on class %@", name, cls);
  41. return [self property:property onClass:cls];
  42. }
  43. + (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
  44. return [[self alloc] initWithName:name attributes:attributes];
  45. }
  46. - (id)initWithProperty:(objc_property_t)property onClass:(Class)cls {
  47. NSParameterAssert(property);
  48. self = [super init];
  49. if (self) {
  50. _objc_property = property;
  51. _attributes = [FLEXPropertyAttributes attributesForProperty:property];
  52. _name = @(property_getName(property) ?: "(nil)");
  53. _cls = cls;
  54. if (!_attributes) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property attributes"];
  55. if (!_name) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property name"];
  56. [self examine];
  57. }
  58. return self;
  59. }
  60. - (id)initWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
  61. NSParameterAssert(name); NSParameterAssert(attributes);
  62. self = [super init];
  63. if (self) {
  64. _attributes = attributes;
  65. _name = name;
  66. [self examine];
  67. }
  68. return self;
  69. }
  70. #pragma mark Private
  71. - (void)examine {
  72. if (self.attributes.typeEncoding.length) {
  73. _type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
  74. }
  75. // Return the given selector if the class responds to it
  76. Class cls = _cls;
  77. SEL (^selectorIfValid)() = ^SEL(SEL sel) {
  78. if (!sel || !cls) return nil;
  79. return [cls instancesRespondToSelector:sel] ? sel : nil;
  80. };
  81. SEL customGetter = self.attributes.customGetter;
  82. SEL customSetter = self.attributes.customSetter;
  83. SEL defaultGetter = NSSelectorFromString(self.name);
  84. SEL defaultSetter = NSSelectorFromString([NSString
  85. stringWithFormat:@"set%c%@:",
  86. (char)toupper([self.name characterAtIndex:0]),
  87. [self.name substringFromIndex:1]
  88. ]);
  89. // Check if the likely getters/setters exist
  90. SEL validGetter = selectorIfValid(customGetter) ?: selectorIfValid(defaultGetter);
  91. SEL validSetter = selectorIfValid(customSetter) ?: selectorIfValid(defaultSetter);
  92. _likelyGetterExists = validGetter != nil;
  93. _likelySetterExists = validSetter != nil;
  94. // Assign likely getters and setters to the valid one,
  95. // or the default, regardless of whether the default exists
  96. _likelyGetter = validGetter ?: defaultGetter;
  97. _likelySetter = validSetter ?: defaultSetter;
  98. _likelyGetterString = NSStringFromSelector(_likelyGetter);
  99. _likelySetterString = NSStringFromSelector(_likelySetter);
  100. _isClassProperty = _cls ? class_isMetaClass(_cls) : NO;
  101. }
  102. #pragma mark Overrides
  103. - (NSString *)description {
  104. if (!_flex_description) {
  105. NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.attributes.typeEncoding];
  106. _flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType];
  107. }
  108. return _flex_description;
  109. }
  110. - (NSString *)debugDescription {
  111. return [NSString stringWithFormat:@"<%@ name=%@, property=%p, attributes:\n\t%@\n>",
  112. NSStringFromClass(self.class), self.name, self.objc_property, self.attributes];
  113. }
  114. #pragma mark Public
  115. - (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount {
  116. if (self.objc_property) {
  117. return property_copyAttributeList(self.objc_property, attributesCount);
  118. } else {
  119. return [self.attributes copyAttributesList:attributesCount];
  120. }
  121. }
  122. - (void)replacePropertyOnClass:(Class)cls {
  123. class_replaceProperty(cls, self.name.UTF8String, self.attributes.list, (unsigned int)self.attributes.count);
  124. }
  125. - (void)computeSymbolInfo:(BOOL)forceBundle {
  126. Dl_info exeInfo;
  127. if (dladdr(_objc_property, &exeInfo)) {
  128. _imagePath = exeInfo.dli_fname ? @(exeInfo.dli_fname) : nil;
  129. }
  130. if ((!_multiple || !_uniqueCheckFlag) && _cls) {
  131. _multiple = _objc_property != class_getProperty(_cls, self.name.UTF8String);
  132. if (_multiple || forceBundle) {
  133. NSString *path = _imagePath.stringByDeletingLastPathComponent;
  134. _imageName = [NSBundle bundleWithPath:path].executablePath.lastPathComponent;
  135. }
  136. }
  137. }
  138. - (BOOL)multiple {
  139. [self computeSymbolInfo:NO];
  140. return _multiple;
  141. }
  142. - (NSString *)imagePath {
  143. [self computeSymbolInfo:YES];
  144. return _imagePath;
  145. }
  146. - (NSString *)imageName {
  147. [self computeSymbolInfo:YES];
  148. return _imageName;
  149. }
  150. - (NSString *)fullDescription {
  151. NSMutableArray<NSString *> *attributesStrings = [NSMutableArray new];
  152. FLEXPropertyAttributes *attributes = self.attributes;
  153. // Atomicity
  154. if (attributes.isNonatomic) {
  155. [attributesStrings addObject:@"nonatomic"];
  156. } else {
  157. [attributesStrings addObject:@"atomic"];
  158. }
  159. // Storage
  160. if (attributes.isRetained) {
  161. [attributesStrings addObject:@"strong"];
  162. } else if (attributes.isCopy) {
  163. [attributesStrings addObject:@"copy"];
  164. } else if (attributes.isWeak) {
  165. [attributesStrings addObject:@"weak"];
  166. } else {
  167. [attributesStrings addObject:@"assign"];
  168. }
  169. // Mutability
  170. if (attributes.isReadOnly) {
  171. [attributesStrings addObject:@"readonly"];
  172. } else {
  173. [attributesStrings addObject:@"readwrite"];
  174. }
  175. // Class or not
  176. if (self.isClassProperty) {
  177. [attributesStrings addObject:@"class"];
  178. }
  179. // Custom getter/setter
  180. SEL customGetter = attributes.customGetter;
  181. SEL customSetter = attributes.customSetter;
  182. if (customGetter) {
  183. [attributesStrings addObject:[NSString stringWithFormat:@"getter=%s", sel_getName(customGetter)]];
  184. }
  185. if (customSetter) {
  186. [attributesStrings addObject:[NSString stringWithFormat:@"setter=%s", sel_getName(customSetter)]];
  187. }
  188. NSString *attributesString = [attributesStrings componentsJoinedByString:@", "];
  189. return [NSString stringWithFormat:@"@property (%@) %@", attributesString, self.description];
  190. }
  191. - (id)getValue:(id)target {
  192. if (!target) return nil;
  193. // We don't care about checking dynamically whether the getter
  194. // _now_ exists on this object. If the getter doesn't exist
  195. // when this property is initialized, it will never call it.
  196. // Just re-create the property object if you need to call it.
  197. if (self.likelyGetterExists) {
  198. BOOL objectIsClass = object_isClass(target);
  199. BOOL instanceAndInstanceProperty = !objectIsClass && !self.isClassProperty;
  200. BOOL classAndClassProperty = objectIsClass && self.isClassProperty;
  201. if (instanceAndInstanceProperty || classAndClassProperty) {
  202. return [FLEXRuntimeUtility performSelector:self.likelyGetter onObject:target];
  203. }
  204. }
  205. return nil;
  206. }
  207. - (id)getPotentiallyUnboxedValue:(id)target {
  208. if (!target) return nil;
  209. return [FLEXRuntimeUtility
  210. potentiallyUnwrapBoxedPointer:[self getValue:target]
  211. type:self.attributes.typeEncoding.UTF8String
  212. ];
  213. }
  214. #pragma mark Suggested getters and setters
  215. - (FLEXMethodBase *)getterWithImplementation:(IMP)implementation {
  216. NSString *types = [NSString stringWithFormat:@"%@%s%s", self.attributes.typeEncoding, @encode(id), @encode(SEL)];
  217. NSString *name = [NSString stringWithFormat:@"%@", self.name];
  218. FLEXMethodBase *getter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
  219. return getter;
  220. }
  221. - (FLEXMethodBase *)setterWithImplementation:(IMP)implementation {
  222. NSString *types = [NSString stringWithFormat:@"%s%s%s%@", @encode(void), @encode(id), @encode(SEL), self.attributes.typeEncoding];
  223. NSString *name = [NSString stringWithFormat:@"set%@:", self.name.capitalizedString];
  224. FLEXMethodBase *setter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
  225. return setter;
  226. }
  227. @end