FLEXRuntimeUtility.m 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607
  1. //
  2. // FLEXRuntimeUtility.m
  3. // Flipboard
  4. //
  5. // Created by Ryan Olson on 6/8/14.
  6. // Copyright (c) 2014 Flipboard. All rights reserved.
  7. //
  8. #import "FLEXRuntimeUtility.h"
  9. // See https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
  10. static NSString *const kFLEXUtilityAttributeTypeEncoding = @"T";
  11. static NSString *const kFLEXUtilityAttributeBackingIvar = @"V";
  12. static NSString *const kFLEXUtilityAttributeReadOnly = @"R";
  13. static NSString *const kFLEXUtilityAttributeCopy = @"C";
  14. static NSString *const kFLEXUtilityAttributeRetain = @"&";
  15. static NSString *const kFLEXUtilityAttributeNonAtomic = @"N";
  16. static NSString *const kFLEXUtilityAttributeCustomGetter = @"G";
  17. static NSString *const kFLEXUtilityAttributeCustomSetter = @"S";
  18. static NSString *const kFLEXUtilityAttributeDynamic = @"D";
  19. static NSString *const kFLEXUtilityAttributeWeak = @"W";
  20. static NSString *const kFLEXUtilityAttributeGarbageCollectable = @"P";
  21. static NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding = @"t";
  22. static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
  23. typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
  24. FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
  25. FLEXRuntimeUtilityErrorCodeInvocationFailed = 1
  26. };
  27. // Arguments 0 and 1 are self and _cmd always
  28. const unsigned int kFLEXNumberOfImplicitArgs = 2;
  29. @implementation FLEXRuntimeUtility
  30. #pragma mark - Property Helpers (Public)
  31. + (NSString *)prettyNameForProperty:(objc_property_t)property
  32. {
  33. NSString *name = @(property_getName(property));
  34. NSString *encoding = [self typeEncodingForProperty:property];
  35. NSString *readableType = [self readableTypeForEncoding:encoding];
  36. return [self appendName:name toType:readableType];
  37. }
  38. + (NSString *)typeEncodingForProperty:(objc_property_t)property
  39. {
  40. NSDictionary *attributesDictionary = [self attributesDictionaryForProperty:property];
  41. return [attributesDictionary objectForKey:kFLEXUtilityAttributeTypeEncoding];
  42. }
  43. + (BOOL)isReadonlyProperty:(objc_property_t)property
  44. {
  45. return [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeReadOnly] != nil;
  46. }
  47. + (SEL)setterSelectorForProperty:(objc_property_t)property
  48. {
  49. SEL setterSelector = NULL;
  50. NSString *setterSelectorString = [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeCustomSetter];
  51. if (!setterSelectorString) {
  52. NSString *propertyName = @(property_getName(property));
  53. setterSelectorString = [NSString stringWithFormat:@"set%@%@:", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]];
  54. }
  55. if (setterSelectorString) {
  56. setterSelector = NSSelectorFromString(setterSelectorString);
  57. }
  58. return setterSelector;
  59. }
  60. + (NSString *)fullDescriptionForProperty:(objc_property_t)property
  61. {
  62. NSDictionary *attributesDictionary = [self attributesDictionaryForProperty:property];
  63. NSMutableArray *attributesStrings = [NSMutableArray array];
  64. // Atomicity
  65. if ([attributesDictionary objectForKey:kFLEXUtilityAttributeNonAtomic]) {
  66. [attributesStrings addObject:@"nonatomic"];
  67. } else {
  68. [attributesStrings addObject:@"atomic"];
  69. }
  70. // Storage
  71. if ([attributesDictionary objectForKey:kFLEXUtilityAttributeRetain]) {
  72. [attributesStrings addObject:@"strong"];
  73. } else if ([attributesDictionary objectForKey:kFLEXUtilityAttributeCopy]) {
  74. [attributesStrings addObject:@"copy"];
  75. } else if ([attributesDictionary objectForKey:kFLEXUtilityAttributeWeak]) {
  76. [attributesStrings addObject:@"weak"];
  77. } else {
  78. [attributesStrings addObject:@"assign"];
  79. }
  80. // Mutability
  81. if ([attributesDictionary objectForKey:kFLEXUtilityAttributeReadOnly]) {
  82. [attributesStrings addObject:@"readonly"];
  83. } else {
  84. [attributesStrings addObject:@"readwrite"];
  85. }
  86. // Custom getter/setter
  87. NSString *customGetter = [attributesDictionary objectForKey:kFLEXUtilityAttributeCustomGetter];
  88. NSString *customSetter = [attributesDictionary objectForKey:kFLEXUtilityAttributeCustomSetter];
  89. if (customGetter) {
  90. [attributesStrings addObject:[NSString stringWithFormat:@"getter=%@", customGetter]];
  91. }
  92. if (customSetter) {
  93. [attributesStrings addObject:[NSString stringWithFormat:@"setter=%@", customSetter]];
  94. }
  95. NSString *attributesString = [attributesStrings componentsJoinedByString:@", "];
  96. NSString *shortName = [self prettyNameForProperty:property];
  97. return [NSString stringWithFormat:@"@property (%@) %@", attributesString, shortName];
  98. }
  99. + (id)valueForProperty:(objc_property_t)property onObject:(id)object
  100. {
  101. NSString *customGetterString = nil;
  102. char *customGetterName = property_copyAttributeValue(property, "G");
  103. if (customGetterName) {
  104. customGetterString = @(customGetterName);
  105. free(customGetterName);
  106. }
  107. SEL getterSelector;
  108. if ([customGetterString length] > 0) {
  109. getterSelector = NSSelectorFromString(customGetterString);
  110. } else {
  111. NSString *propertyName = @(property_getName(property));
  112. getterSelector = NSSelectorFromString(propertyName);
  113. }
  114. return [self performSelector:getterSelector onObject:object withArguments:nil error:NULL];
  115. }
  116. + (NSString *)descriptionForIvarOrPropertyValue:(id)value
  117. {
  118. NSString *description = nil;
  119. // Special case BOOL for better readability.
  120. if ([value isKindOfClass:[NSValue class]]) {
  121. const char *type = [value objCType];
  122. if (strcmp(type, @encode(BOOL)) == 0) {
  123. BOOL boolValue = NO;
  124. [value getValue:&boolValue];
  125. description = boolValue ? @"YES" : @"NO";
  126. }
  127. }
  128. if (!description) {
  129. // Single line display - replace newlines and tabs with spaces.
  130. description = [[value description] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
  131. description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
  132. }
  133. if (!description) {
  134. description = @"nil";
  135. }
  136. return description;
  137. }
  138. + (void)addFramePropertyToUIViewIfNeeded
  139. {
  140. const char *framePropertyName = "frame";
  141. objc_property_t frameProperty = class_getProperty([UIView class], framePropertyName);
  142. if (!frameProperty) {
  143. objc_property_attribute_t typeAttribute;
  144. typeAttribute.name = [kFLEXUtilityAttributeTypeEncoding UTF8String];
  145. typeAttribute.value = @encode(CGRect);
  146. objc_property_attribute_t nonatomicAttribute;
  147. nonatomicAttribute.name = [kFLEXUtilityAttributeNonAtomic UTF8String];
  148. nonatomicAttribute.value = "";
  149. const unsigned int kAttributeCount = 2;
  150. objc_property_attribute_t attributes[kAttributeCount] = {typeAttribute, nonatomicAttribute};
  151. class_addProperty([UIView class], framePropertyName, attributes, kAttributeCount);
  152. }
  153. }
  154. #pragma mark - Ivar Helpers (Public)
  155. + (NSString *)prettyNameForIvar:(Ivar)ivar
  156. {
  157. NSString *name = @(ivar_getName(ivar));
  158. NSString *encoding = @(ivar_getTypeEncoding(ivar));
  159. NSString *readableType = [self readableTypeForEncoding:encoding];
  160. return [self appendName:name toType:readableType];
  161. }
  162. + (id)valueForIvar:(Ivar)ivar onObject:(id)object
  163. {
  164. id value = nil;
  165. const char *type = ivar_getTypeEncoding(ivar);
  166. if (type[0] == @encode(id)[0] || type[0] == @encode(Class)[0]) {
  167. value = object_getIvar(object, ivar);
  168. } else {
  169. ptrdiff_t offset = ivar_getOffset(ivar);
  170. void *pointer = (__bridge void *)object + offset;
  171. value = [self valueForPrimitivePointer:pointer objcType:type];
  172. }
  173. return value;
  174. }
  175. + (void)setIvar:(Ivar)ivar onObject:(id)object withInputString:(NSString *)inputString
  176. {
  177. const char *typeEncodingCString = ivar_getTypeEncoding(ivar);
  178. if (typeEncodingCString[0] == '@') {
  179. // Object - use NSJSONSerialization
  180. id ivarValue = [self objectValueFromEditableString:inputString];
  181. object_setIvar(object, ivar, ivarValue);
  182. } else {
  183. // Primitive - try to parse a number or struct from the string based on the type encoding
  184. NSUInteger bufferSize = 0;
  185. NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
  186. void *buffer = malloc(bufferSize);
  187. [self getPrimitiveValue:buffer withObjCType:typeEncodingCString fromInputString:inputString];
  188. ptrdiff_t offset = ivar_getOffset(ivar);
  189. void *pointer = (__bridge void *)object + offset;
  190. memcpy(pointer, buffer, bufferSize);
  191. }
  192. }
  193. #pragma mark - Method Helpers (Public)
  194. + (NSString *)prettyNameForMethod:(Method)method isClassMethod:(BOOL)isClassMethod
  195. {
  196. NSString *selectorName = NSStringFromSelector(method_getName(method));
  197. NSString *methodTypeString = isClassMethod ? @"+" : @"-";
  198. char *returnType = method_copyReturnType(method);
  199. NSString *readableReturnType = [self readableTypeForEncoding:@(returnType)];
  200. free(returnType);
  201. NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType];
  202. NSArray *components = [self prettyArgumentComponentsForMethod:method];
  203. if ([components count] > 0) {
  204. prettyName = [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]];
  205. } else {
  206. prettyName = [prettyName stringByAppendingString:selectorName];
  207. }
  208. return prettyName;
  209. }
  210. + (NSArray *)prettyArgumentComponentsForMethod:(Method)method
  211. {
  212. NSMutableArray *components = [NSMutableArray array];
  213. NSString *selectorName = NSStringFromSelector(method_getName(method));
  214. NSArray *selectorComponents = [selectorName componentsSeparatedByString:@":"];
  215. unsigned int numberOfArguments = method_getNumberOfArguments(method);
  216. for (unsigned int argIndex = kFLEXNumberOfImplicitArgs; argIndex < numberOfArguments; argIndex++) {
  217. char *argType = method_copyArgumentType(method, argIndex);
  218. NSString *readableArgType = [self readableTypeForEncoding:@(argType)];
  219. free(argType);
  220. NSString *prettyComponent = [NSString stringWithFormat:@"%@:(%@) ", [selectorComponents objectAtIndex:argIndex - kFLEXNumberOfImplicitArgs], readableArgType];
  221. [components addObject:prettyComponent];
  222. }
  223. return components;
  224. }
  225. #pragma mark - Method Calling/Field Editing (Public)
  226. + (id)performSelector:(SEL)selector onObject:(id)object withArguments:(NSArray *)arguments error:(NSError * __autoreleasing *)error
  227. {
  228. // Bail if the object won't respond to this selector.
  229. if (![object respondsToSelector:selector]) {
  230. if (error) {
  231. NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@ does not respond to the selector %@", object, NSStringFromSelector(selector)]};
  232. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector userInfo:userInfo];
  233. }
  234. return nil;
  235. }
  236. // Build the invocation
  237. NSMethodSignature *methodSignature = [object methodSignatureForSelector:selector];
  238. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
  239. [invocation setSelector:selector];
  240. [invocation setTarget:object];
  241. [invocation retainArguments];
  242. // Always self and _cmd
  243. NSUInteger numberOfArguments = [methodSignature numberOfArguments];
  244. for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
  245. NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
  246. NSString *argumentString = [arguments count] > argumentsArrayIndex ? [arguments objectAtIndex:argumentsArrayIndex] : nil;
  247. const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
  248. if (typeEncodingCString[0] == @encode(id)[0] || typeEncodingCString[0] == @encode(Class)[0]) {
  249. // Object
  250. id argumentObject = [self objectValueFromEditableString:argumentString];
  251. [invocation setArgument:&argumentObject atIndex:argumentIndex];
  252. } else if (strcmp(typeEncodingCString, @encode(const char *)) == 0 || strcmp(typeEncodingCString, @encode(char *)) == 0) {
  253. // C string
  254. const char *argumentCString = [argumentString UTF8String];
  255. [invocation setArgument:&argumentCString atIndex:argumentIndex];
  256. } else {
  257. // Primitive number or struct
  258. NSUInteger bufferSize = 0;
  259. NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
  260. void *buffer = malloc(bufferSize);
  261. [self getPrimitiveValue:buffer withObjCType:typeEncodingCString fromInputString:argumentString];
  262. [invocation setArgument:buffer atIndex:argumentIndex];
  263. free(buffer);
  264. }
  265. }
  266. // Try to invoke the invocation but guard against an exception being thrown.
  267. BOOL successfullyInvoked = NO;
  268. @try {
  269. // Some methods are not fit to be called...
  270. // Looking at you -[UIResponder(UITextInputAdditions) _caretRect]
  271. [invocation invoke];
  272. successfullyInvoked = YES;
  273. } @catch (NSException *exception) {
  274. // Bummer...
  275. if (error) {
  276. NSDictionary *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Exception thrown while performing selector %@ on object %@", object, NSStringFromSelector(selector)]};
  277. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeInvocationFailed userInfo:userInfo];
  278. }
  279. }
  280. // Retreive the return value and box if necessary.
  281. id returnObject = nil;
  282. if (successfullyInvoked) {
  283. const char *returnType = [methodSignature methodReturnType];
  284. if (returnType[0] == @encode(id)[0] || returnType[0] == @encode(Class)[0]) {
  285. __unsafe_unretained id objectReturnedFromMethod = nil;
  286. [invocation getReturnValue:&objectReturnedFromMethod];
  287. returnObject = objectReturnedFromMethod;
  288. } else if (returnType[0] != @encode(void)[0]) {
  289. void *returnValue = malloc([methodSignature methodReturnLength]);
  290. if (returnValue) {
  291. [invocation getReturnValue:returnValue];
  292. returnObject = [self valueForPrimitivePointer:returnValue objcType:returnType];
  293. free(returnValue);
  294. }
  295. }
  296. }
  297. return returnObject;
  298. }
  299. + (NSString *)editiableDescriptionForObject:(id)object
  300. {
  301. NSString *editableDescription = nil;
  302. if ([object isKindOfClass:[NSValue class]]) {
  303. NSValue *value = (NSValue *)object;
  304. const char *typeEncoding = [value objCType];
  305. if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
  306. editableDescription = NSStringFromCGRect([value CGRectValue]);
  307. } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
  308. editableDescription = NSStringFromCGSize([value CGSizeValue]);
  309. } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
  310. editableDescription = NSStringFromCGPoint([value CGPointValue]);
  311. } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
  312. editableDescription = NSStringFromCGAffineTransform([value CGAffineTransformValue]);
  313. } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
  314. editableDescription = NSStringFromRange([value rangeValue]);
  315. } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
  316. editableDescription = NSStringFromUIEdgeInsets([value UIEdgeInsetsValue]);
  317. } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
  318. editableDescription = NSStringFromUIOffset([value UIOffsetValue]);
  319. }
  320. }
  321. if (object && !editableDescription) {
  322. // This is a hack to use JSON serialzation for our editable objects.
  323. // NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary.
  324. // We always wrap the object inside an array and then strip the outter square braces off the final string.
  325. NSArray *wrappedObject = @[object];
  326. if ([NSJSONSerialization isValidJSONObject:wrappedObject]) {
  327. NSString *wrappedDescription = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:wrappedObject options:0 error:NULL] encoding:NSUTF8StringEncoding];
  328. editableDescription = [wrappedDescription substringWithRange:NSMakeRange(1, [wrappedDescription length] - 2)];
  329. }
  330. }
  331. return editableDescription;
  332. }
  333. + (id)objectValueFromEditableString:(NSString *)string
  334. {
  335. id value = nil;
  336. // nil for empty string/whitespace
  337. if ([[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0) {
  338. value = [NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:NULL];
  339. }
  340. return value;
  341. }
  342. #pragma mark - Internal Helpers
  343. + (NSDictionary *)attributesDictionaryForProperty:(objc_property_t)property
  344. {
  345. NSString *attributes = @(property_getAttributes(property));
  346. // Thanks to MAObjcRuntime for inspiration here.
  347. NSArray *attributePairs = [attributes componentsSeparatedByString:@","];
  348. NSMutableDictionary *attributesDictionary = [NSMutableDictionary dictionaryWithCapacity:[attributePairs count]];
  349. for (NSString *attributePair in attributePairs) {
  350. [attributesDictionary setObject:[attributePair substringFromIndex:1] forKey:[attributePair substringToIndex:1]];
  351. }
  352. return attributesDictionary;
  353. }
  354. + (NSString *)appendName:(NSString *)name toType:(NSString *)type
  355. {
  356. NSString *combined = nil;
  357. if ([type characterAtIndex:[type length] - 1] == '*') {
  358. combined = [type stringByAppendingString:name];
  359. } else {
  360. combined = [type stringByAppendingFormat:@" %@", name];
  361. }
  362. return combined;
  363. }
  364. + (NSString *)readableTypeForEncoding:(NSString *)encodingString
  365. {
  366. // See https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
  367. // class-dump has a much nicer and much more complete implementation for this task, but it is distributed under GPLv2 :/
  368. // See https://github.com/nygard/class-dump/blob/master/Source/CDType.m
  369. // Warning: this method uses multiple middle returns and macros to cut down on boilerplate.
  370. // The use of macros here was inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  371. const char *encodingCString = [encodingString UTF8String];
  372. // Objects
  373. if (encodingCString[0] == '@') {
  374. NSString *class = [encodingString substringFromIndex:1];
  375. class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""];
  376. if ([class length] == 0) {
  377. class = @"id";
  378. } else {
  379. class = [class stringByAppendingString:@" *"];
  380. }
  381. return class;
  382. }
  383. // C Types
  384. #define TRANSLATE(ctype) \
  385. if (strcmp(encodingCString, @encode(ctype)) == 0) { \
  386. return (NSString *)CFSTR(#ctype); \
  387. }
  388. // Order matters here since some of the cocoa types are typedefed to c types.
  389. // We can't recover the exact mapping, but we choose to prefer the cocoa types.
  390. // This is not an exhaustive list, but it covers the most common types
  391. TRANSLATE(CGRect);
  392. TRANSLATE(CGPoint);
  393. TRANSLATE(CGSize);
  394. TRANSLATE(UIEdgeInsets);
  395. TRANSLATE(UIOffset);
  396. TRANSLATE(NSRange);
  397. TRANSLATE(CGAffineTransform);
  398. TRANSLATE(NSInteger);
  399. TRANSLATE(NSUInteger);
  400. TRANSLATE(CGFloat);
  401. TRANSLATE(BOOL);
  402. TRANSLATE(int);
  403. TRANSLATE(short);
  404. TRANSLATE(long);
  405. TRANSLATE(long long);
  406. TRANSLATE(unsigned char);
  407. TRANSLATE(unsigned int);
  408. TRANSLATE(unsigned short);
  409. TRANSLATE(unsigned long);
  410. TRANSLATE(unsigned long long);
  411. TRANSLATE(float);
  412. TRANSLATE(double);
  413. TRANSLATE(long double);
  414. TRANSLATE(char *);
  415. TRANSLATE(Class);
  416. TRANSLATE(objc_property_t);
  417. TRANSLATE(Ivar);
  418. TRANSLATE(Method);
  419. TRANSLATE(Category);
  420. TRANSLATE(NSZone *);
  421. TRANSLATE(SEL);
  422. TRANSLATE(void);
  423. #undef TRANSLATE
  424. // Qualifier Prefixes
  425. // Do this after the checks above since some of the direct translations (i.e. Method) contain a prefix.
  426. #define RECURSIVE_TRANSLATE(prefix, formatString) \
  427. if (encodingCString[0] == prefix) { \
  428. NSString *recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:1]]; \
  429. return [NSString stringWithFormat:formatString, recursiveType]; \
  430. }
  431. // If there's a qualifier prefix on the encoding, translate it and then
  432. // recursively call this method with the rest of the encoding string.
  433. RECURSIVE_TRANSLATE('^', @"%@ *");
  434. RECURSIVE_TRANSLATE('r', @"const %@");
  435. RECURSIVE_TRANSLATE('n', @"in %@");
  436. RECURSIVE_TRANSLATE('N', @"inout %@");
  437. RECURSIVE_TRANSLATE('o', @"out %@");
  438. RECURSIVE_TRANSLATE('O', @"bycopy %@");
  439. RECURSIVE_TRANSLATE('R', @"byref %@");
  440. RECURSIVE_TRANSLATE('V', @"oneway %@");
  441. RECURSIVE_TRANSLATE('b', @"bitfield(%@)");
  442. #undef RECURSIVE_TRANSLATE
  443. // If we couldn't translate, just return the original encoding string
  444. return encodingString;
  445. }
  446. + (NSValue *)valueForPrimitivePointer:(void *)pointer objcType:(const char *)type
  447. {
  448. // CASE marcro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  449. #define CASE(ctype, selectorpart) \
  450. if(strcmp(type, @encode(ctype)) == 0) { \
  451. return [NSNumber numberWith ## selectorpart: *(ctype *)pointer]; \
  452. }
  453. CASE(BOOL, Bool);
  454. CASE(unsigned char, UnsignedChar);
  455. CASE(short, Short);
  456. CASE(unsigned short, UnsignedShort);
  457. CASE(int, Int);
  458. CASE(unsigned int, UnsignedInt);
  459. CASE(long, Long);
  460. CASE(unsigned long, UnsignedLong);
  461. CASE(long long, LongLong);
  462. CASE(unsigned long long, UnsignedLongLong);
  463. CASE(float, Float);
  464. CASE(double, Double);
  465. #undef CASE
  466. NSValue *value = nil;
  467. @try {
  468. value = [NSValue valueWithBytes:pointer objCType:type];
  469. } @catch (NSException *exception) {
  470. // Certain type encodings are not supported by valueWithBytes:objCType:. Just fail silently if an exception is thrown.
  471. }
  472. return value;
  473. }
  474. + (void)getPrimitiveValue:(void *)value withObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString
  475. {
  476. if (strcmp(typeEncoding, @encode(CGRect)) == 0) {
  477. *(CGRect *)value = CGRectFromString(inputString);
  478. } else if (strcmp(typeEncoding, @encode(CGSize)) == 0) {
  479. *(CGSize *)value = CGSizeFromString(inputString);
  480. } else if (strcmp(typeEncoding, @encode(CGPoint)) == 0) {
  481. *(CGPoint *)value = CGPointFromString(inputString);
  482. } else if (strcmp(typeEncoding, @encode(CGAffineTransform)) == 0) {
  483. *(CGAffineTransform *)value = CGAffineTransformFromString(inputString);
  484. } else if (strcmp(typeEncoding, @encode(NSRange)) == 0) {
  485. *(NSRange *)value = NSRangeFromString(inputString);
  486. } else if (strcmp(typeEncoding, @encode(UIEdgeInsets)) == 0) {
  487. *(UIEdgeInsets *)value = UIEdgeInsetsFromString(inputString);
  488. } else if (strcmp(typeEncoding, @encode(UIOffset)) == 0) {
  489. *(UIOffset *)value = UIOffsetFromString(inputString);
  490. } else {
  491. NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
  492. [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
  493. NSNumber *number = [formatter numberFromString:inputString];
  494. if (strcmp(typeEncoding, @encode(char)) == 0) {
  495. // Special case - ask for the string's BOOL value if we couldn't parse a number.
  496. // This allows us to take YES/NO/true/false input.
  497. if (number) {
  498. *(char *)value = [number charValue];
  499. } else {
  500. *(char *)value = [inputString boolValue];
  501. }
  502. } else if (strcmp(typeEncoding, @encode(int)) == 0) {
  503. *(int *)value = [number intValue];
  504. } else if (strcmp(typeEncoding, @encode(short)) == 0) {
  505. *(short *)value = [number shortValue];
  506. } else if (strcmp(typeEncoding, @encode(long)) == 0) {
  507. *(long *)value = [number longValue];
  508. } else if (strcmp(typeEncoding, @encode(long long)) == 0) {
  509. *(long long *)value = [number longLongValue];
  510. } else if (strcmp(typeEncoding, @encode(unsigned char)) == 0) {
  511. *(unsigned char *)value = [number unsignedCharValue];
  512. } else if (strcmp(typeEncoding, @encode(unsigned int)) == 0) {
  513. *(unsigned int *)value = [number unsignedIntValue];
  514. } else if (strcmp(typeEncoding, @encode(unsigned short)) == 0) {
  515. *(unsigned short *)value = [number unsignedShortValue];
  516. } else if (strcmp(typeEncoding, @encode(unsigned long)) == 0) {
  517. *(unsigned long *)value = [number unsignedLongValue];
  518. } else if (strcmp(typeEncoding, @encode(unsigned long long)) == 0) {
  519. *(unsigned long long *)value = [number unsignedLongLongValue];
  520. } else if (strcmp(typeEncoding, @encode(float)) == 0) {
  521. *(float *)value = [number floatValue];
  522. } else if (strcmp(typeEncoding, @encode(double)) == 0) {
  523. *(double *)value = [number doubleValue];
  524. } else {
  525. // If we didn't match one of the supported types, fill the buffer with zeros.
  526. NSUInteger bytes = 0;
  527. NSGetSizeAndAlignment(typeEncoding, &bytes, NULL);
  528. memset(value, 0, bytes);
  529. }
  530. }
  531. }
  532. @end