FLEXRuntimeUtility.m 27 KB

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