FLEXRuntimeUtility.m 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  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 <UIKit/UIKit.h>
  9. #import "FLEXRuntimeUtility.h"
  10. // See https://developer.apple.com/library/ios/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
  11. NSString *const kFLEXUtilityAttributeTypeEncoding = @"T";
  12. NSString *const kFLEXUtilityAttributeBackingIvar = @"V";
  13. NSString *const kFLEXUtilityAttributeReadOnly = @"R";
  14. NSString *const kFLEXUtilityAttributeCopy = @"C";
  15. NSString *const kFLEXUtilityAttributeRetain = @"&";
  16. NSString *const kFLEXUtilityAttributeNonAtomic = @"N";
  17. NSString *const kFLEXUtilityAttributeCustomGetter = @"G";
  18. NSString *const kFLEXUtilityAttributeCustomSetter = @"S";
  19. NSString *const kFLEXUtilityAttributeDynamic = @"D";
  20. NSString *const kFLEXUtilityAttributeWeak = @"W";
  21. NSString *const kFLEXUtilityAttributeGarbageCollectable = @"P";
  22. NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding = @"t";
  23. static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
  24. typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
  25. FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector = 0,
  26. FLEXRuntimeUtilityErrorCodeInvocationFailed = 1,
  27. FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch = 2
  28. };
  29. // Arguments 0 and 1 are self and _cmd always
  30. const unsigned int kFLEXNumberOfImplicitArgs = 2;
  31. @implementation FLEXRuntimeUtility
  32. #pragma mark - Property Helpers (Public)
  33. + (NSString *)prettyNameForProperty:(objc_property_t)property
  34. {
  35. NSString *name = @(property_getName(property));
  36. NSString *encoding = [self typeEncodingForProperty:property];
  37. NSString *readableType = [self readableTypeForEncoding:encoding];
  38. return [self appendName:name toType:readableType];
  39. }
  40. + (NSString *)typeEncodingForProperty:(objc_property_t)property
  41. {
  42. NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
  43. return attributesDictionary[kFLEXUtilityAttributeTypeEncoding];
  44. }
  45. + (BOOL)isReadonlyProperty:(objc_property_t)property
  46. {
  47. return [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeReadOnly] != nil;
  48. }
  49. + (SEL)setterSelectorForProperty:(objc_property_t)property
  50. {
  51. SEL setterSelector = NULL;
  52. NSString *setterSelectorString = [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeCustomSetter];
  53. if (!setterSelectorString) {
  54. NSString *propertyName = @(property_getName(property));
  55. setterSelectorString = [NSString stringWithFormat:@"set%@%@:", [[propertyName substringToIndex:1] uppercaseString], [propertyName substringFromIndex:1]];
  56. }
  57. if (setterSelectorString) {
  58. setterSelector = NSSelectorFromString(setterSelectorString);
  59. }
  60. return setterSelector;
  61. }
  62. + (NSString *)fullDescriptionForProperty:(objc_property_t)property
  63. {
  64. NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
  65. NSMutableArray<NSString *> *attributesStrings = [NSMutableArray array];
  66. // Atomicity
  67. if (attributesDictionary[kFLEXUtilityAttributeNonAtomic]) {
  68. [attributesStrings addObject:@"nonatomic"];
  69. } else {
  70. [attributesStrings addObject:@"atomic"];
  71. }
  72. // Storage
  73. if (attributesDictionary[kFLEXUtilityAttributeRetain]) {
  74. [attributesStrings addObject:@"strong"];
  75. } else if (attributesDictionary[kFLEXUtilityAttributeCopy]) {
  76. [attributesStrings addObject:@"copy"];
  77. } else if (attributesDictionary[kFLEXUtilityAttributeWeak]) {
  78. [attributesStrings addObject:@"weak"];
  79. } else {
  80. [attributesStrings addObject:@"assign"];
  81. }
  82. // Mutability
  83. if (attributesDictionary[kFLEXUtilityAttributeReadOnly]) {
  84. [attributesStrings addObject:@"readonly"];
  85. } else {
  86. [attributesStrings addObject:@"readwrite"];
  87. }
  88. // Custom getter/setter
  89. NSString *customGetter = attributesDictionary[kFLEXUtilityAttributeCustomGetter];
  90. NSString *customSetter = attributesDictionary[kFLEXUtilityAttributeCustomSetter];
  91. if (customGetter) {
  92. [attributesStrings addObject:[NSString stringWithFormat:@"getter=%@", customGetter]];
  93. }
  94. if (customSetter) {
  95. [attributesStrings addObject:[NSString stringWithFormat:@"setter=%@", customSetter]];
  96. }
  97. NSString *attributesString = [attributesStrings componentsJoinedByString:@", "];
  98. NSString *shortName = [self prettyNameForProperty:property];
  99. return [NSString stringWithFormat:@"@property (%@) %@", attributesString, shortName];
  100. }
  101. + (id)valueForProperty:(objc_property_t)property onObject:(id)object
  102. {
  103. NSString *customGetterString = nil;
  104. char *customGetterName = property_copyAttributeValue(property, "G");
  105. if (customGetterName) {
  106. customGetterString = @(customGetterName);
  107. free(customGetterName);
  108. }
  109. SEL getterSelector;
  110. if ([customGetterString length] > 0) {
  111. getterSelector = NSSelectorFromString(customGetterString);
  112. } else {
  113. NSString *propertyName = @(property_getName(property));
  114. getterSelector = NSSelectorFromString(propertyName);
  115. }
  116. return [self performSelector:getterSelector onObject:object withArguments:nil error:NULL];
  117. }
  118. + (NSString *)descriptionForIvarOrPropertyValue:(id)value
  119. {
  120. NSString *description = nil;
  121. // Special case BOOL for better readability.
  122. if ([value isKindOfClass:[NSValue class]]) {
  123. const char *type = [value objCType];
  124. if (strcmp(type, @encode(BOOL)) == 0) {
  125. BOOL boolValue = NO;
  126. [value getValue:&boolValue];
  127. description = boolValue ? @"YES" : @"NO";
  128. } else if (strcmp(type, @encode(SEL)) == 0) {
  129. SEL selector = NULL;
  130. [value getValue:&selector];
  131. description = NSStringFromSelector(selector);
  132. }
  133. }
  134. @try {
  135. if (!description) {
  136. // Single line display - replace newlines and tabs with spaces.
  137. description = [[value description] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
  138. description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
  139. }
  140. } @catch (NSException *e) {
  141. description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"];
  142. }
  143. if (!description) {
  144. description = @"nil";
  145. }
  146. return description;
  147. }
  148. + (void)tryAddPropertyWithName:(const char *)name attributes:(NSDictionary<NSString *, NSString *> *)attributePairs toClass:(__unsafe_unretained Class)theClass
  149. {
  150. objc_property_t property = class_getProperty(theClass, name);
  151. if (!property) {
  152. unsigned int totalAttributesCount = (unsigned int)[attributePairs count];
  153. objc_property_attribute_t *attributes = malloc(sizeof(objc_property_attribute_t) * totalAttributesCount);
  154. if (attributes) {
  155. unsigned int attributeIndex = 0;
  156. for (NSString *attributeName in [attributePairs allKeys]) {
  157. objc_property_attribute_t attribute;
  158. attribute.name = [attributeName UTF8String];
  159. attribute.value = [attributePairs[attributeName] UTF8String];
  160. attributes[attributeIndex++] = attribute;
  161. }
  162. class_addProperty(theClass, name, attributes, totalAttributesCount);
  163. free(attributes);
  164. }
  165. }
  166. }
  167. #pragma mark - Ivar Helpers (Public)
  168. + (NSString *)prettyNameForIvar:(Ivar)ivar
  169. {
  170. const char *nameCString = ivar_getName(ivar);
  171. NSString *name = nameCString ? @(nameCString) : nil;
  172. const char *encodingCString = ivar_getTypeEncoding(ivar);
  173. NSString *encoding = encodingCString ? @(encodingCString) : nil;
  174. NSString *readableType = [self readableTypeForEncoding:encoding];
  175. return [self appendName:name toType:readableType];
  176. }
  177. + (id)valueForIvar:(Ivar)ivar onObject:(id)object
  178. {
  179. id value = nil;
  180. const char *type = ivar_getTypeEncoding(ivar);
  181. #ifdef __arm64__
  182. // See http://www.sealiesoftware.com/blog/archive/2013/09/24/objc_explain_Non-pointer_isa.html
  183. const char *name = ivar_getName(ivar);
  184. if (type[0] == @encode(Class)[0] && strcmp(name, "isa") == 0) {
  185. value = object_getClass(object);
  186. } else
  187. #endif
  188. if (type[0] == @encode(id)[0] || type[0] == @encode(Class)[0]) {
  189. value = object_getIvar(object, ivar);
  190. } else {
  191. ptrdiff_t offset = ivar_getOffset(ivar);
  192. void *pointer = (__bridge void *)object + offset;
  193. value = [self valueForPrimitivePointer:pointer objCType:type];
  194. }
  195. return value;
  196. }
  197. + (void)setValue:(id)value forIvar:(Ivar)ivar onObject:(id)object
  198. {
  199. const char *typeEncodingCString = ivar_getTypeEncoding(ivar);
  200. if (typeEncodingCString[0] == '@') {
  201. object_setIvar(object, ivar, value);
  202. } else if ([value isKindOfClass:[NSValue class]]) {
  203. // Primitive - unbox the NSValue.
  204. NSValue *valueValue = (NSValue *)value;
  205. // Make sure that the box contained the correct type.
  206. NSAssert(strcmp([valueValue objCType], typeEncodingCString) == 0, @"Type encoding mismatch (value: %s; ivar: %s) in setting ivar named: %s on object: %@", [valueValue objCType], typeEncodingCString, ivar_getName(ivar), object);
  207. NSUInteger bufferSize = 0;
  208. @try {
  209. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  210. NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
  211. } @catch (NSException *exception) { }
  212. if (bufferSize > 0) {
  213. void *buffer = calloc(bufferSize, 1);
  214. [valueValue getValue:buffer];
  215. ptrdiff_t offset = ivar_getOffset(ivar);
  216. void *pointer = (__bridge void *)object + offset;
  217. memcpy(pointer, buffer, bufferSize);
  218. free(buffer);
  219. }
  220. }
  221. }
  222. #pragma mark - Method Helpers (Public)
  223. + (NSString *)prettyNameForMethod:(Method)method isClassMethod:(BOOL)isClassMethod
  224. {
  225. NSString *selectorName = NSStringFromSelector(method_getName(method));
  226. NSString *methodTypeString = isClassMethod ? @"+" : @"-";
  227. char *returnType = method_copyReturnType(method);
  228. NSString *readableReturnType = [self readableTypeForEncoding:@(returnType)];
  229. free(returnType);
  230. NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType];
  231. NSArray<NSString *> *components = [self prettyArgumentComponentsForMethod:method];
  232. if ([components count] > 0) {
  233. prettyName = [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]];
  234. } else {
  235. prettyName = [prettyName stringByAppendingString:selectorName];
  236. }
  237. return prettyName;
  238. }
  239. + (NSArray<NSString *> *)prettyArgumentComponentsForMethod:(Method)method
  240. {
  241. NSMutableArray<NSString *> *components = [NSMutableArray array];
  242. NSString *selectorName = NSStringFromSelector(method_getName(method));
  243. NSMutableArray<NSString *> *selectorComponents = [[selectorName componentsSeparatedByString:@":"] mutableCopy];
  244. // this is a workaround cause method_getNumberOfArguments() returns wrong number for some methods
  245. if (selectorComponents.count == 1) {
  246. return [selectorComponents copy];
  247. }
  248. if ([selectorComponents.lastObject isEqualToString:@""]) {
  249. [selectorComponents removeLastObject];
  250. }
  251. for (unsigned int argIndex = 0; argIndex < selectorComponents.count; argIndex++) {
  252. char *argType = method_copyArgumentType(method, argIndex + kFLEXNumberOfImplicitArgs);
  253. NSString *readableArgType = (argType != NULL) ? [self readableTypeForEncoding:@(argType)] : nil;
  254. free(argType);
  255. NSString *prettyComponent = [NSString stringWithFormat:@"%@:(%@) ", [selectorComponents objectAtIndex:argIndex], readableArgType];
  256. [components addObject:prettyComponent];
  257. }
  258. return components;
  259. }
  260. #pragma mark - Method Calling/Field Editing (Public)
  261. + (id)performSelector:(SEL)selector onObject:(id)object withArguments:(NSArray *)arguments error:(NSError * __autoreleasing *)error
  262. {
  263. // Bail if the object won't respond to this selector.
  264. if (![object respondsToSelector:selector]) {
  265. if (error) {
  266. NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"%@ does not respond to the selector %@", object, NSStringFromSelector(selector)]};
  267. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeDoesNotRecognizeSelector userInfo:userInfo];
  268. }
  269. return nil;
  270. }
  271. // Build the invocation
  272. NSMethodSignature *methodSignature = [object methodSignatureForSelector:selector];
  273. NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:methodSignature];
  274. [invocation setSelector:selector];
  275. [invocation setTarget:object];
  276. [invocation retainArguments];
  277. // Always self and _cmd
  278. NSUInteger numberOfArguments = [methodSignature numberOfArguments];
  279. for (NSUInteger argumentIndex = kFLEXNumberOfImplicitArgs; argumentIndex < numberOfArguments; argumentIndex++) {
  280. NSUInteger argumentsArrayIndex = argumentIndex - kFLEXNumberOfImplicitArgs;
  281. id argumentObject = [arguments count] > argumentsArrayIndex ? arguments[argumentsArrayIndex] : nil;
  282. // NSNull in the arguments array can be passed as a placeholder to indicate nil. We only need to set the argument if it will be non-nil.
  283. if (argumentObject && ![argumentObject isKindOfClass:[NSNull class]]) {
  284. const char *typeEncodingCString = [methodSignature getArgumentTypeAtIndex:argumentIndex];
  285. if (typeEncodingCString[0] == @encode(id)[0] || typeEncodingCString[0] == @encode(Class)[0] || [self isTollFreeBridgedValue:argumentObject forCFType:typeEncodingCString]) {
  286. // Object
  287. [invocation setArgument:&argumentObject atIndex:argumentIndex];
  288. } else if (strcmp(typeEncodingCString, @encode(CGColorRef)) == 0 && [argumentObject isKindOfClass:[UIColor class]]) {
  289. // Bridging UIColor to CGColorRef
  290. CGColorRef colorRef = [argumentObject CGColor];
  291. [invocation setArgument:&colorRef atIndex:argumentIndex];
  292. } else if ([argumentObject isKindOfClass:[NSValue class]]) {
  293. // Primitive boxed in NSValue
  294. NSValue *argumentValue = (NSValue *)argumentObject;
  295. // Ensure that the type encoding on the NSValue matches the type encoding of the argument in the method signature
  296. if (strcmp([argumentValue objCType], typeEncodingCString) != 0) {
  297. if (error) {
  298. NSDictionary<NSString *, id> *userInfo = @{ NSLocalizedDescriptionKey : [NSString stringWithFormat:@"Type encoding mismatch for agrument at index %lu. Value type: %s; Method argument type: %s.", (unsigned long)argumentsArrayIndex, [argumentValue objCType], typeEncodingCString]};
  299. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain code:FLEXRuntimeUtilityErrorCodeArgumentTypeMismatch userInfo:userInfo];
  300. }
  301. return nil;
  302. }
  303. @try {
  304. NSUInteger bufferSize = 0;
  305. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  306. NSGetSizeAndAlignment(typeEncodingCString, &bufferSize, NULL);
  307. if (bufferSize > 0) {
  308. void *buffer = calloc(bufferSize, 1);
  309. [argumentValue getValue:buffer];
  310. [invocation setArgument:buffer atIndex:argumentIndex];
  311. free(buffer);
  312. }
  313. } @catch (NSException *exception) { }
  314. }
  315. }
  316. }
  317. // Try to invoke the invocation but guard against an exception being thrown.
  318. id returnObject = nil;
  319. @try {
  320. // Some methods are not fit to be called...
  321. // Looking at you -[UIResponder(UITextInputAdditions) _caretRect]
  322. [invocation invoke];
  323. // Retreive the return value and box if necessary.
  324. const char *returnType = [methodSignature methodReturnType];
  325. if (returnType[0] == @encode(id)[0] || returnType[0] == @encode(Class)[0]) {
  326. // Return value is an object.
  327. __unsafe_unretained id objectReturnedFromMethod = nil;
  328. [invocation getReturnValue:&objectReturnedFromMethod];
  329. returnObject = objectReturnedFromMethod;
  330. } else if (returnType[0] != @encode(void)[0]) {
  331. // Will use arbitrary buffer for return value and box it.
  332. void *returnValue = malloc([methodSignature methodReturnLength]);
  333. if (returnValue) {
  334. [invocation getReturnValue:returnValue];
  335. returnObject = [self valueForPrimitivePointer:returnValue objCType:returnType];
  336. free(returnValue);
  337. }
  338. }
  339. } @catch (NSException *exception) {
  340. // Bummer...
  341. if (error) {
  342. // "… on <class>" / "… on instance of <class>"
  343. NSString *class = NSStringFromClass([object class]);
  344. NSString *calledOn = object == [object class] ? class : [@"an instance of " stringByAppendingString:class];
  345. NSString *message = [NSString stringWithFormat:@"Exception '%@' thrown while performing selector '%@' on %@.\nReason:\n\n%@",
  346. exception.name,
  347. NSStringFromSelector(selector),
  348. calledOn,
  349. exception.reason];
  350. *error = [NSError errorWithDomain:FLEXRuntimeUtilityErrorDomain
  351. code:FLEXRuntimeUtilityErrorCodeInvocationFailed
  352. userInfo:@{ NSLocalizedDescriptionKey : message }];
  353. }
  354. }
  355. return returnObject;
  356. }
  357. + (BOOL)isTollFreeBridgedValue:(id)value forCFType:(const char *)typeEncoding
  358. {
  359. // See https://developer.apple.com/library/ios/documentation/general/conceptual/CocoaEncyclopedia/Toll-FreeBridgin/Toll-FreeBridgin.html
  360. #define CASE(cftype, foundationClass) \
  361. if(strcmp(typeEncoding, @encode(cftype)) == 0) { \
  362. return [value isKindOfClass:[foundationClass class]]; \
  363. }
  364. CASE(CFArrayRef, NSArray);
  365. CASE(CFAttributedStringRef, NSAttributedString);
  366. CASE(CFCalendarRef, NSCalendar);
  367. CASE(CFCharacterSetRef, NSCharacterSet);
  368. CASE(CFDataRef, NSData);
  369. CASE(CFDateRef, NSDate);
  370. CASE(CFDictionaryRef, NSDictionary);
  371. CASE(CFErrorRef, NSError);
  372. CASE(CFLocaleRef, NSLocale);
  373. CASE(CFMutableArrayRef, NSMutableArray);
  374. CASE(CFMutableAttributedStringRef, NSMutableAttributedString);
  375. CASE(CFMutableCharacterSetRef, NSMutableCharacterSet);
  376. CASE(CFMutableDataRef, NSMutableData);
  377. CASE(CFMutableDictionaryRef, NSMutableDictionary);
  378. CASE(CFMutableSetRef, NSMutableSet);
  379. CASE(CFMutableStringRef, NSMutableString);
  380. CASE(CFNumberRef, NSNumber);
  381. CASE(CFReadStreamRef, NSInputStream);
  382. CASE(CFRunLoopTimerRef, NSTimer);
  383. CASE(CFSetRef, NSSet);
  384. CASE(CFStringRef, NSString);
  385. CASE(CFTimeZoneRef, NSTimeZone);
  386. CASE(CFURLRef, NSURL);
  387. CASE(CFWriteStreamRef, NSOutputStream);
  388. #undef CASE
  389. return NO;
  390. }
  391. + (NSString *)editableJSONStringForObject:(id)object
  392. {
  393. NSString *editableDescription = nil;
  394. if (object) {
  395. // This is a hack to use JSON serialization for our editable objects.
  396. // NSJSONSerialization doesn't allow writing fragments - the top level object must be an array or dictionary.
  397. // We always wrap the object inside an array and then strip the outer square braces off the final string.
  398. NSArray *wrappedObject = @[object];
  399. if ([NSJSONSerialization isValidJSONObject:wrappedObject]) {
  400. NSString *wrappedDescription = [[NSString alloc] initWithData:[NSJSONSerialization dataWithJSONObject:wrappedObject options:0 error:NULL] encoding:NSUTF8StringEncoding];
  401. editableDescription = [wrappedDescription substringWithRange:NSMakeRange(1, [wrappedDescription length] - 2)];
  402. }
  403. }
  404. return editableDescription;
  405. }
  406. + (id)objectValueFromEditableJSONString:(NSString *)string
  407. {
  408. id value = nil;
  409. // nil for empty string/whitespace
  410. if ([[string stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] length] > 0) {
  411. value = [NSJSONSerialization JSONObjectWithData:[string dataUsingEncoding:NSUTF8StringEncoding] options:NSJSONReadingAllowFragments error:NULL];
  412. }
  413. return value;
  414. }
  415. + (NSValue *)valueForNumberWithObjCType:(const char *)typeEncoding fromInputString:(NSString *)inputString
  416. {
  417. NSNumberFormatter *formatter = [[NSNumberFormatter alloc] init];
  418. [formatter setNumberStyle:NSNumberFormatterDecimalStyle];
  419. NSNumber *number = [formatter numberFromString:inputString];
  420. // Make sure we box the number with the correct type encoding so it can be propperly unboxed later via getValue:
  421. NSValue *value = nil;
  422. if (strcmp(typeEncoding, @encode(char)) == 0) {
  423. char primitiveValue = [number charValue];
  424. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  425. } else if (strcmp(typeEncoding, @encode(int)) == 0) {
  426. int primitiveValue = [number intValue];
  427. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  428. } else if (strcmp(typeEncoding, @encode(short)) == 0) {
  429. short primitiveValue = [number shortValue];
  430. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  431. } else if (strcmp(typeEncoding, @encode(long)) == 0) {
  432. long primitiveValue = [number longValue];
  433. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  434. } else if (strcmp(typeEncoding, @encode(long long)) == 0) {
  435. long long primitiveValue = [number longLongValue];
  436. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  437. } else if (strcmp(typeEncoding, @encode(unsigned char)) == 0) {
  438. unsigned char primitiveValue = [number unsignedCharValue];
  439. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  440. } else if (strcmp(typeEncoding, @encode(unsigned int)) == 0) {
  441. unsigned int primitiveValue = [number unsignedIntValue];
  442. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  443. } else if (strcmp(typeEncoding, @encode(unsigned short)) == 0) {
  444. unsigned short primitiveValue = [number unsignedShortValue];
  445. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  446. } else if (strcmp(typeEncoding, @encode(unsigned long)) == 0) {
  447. unsigned long primitiveValue = [number unsignedLongValue];
  448. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  449. } else if (strcmp(typeEncoding, @encode(unsigned long long)) == 0) {
  450. unsigned long long primitiveValue = [number unsignedLongValue];
  451. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  452. } else if (strcmp(typeEncoding, @encode(float)) == 0) {
  453. float primitiveValue = [number floatValue];
  454. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  455. } else if (strcmp(typeEncoding, @encode(double)) == 0) {
  456. double primitiveValue = [number doubleValue];
  457. value = [NSValue value:&primitiveValue withObjCType:typeEncoding];
  458. }
  459. return value;
  460. }
  461. + (void)enumerateTypesInStructEncoding:(const char *)structEncoding usingBlock:(void (^)(NSString *structName, const char *fieldTypeEncoding, NSString *prettyTypeEncoding, NSUInteger fieldIndex, NSUInteger fieldOffset))typeBlock
  462. {
  463. if (structEncoding && structEncoding[0] == '{') {
  464. const char *equals = strchr(structEncoding, '=');
  465. if (equals) {
  466. const char *nameStart = structEncoding + 1;
  467. NSString *structName = [@(structEncoding) substringWithRange:NSMakeRange(nameStart - structEncoding, equals - nameStart)];
  468. NSUInteger fieldAlignment = 0;
  469. NSUInteger structSize = 0;
  470. @try {
  471. // NSGetSizeAndAlignment barfs on type encoding for bitfields.
  472. NSGetSizeAndAlignment(structEncoding, &structSize, &fieldAlignment);
  473. } @catch (NSException *exception) { }
  474. if (structSize > 0) {
  475. NSUInteger runningFieldIndex = 0;
  476. NSUInteger runningFieldOffset = 0;
  477. const char *typeStart = equals + 1;
  478. while (*typeStart != '}') {
  479. NSUInteger fieldSize = 0;
  480. // If the struct type encoding was successfully handled by NSGetSizeAndAlignment above, we *should* be ok with the field here.
  481. const char *nextTypeStart = NSGetSizeAndAlignment(typeStart, &fieldSize, NULL);
  482. NSString *typeEncoding = [@(structEncoding) substringWithRange:NSMakeRange(typeStart - structEncoding, nextTypeStart - typeStart)];
  483. typeBlock(structName, [typeEncoding UTF8String], [self readableTypeForEncoding:typeEncoding], runningFieldIndex, runningFieldOffset);
  484. runningFieldOffset += fieldSize;
  485. // Padding to keep propper alignment. __attribute((packed)) structs will break here.
  486. // The type encoding is no different for packed structs, so it's not clear there's anything we can do for those.
  487. if (runningFieldOffset % fieldAlignment != 0) {
  488. runningFieldOffset += fieldAlignment - runningFieldOffset % fieldAlignment;
  489. }
  490. runningFieldIndex++;
  491. typeStart = nextTypeStart;
  492. }
  493. }
  494. }
  495. }
  496. }
  497. #pragma mark - Internal Helpers
  498. + (NSDictionary<NSString *, NSString *> *)attributesDictionaryForProperty:(objc_property_t)property
  499. {
  500. NSString *attributes = @(property_getAttributes(property));
  501. // Thanks to MAObjcRuntime for inspiration here.
  502. NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","];
  503. NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary dictionaryWithCapacity:[attributePairs count]];
  504. for (NSString *attributePair in attributePairs) {
  505. [attributesDictionary setObject:[attributePair substringFromIndex:1] forKey:[attributePair substringToIndex:1]];
  506. }
  507. return attributesDictionary;
  508. }
  509. + (NSString *)appendName:(NSString *)name toType:(NSString *)type
  510. {
  511. NSString *combined = nil;
  512. if ([type characterAtIndex:[type length] - 1] == '*') {
  513. combined = [type stringByAppendingString:name];
  514. } else {
  515. combined = [type stringByAppendingFormat:@" %@", name];
  516. }
  517. return combined;
  518. }
  519. + (NSString *)readableTypeForEncoding:(NSString *)encodingString
  520. {
  521. if (!encodingString) {
  522. return nil;
  523. }
  524. // See https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
  525. // class-dump has a much nicer and much more complete implementation for this task, but it is distributed under GPLv2 :/
  526. // See https://github.com/nygard/class-dump/blob/master/Source/CDType.m
  527. // Warning: this method uses multiple middle returns and macros to cut down on boilerplate.
  528. // The use of macros here was inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  529. const char *encodingCString = [encodingString UTF8String];
  530. // Objects
  531. if (encodingCString[0] == '@') {
  532. NSString *class = [encodingString substringFromIndex:1];
  533. class = [class stringByReplacingOccurrencesOfString:@"\"" withString:@""];
  534. if ([class length] == 0 || [class isEqual:@"?"]) {
  535. class = @"id";
  536. } else {
  537. class = [class stringByAppendingString:@" *"];
  538. }
  539. return class;
  540. }
  541. // C Types
  542. #define TRANSLATE(ctype) \
  543. if (strcmp(encodingCString, @encode(ctype)) == 0) { \
  544. return (NSString *)CFSTR(#ctype); \
  545. }
  546. // Order matters here since some of the cocoa types are typedefed to c types.
  547. // We can't recover the exact mapping, but we choose to prefer the cocoa types.
  548. // This is not an exhaustive list, but it covers the most common types
  549. TRANSLATE(CGRect);
  550. TRANSLATE(CGPoint);
  551. TRANSLATE(CGSize);
  552. TRANSLATE(UIEdgeInsets);
  553. TRANSLATE(UIOffset);
  554. TRANSLATE(NSRange);
  555. TRANSLATE(CGAffineTransform);
  556. TRANSLATE(CATransform3D);
  557. TRANSLATE(CGColorRef);
  558. TRANSLATE(CGPathRef);
  559. TRANSLATE(CGContextRef);
  560. TRANSLATE(NSInteger);
  561. TRANSLATE(NSUInteger);
  562. TRANSLATE(CGFloat);
  563. TRANSLATE(BOOL);
  564. TRANSLATE(int);
  565. TRANSLATE(short);
  566. TRANSLATE(long);
  567. TRANSLATE(long long);
  568. TRANSLATE(unsigned char);
  569. TRANSLATE(unsigned int);
  570. TRANSLATE(unsigned short);
  571. TRANSLATE(unsigned long);
  572. TRANSLATE(unsigned long long);
  573. TRANSLATE(float);
  574. TRANSLATE(double);
  575. TRANSLATE(long double);
  576. TRANSLATE(char *);
  577. TRANSLATE(Class);
  578. TRANSLATE(objc_property_t);
  579. TRANSLATE(Ivar);
  580. TRANSLATE(Method);
  581. TRANSLATE(Category);
  582. TRANSLATE(NSZone *);
  583. TRANSLATE(SEL);
  584. TRANSLATE(void);
  585. #undef TRANSLATE
  586. // Qualifier Prefixes
  587. // Do this after the checks above since some of the direct translations (i.e. Method) contain a prefix.
  588. #define RECURSIVE_TRANSLATE(prefix, formatString) \
  589. if (encodingCString[0] == prefix) { \
  590. NSString *recursiveType = [self readableTypeForEncoding:[encodingString substringFromIndex:1]]; \
  591. return [NSString stringWithFormat:formatString, recursiveType]; \
  592. }
  593. // If there's a qualifier prefix on the encoding, translate it and then
  594. // recursively call this method with the rest of the encoding string.
  595. RECURSIVE_TRANSLATE('^', @"%@ *");
  596. RECURSIVE_TRANSLATE('r', @"const %@");
  597. RECURSIVE_TRANSLATE('n', @"in %@");
  598. RECURSIVE_TRANSLATE('N', @"inout %@");
  599. RECURSIVE_TRANSLATE('o', @"out %@");
  600. RECURSIVE_TRANSLATE('O', @"bycopy %@");
  601. RECURSIVE_TRANSLATE('R', @"byref %@");
  602. RECURSIVE_TRANSLATE('V', @"oneway %@");
  603. RECURSIVE_TRANSLATE('b', @"bitfield(%@)");
  604. #undef RECURSIVE_TRANSLATE
  605. // If we couldn't translate, just return the original encoding string
  606. return encodingString;
  607. }
  608. + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type
  609. {
  610. // CASE macro inspired by https://www.mikeash.com/pyblog/friday-qa-2013-02-08-lets-build-key-value-coding.html
  611. #define CASE(ctype, selectorpart) \
  612. if(strcmp(type, @encode(ctype)) == 0) { \
  613. return [NSNumber numberWith ## selectorpart: *(ctype *)pointer]; \
  614. }
  615. CASE(BOOL, Bool);
  616. CASE(unsigned char, UnsignedChar);
  617. CASE(short, Short);
  618. CASE(unsigned short, UnsignedShort);
  619. CASE(int, Int);
  620. CASE(unsigned int, UnsignedInt);
  621. CASE(long, Long);
  622. CASE(unsigned long, UnsignedLong);
  623. CASE(long long, LongLong);
  624. CASE(unsigned long long, UnsignedLongLong);
  625. CASE(float, Float);
  626. CASE(double, Double);
  627. #undef CASE
  628. NSValue *value = nil;
  629. @try {
  630. value = [NSValue valueWithBytes:pointer objCType:type];
  631. } @catch (NSException *exception) {
  632. // Certain type encodings are not supported by valueWithBytes:objCType:. Just fail silently if an exception is thrown.
  633. }
  634. return value;
  635. }
  636. @end