You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

789 lines
33 KiB

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