Просмотр исходного кода

Runtime utilities upgrades

An objc runtime wrapper derived from NSExceptional/MirrorKit has been added and will come to replace a few small classes and many of the methods in the FLEXRuntimeUtility and FLEXUtility classes.
Tanner Bennett лет назад: 6
Родитель
Сommit
1e9379dc02
37 измененных файлов с 3492 добавлено и 118 удалено
  1. 1 1
      Classes/GlobalStateExplorers/FLEXInstancesTableViewController.m
  2. 4 4
      Classes/ObjectExplorers/Controllers/FLEXViewExplorerViewController.m
  3. 24 0
      Classes/Utility/Categories/FLEXRuntime+Compare.h
  4. 39 0
      Classes/Utility/Categories/FLEXRuntime+Compare.m
  5. 32 0
      Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.h
  6. 135 0
      Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.m
  7. 18 0
      Classes/Utility/Categories/NSDictionary+ObjcRuntime.h
  8. 89 0
      Classes/Utility/Categories/NSDictionary+ObjcRuntime.m
  9. 206 0
      Classes/Utility/Categories/NSObject+Reflection.h
  10. 350 0
      Classes/Utility/Categories/NSObject+Reflection.m
  11. 20 0
      Classes/Utility/Categories/NSString+ObjcRuntime.h
  12. 74 0
      Classes/Utility/Categories/NSString+ObjcRuntime.m
  13. 1 4
      Classes/Utility/FLEXUtility.h
  14. 7 42
      Classes/Utility/FLEXUtility.m
  15. 80 0
      Classes/Utility/Runtime/FLEXClassBuilder.h
  16. 170 0
      Classes/Utility/Runtime/FLEXClassBuilder.m
  17. 42 0
      Classes/Utility/Runtime/FLEXIvar.h
  18. 85 0
      Classes/Utility/Runtime/FLEXIvar.m
  19. 95 0
      Classes/Utility/Runtime/FLEXMethod.h
  20. 404 0
      Classes/Utility/Runtime/FLEXMethod.m
  21. 43 0
      Classes/Utility/Runtime/FLEXMethodBase.h
  22. 49 0
      Classes/Utility/Runtime/FLEXMethodBase.m
  23. 60 0
      Classes/Utility/Runtime/FLEXMirror.h
  24. 138 0
      Classes/Utility/Runtime/FLEXMirror.m
  25. 0 0
      Classes/Utility/Runtime/FLEXObjcInternal.h
  26. 0 0
      Classes/Utility/Runtime/FLEXObjcInternal.mm
  27. 124 0
      Classes/Utility/Runtime/FLEXProperty.h
  28. 186 0
      Classes/Utility/Runtime/FLEXProperty.m
  29. 106 0
      Classes/Utility/Runtime/FLEXPropertyAttributes.h
  30. 331 0
      Classes/Utility/Runtime/FLEXPropertyAttributes.m
  31. 55 0
      Classes/Utility/Runtime/FLEXProtocol.h
  32. 139 0
      Classes/Utility/Runtime/FLEXProtocol.m
  33. 41 0
      Classes/Utility/Runtime/FLEXProtocolBuilder.h
  34. 93 0
      Classes/Utility/Runtime/FLEXProtocolBuilder.m
  35. 39 14
      Classes/Utility/FLEXRuntimeUtility.h
  36. 86 47
      Classes/Utility/FLEXRuntimeUtility.m
  37. 126 6
      FLEX.xcodeproj/project.pbxproj

+ 1 - 1
Classes/GlobalStateExplorers/FLEXInstancesTableViewController.m

@@ -215,7 +215,7 @@
 
     FLEXObjectRef *row = self.sections[indexPath.section][indexPath.row];
     cell.textLabel.text = row.reference;
-    cell.detailTextLabel.text = [FLEXRuntimeUtility descriptionForIvarOrPropertyValue:row.object];
+    cell.detailTextLabel.text = [FLEXRuntimeUtility summaryForObject:row.object];
     
     return cell;
 }

+ 4 - 4
Classes/ObjectExplorers/Controllers/FLEXViewExplorerViewController.m

@@ -164,15 +164,15 @@ typedef NS_ENUM(NSUInteger, FLEXViewExplorerRow) {
 
 #pragma mark - Runtime Adjustment
 
-#define PropertyKey(suffix) kFLEXUtilityAttribute##suffix : @""
-#define PropertyKeyGetter(getter) kFLEXUtilityAttributeCustomGetter : NSStringFromSelector(@selector(getter))
-#define PropertyKeySetter(setter) kFLEXUtilityAttributeCustomSetter : NSStringFromSelector(@selector(setter))
+#define PropertyKey(suffix) kFLEXPropertyAttributeKey##suffix : @""
+#define PropertyKeyGetter(getter) kFLEXPropertyAttributeKeyCustomGetter : NSStringFromSelector(@selector(getter))
+#define PropertyKeySetter(setter) kFLEXPropertyAttributeKeyCustomSetter : NSStringFromSelector(@selector(setter))
 
 /// Takes: min iOS version, property name, target class, property type, and a list of attributes
 #define FLEXRuntimeUtilityTryAddProperty(iOS_atLeast, name, cls, type, ...) ({ \
     if (@available(iOS iOS_atLeast, *)) { \
         NSMutableDictionary *attrs = [NSMutableDictionary dictionaryWithDictionary:@{ \
-            kFLEXUtilityAttributeTypeEncoding : @(type), \
+            kFLEXPropertyAttributeKeyTypeEncoding : @(type), \
             __VA_ARGS__ \
         }]; \
         [FLEXRuntimeUtility \

+ 24 - 0
Classes/Utility/Categories/FLEXRuntime+Compare.h

@@ -0,0 +1,24 @@
+//
+//  FLEXRuntime+Compare.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXProperty.h"
+#import "FLEXIvar.h"
+#import "FLEXMethodBase.h"
+
+@interface FLEXProperty (Compare)
+- (NSComparisonResult)compare:(FLEXProperty *)other;
+@end
+
+@interface FLEXIvar (Compare)
+- (NSComparisonResult)compare:(FLEXIvar *)other;
+@end
+
+@interface FLEXMethodBase (Compare)
+- (NSComparisonResult)compare:(FLEXMethodBase *)other;
+@end

+ 39 - 0
Classes/Utility/Categories/FLEXRuntime+Compare.m

@@ -0,0 +1,39 @@
+//
+//  FLEXRuntime+Compare.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 8/28/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXRuntime+Compare.h"
+
+@implementation FLEXProperty (Compare)
+
+- (NSComparisonResult)compare:(FLEXProperty *)other {
+    NSComparisonResult r = [self.name caseInsensitiveCompare:other.name];
+    if (r == NSOrderedSame) {
+        // TODO make sure empty image name sorts above an image name
+        return [self.imageName ?: @"" compare:other.imageName];
+    }
+
+    return r;
+}
+
+@end
+
+@implementation FLEXIvar (Compare)
+
+- (NSComparisonResult)compare:(FLEXIvar *)other {
+    return [self.name caseInsensitiveCompare:other.name];
+}
+
+@end
+
+@implementation FLEXMethodBase (Compare)
+
+- (NSComparisonResult)compare:(FLEXMethodBase *)other {
+    return [self.name caseInsensitiveCompare:other.name];
+}
+
+@end

+ 32 - 0
Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.h

@@ -0,0 +1,32 @@
+//
+//  FLEXRuntime+UIKitHelpers.h
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/16/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import <UIKit/UIKit.h>
+#import "FLEXProperty.h"
+#import "FLEXIvar.h"
+#import "FLEXMethod.h"
+
+@protocol FLEXRuntimeMetadata <NSObject>
+/// YES for properties and ivars which surely support editing, NO for all methods.
+@property (nonatomic, readonly) BOOL isEditable;
+/// NO for ivars, YES for supported methods and properties
+@property (nonatomic, readonly) BOOL isCallable;
+
+/// For internal use
+@property (nonatomic) id tag;
+
+- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object;
+@end
+
+// Even if a property is readonly, it still may be editable
+// via a setter. Checking isEditable will not reflect that
+// unless the property was initialized with a class.
+@interface FLEXProperty (UIKitHelpers) <FLEXRuntimeMetadata> @end
+@interface FLEXIvar (UIKitHelpers) <FLEXRuntimeMetadata> @end
+@interface FLEXMethodBase (UIKitHelpers) <FLEXRuntimeMetadata> @end
+@interface FLEXMethod (UIKitHelpers) <FLEXRuntimeMetadata> @end

+ 135 - 0
Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.m

@@ -0,0 +1,135 @@
+//
+//  FLEXRuntime+UIKitHelpers.m
+//  FLEX
+//
+//  Created by Tanner Bennett on 12/16/19.
+//  Copyright © 2019 Flipboard. All rights reserved.
+//
+
+#import "FLEXRuntime+UIKitHelpers.h"
+#import "FLEXRuntimeUtility.h"
+#import "FLEXPropertyAttributes.h"
+#import "FLEXArgumentInputViewFactory.h"
+
+@implementation FLEXProperty (UIKitHelpers)
+
+- (BOOL)isEditable {
+    if (self.attributes.isReadOnly) {
+        return self.likelySetterExists;
+    }
+    
+    const FLEXTypeEncoding *typeEncoding = self.attributes.typeEncoding.UTF8String;
+    return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
+}
+
+- (BOOL)isCallable {
+    return YES;
+}
+
+- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
+    if (object_isClass(object) && !self.isClassProperty) {
+        return UITableViewCellAccessoryNone;
+    }
+
+    // We use .tag to store the cached value of .isEditable that is
+    // initialized by FLEXObjectExplorer in -reloadMetada
+    if ([self getPotentiallyUnboxedValue:object]) {
+        if (self.tag) {
+            // Editable non-nil value, both
+            return UITableViewCellAccessoryDetailDisclosureButton;
+        } else {
+            // Uneditable non-nil value, chevron only
+            return UITableViewCellAccessoryDisclosureIndicator;
+        }
+    } else {
+        if (self.tag) {
+            // Editable nil value, just (i)
+            return UITableViewCellAccessoryDetailButton;
+        } else {
+            // Non-editable nil value, neither
+            return UITableViewCellAccessoryNone;
+        }
+    }
+}
+
+@end
+
+
+@implementation FLEXIvar (UIKitHelpers)
+
+- (BOOL)isEditable {
+    const FLEXTypeEncoding *typeEncoding = self.typeEncoding.UTF8String;
+    return [FLEXArgumentInputViewFactory canEditFieldWithTypeEncoding:typeEncoding currentValue:nil];
+}
+
+- (BOOL)isCallable {
+    return NO;
+}
+
+- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
+    if (object_isClass(object)) {
+        return UITableViewCellAccessoryNone;
+    }
+
+    // Could use .isEditable here, but we use .tag for speed since it is cached
+    if ([self getPotentiallyUnboxedValue:object]) {
+        if (self.tag) {
+            // Editable non-nil value, both
+            return UITableViewCellAccessoryDetailDisclosureButton;
+        } else {
+            // Uneditable non-nil value, chevron only
+            return UITableViewCellAccessoryDisclosureIndicator;
+        }
+    } else {
+        if (self.tag) {
+            // Editable nil value, just (i)
+            return UITableViewCellAccessoryDetailButton;
+        } else {
+            // Non-editable nil value, neither
+            return UITableViewCellAccessoryNone;
+        }
+    }
+}
+
+@end
+
+
+@implementation FLEXMethodBase (UIKitHelpers)
+
+- (BOOL)isEditable {
+    return NO;
+}
+
+- (BOOL)isCallable {
+    return NO;
+}
+
+- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
+    // We shouldn't be using any FLEXMethodBase objects for this
+    @throw NSInternalInconsistencyException;
+    return UITableViewCellAccessoryNone;
+}
+
+@end
+
+@implementation FLEXMethod (UIKitHelpers)
+
+- (BOOL)isCallable {
+    return self.signature != nil;
+}
+
+- (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
+    if (self.isInstanceMethod) {
+        if (object_isClass(object)) {
+            // Instance method from class, can't call
+            return UITableViewCellAccessoryNone;
+        } else {
+            // Instance method from instance, can call
+            return UITableViewCellAccessoryDisclosureIndicator;
+        }
+    } else {
+        return UITableViewCellAccessoryDisclosureIndicator;
+    }
+}
+
+@end

+ 18 - 0
Classes/Utility/Categories/NSDictionary+ObjcRuntime.h

@@ -0,0 +1,18 @@
+//
+//  NSDictionary+ObjcRuntime.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/5/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSDictionary (ObjcRuntime)
+
+/// \c kFLEXPropertyAttributeKeyTypeEncoding is the only required key.
+/// Keys representing a boolean value should have a value of \c @YES instead of an empty string.
+- (NSString *)propertyAttributesString;
+
+@end

+ 89 - 0
Classes/Utility/Categories/NSDictionary+ObjcRuntime.m

@@ -0,0 +1,89 @@
+//
+//  NSDictionary+ObjcRuntime.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/5/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "NSDictionary+ObjcRuntime.h"
+#import "FLEXRuntimeUtility.h"
+
+@implementation NSDictionary (ObjcRuntime)
+
+/// See this link on how to construct a proper attributes string:
+/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
+- (NSString *)propertyAttributesString {
+    if (!self[kFLEXPropertyAttributeKeyTypeEncoding]) return nil;
+    
+    NSMutableString *attributes = [NSMutableString string];
+    [attributes appendFormat:@"T%@,", self[kFLEXPropertyAttributeKeyTypeEncoding]];
+    
+    for (NSString *attribute in self.allKeys) {
+        FLEXPropertyAttribute c = (FLEXPropertyAttribute)[attribute characterAtIndex:0];
+        switch (c) {
+            case FLEXPropertyAttributeTypeEncoding:
+                break;
+            case FLEXPropertyAttributeBackingIvarName:
+                [attributes appendFormat:@"%@%@,",
+                    kFLEXPropertyAttributeKeyBackingIvarName,
+                    self[kFLEXPropertyAttributeKeyBackingIvarName]
+                ];
+                break;
+            case FLEXPropertyAttributeCopy:
+                if ([self[kFLEXPropertyAttributeKeyCopy] boolValue])
+                [attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyCopy];
+                break;
+            case FLEXPropertyAttributeCustomGetter:
+                [attributes appendFormat:@"%@%@,",
+                    kFLEXPropertyAttributeKeyCustomGetter,
+                    self[kFLEXPropertyAttributeKeyCustomGetter]
+                ];
+                break;
+            case FLEXPropertyAttributeCustomSetter:
+                [attributes appendFormat:@"%@%@,",
+                    kFLEXPropertyAttributeKeyCustomSetter,
+                    self[kFLEXPropertyAttributeKeyCustomSetter]
+                ];
+                break;
+            case FLEXPropertyAttributeDynamic:
+                if ([self[kFLEXPropertyAttributeKeyDynamic] boolValue])
+                [attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyDynamic];
+                break;
+            case FLEXPropertyAttributeGarbageCollectible:
+                [attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyGarbageCollectable];
+                break;
+            case FLEXPropertyAttributeNonAtomic:
+                if ([self[kFLEXPropertyAttributeKeyNonAtomic] boolValue])
+                [attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyNonAtomic];
+                break;
+            case FLEXPropertyAttributeOldTypeEncoding:
+                [attributes appendFormat:@"%@%@,",
+                    kFLEXPropertyAttributeKeyOldStyleTypeEncoding,
+                    self[kFLEXPropertyAttributeKeyOldStyleTypeEncoding]
+                ];
+                break;
+            case FLEXPropertyAttributeReadOnly:
+                if ([self[kFLEXPropertyAttributeKeyReadOnly] boolValue])
+                [attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyReadOnly];
+                break;
+            case FLEXPropertyAttributeRetain:
+                if ([self[kFLEXPropertyAttributeKeyRetain] boolValue])
+                [attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyRetain];
+                break;
+            case FLEXPropertyAttributeWeak:
+                if ([self[kFLEXPropertyAttributeKeyWeak] boolValue])
+                [attributes appendFormat:@"%@,", kFLEXPropertyAttributeKeyWeak];
+                break;
+            default:
+                return nil;
+                break;
+        }
+    }
+    
+    [attributes deleteCharactersInRange:NSMakeRange(attributes.length-1, 1)];
+    return attributes.copy;
+}
+
+@end

+ 206 - 0
Classes/Utility/Categories/NSObject+Reflection.h

@@ -0,0 +1,206 @@
+//
+//  NSObject+Reflection.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <objc/runtime.h>
+@class FLEXMirror, FLEXMethod, FLEXIvar, FLEXProperty, FLEXMethodBase, FLEXPropertyAttributes;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Returns the type encoding string given the encoding for the return type and parameters, if any.
+/// @discussion Example usage for a \c void returning method which takes
+/// an \c int: @code FLEXTypeEncoding(@encode(void), @encode(int));
+/// @param returnType The encoded return type. \c void for exmaple would be \c @encode(void).
+/// @param count The number of parameters in this type encoding string.
+/// @return The type encoding string, or \c nil if \e returnType is \c NULL.
+extern NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...);
+
+#pragma mark Reflection
+@interface NSObject (Reflection)
+
+@property (nonatomic, readonly       ) FLEXMirror *flex_reflection;
+@property (nonatomic, readonly, class) FLEXMirror *flex_reflection;
+
+/// @return Every subclass of the receiving class, including the receiver itself.
+@property (nonatomic, readonly, class) NSArray<Class> *flex_allSubclasses;
+
+/// @return The \c Class object for the metaclass of the recieving class, or \c Nil if the class is Nil or not registered.
+@property (nonatomic, readonly, class) Class flex_metaclass;
+/// @return The size in bytes of instances of the recieving class, or \c 0 if \e cls is \c Nil.
+@property (nonatomic, readonly, class) size_t flex_instanceSize;
+
+/// Changes the class of an object instance.
+/// @return The previous value of the objects \c class, or \c Nil if the object is \c nil.
+- (Class)flex_setClass:(Class)cls;
+/// Sets the recieving class's superclass. "You should not use this method" — Apple.
+/// @return The old superclass.
++ (Class)flex_setSuperclass:(Class)superclass;
+
+@end
+
+
+#pragma mark Methods
+@interface NSObject (Methods)
+
+/// All instance and class methods specific to the recieving class.
+/// @discussion This method will only retrieve methods specific to the recieving class.
+/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
+/// @return An array of \c FLEXMethod objects.
+@property (nonatomic, readonly, class) NSArray<FLEXMethod *> *flex_allMethods;
+/// All instance methods specific to the recieving class.
+/// @discussion This method will only retrieve methods specific to the recieving class.
+/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
+/// @return An array of \c FLEXMethod objects.
+@property (nonatomic, readonly, class) NSArray<FLEXMethod *> *flex_allInstanceMethods;
+/// All class methods specific to the recieving class.
+/// @discussion This method will only retrieve methods specific to the recieving class.
+/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
+/// @return An array of \c FLEXMethod objects.
+@property (nonatomic, readonly, class) NSArray<FLEXMethod *> *flex_allClassMethods;
+
+/// Retrieves the class's instance method with the given name.
+/// @return An initialized \c FLEXMethod object, or \c nil if the method wasn't found.
++ (FLEXMethod *)flex_methodNamed:(NSString *)name;
+
+/// Retrieves the class's class method with the given name.
+/// @return An initialized \c FLEXMethod object, or \c nil if the method wasn't found.
++ (FLEXMethod *)flex_classMethodNamed:(NSString *)name;
+
+/// Adds a new method to the recieving class with a given name and implementation.
+/// @discussion This method will add an override of a superclass's implementation,
+/// but will not replace an existing implementation in the class.
+/// To change an existing implementation, use \c replaceImplementationOfMethod:with:.
+///
+/// Type encodings start with the return type and end with the parameter types in order.
+/// The type encoding for \c NSArray's \c count property getter looks like this:
+/// @code [NSString stringWithFormat:@"%s%s%s%s", @encode(void), @encode(id), @encode(SEL), @encode(NSUInteger)] @endcode
+/// Using the \c FLEXTypeEncoding function for the same method looks like this:
+/// @code FLEXTypeEncodingString(@encode(void), 1, @encode(NSUInteger)) @endcode
+/// @param typeEncoding The type encoding string. Consider using the \c FLEXTypeEncodingString() function.
+/// @param instanceMethod NO to add the method to the class itself or YES to add it as an instance method.
+/// @return YES if the method was added successfully, \c NO otherwise
+/// (for example, the class already contains a method implementation with that name).
++ (BOOL)addMethod:(SEL)selector
+     typeEncoding:(NSString *)typeEncoding
+   implementation:(IMP)implementaiton
+      toInstances:(BOOL)instanceMethod;
+
+/// Replaces the implementation of a method in the recieving class.
+/// @param instanceMethod YES to replace the instance method, NO to replace the class method.
+/// @note This function behaves in two different ways:
+///
+/// - If the method does not yet exist in the recieving class, it is added as if
+/// \c addMethod:typeEncoding:implementation were called.
+///
+/// - If the method does exist, its \c IMP is replaced.
+/// @return The previous \c IMP of \e method.
++ (IMP)replaceImplementationOfMethod:(FLEXMethodBase *)method with:(IMP)implementation useInstance:(BOOL)instanceMethod;
+/// Swaps the implementations of the given methods.
+/// @discussion If one or neither of the given methods exist in the recieving class,
+/// they are added to the class with their implementations swapped as if each method did exist.
+/// This method will not fail if each \c FLEXSimpleMethod contains a valid selector.
+/// @param instanceMethod YES to swizzle the instance method, NO to swizzle the class method.
++ (void)swizzle:(FLEXMethodBase *)original with:(FLEXMethodBase *)other onInstance:(BOOL)instanceMethod;
+/// Swaps the implementations of the given methods.
+/// @param instanceMethod YES to swizzle the instance method, NO to swizzle the class method.
+/// @return \c YES if successful, and \c NO if selectors could not be retrieved from the given strings.
++ (BOOL)swizzleByName:(NSString *)original with:(NSString *)other onInstance:(BOOL)instanceMethod;
+/// Swaps the implementations of methods corresponding to the given selectors.
++ (void)swizzleBySelector:(SEL)original with:(SEL)other onInstance:(BOOL)instanceMethod;
+
+@end
+
+
+#pragma mark Properties
+@interface NSObject (Ivars)
+
+/// All of the instance variables specific to the recieving class.
+/// @discussion This method will only retrieve instance varibles specific to the recieving class.
+/// To retrieve instance variables on a parent class, simply call \c [[self superclass] allIvars].
+/// @return An array of \c FLEXIvar objects.
+@property (nonatomic, readonly, class) NSArray<FLEXIvar *> *flex_allIvars;
+
+/// Retrieves an instance variable with the corresponding name.
+/// @return An initialized \c FLEXIvar object, or \c nil if the Ivar wasn't found.
++ (FLEXIvar *)flex_ivarNamed:(NSString *)name;
+
+/// @return The address of the given ivar in the recieving object in memory,
+/// or \c NULL if it could not be found.
+- (void *)flex_getIvarAddress:(FLEXIvar *)ivar;
+/// @return The address of the given ivar in the recieving object in memory,
+/// or \c NULL if it could not be found.
+- (void *)flex_getIvarAddressByName:(NSString *)name;
+/// @discussion This method faster than creating an \c FLEXIvar and calling
+/// \c -getIvarAddress: if you already have an \c Ivar on hand
+/// @return The address of the given ivar in the recieving object in memory,
+/// or \c NULL if it could not be found\.
+- (void *)flex_getObjcIvarAddress:(Ivar)ivar;
+
+/// Sets the value of the given instance variable on the recieving object.
+/// @discussion Use only when the target instance variable is an object.
+- (void)flex_setIvar:(FLEXIvar *)ivar object:(id)value;
+/// Sets the value of the given instance variable on the recieving object.
+/// @discussion Use only when the target instance variable is an object.
+/// @return \c YES if successful, or \c NO if the instance variable could not be found.
+- (BOOL)flex_setIvarByName:(NSString *)name object:(id)value;
+/// @discussion Use only when the target instance variable is an object.
+/// This method is faster than creating an \c FLEXIvar and calling
+/// \c -setIvar: if you already have an \c Ivar on hand.
+- (void)flex_setObjcIvar:(Ivar)ivar object:(id)value;
+
+/// Sets the value of the given instance variable on the recieving object to the
+/// \e size number of bytes of data at \e value.
+/// @discussion Use one of the other methods if you can help it.
+- (void)flex_setIvar:(FLEXIvar *)ivar value:(void *)value size:(size_t)size;
+/// Sets the value of the given instance variable on the recieving object to the
+/// \e size number of bytes of data at \e value.
+/// @discussion Use one of the other methods if you can help it
+/// @return \c YES if successful, or \c NO if the instance variable could not be found.
+- (BOOL)flex_setIvarByName:(NSString *)name value:(void *)value size:(size_t)size;
+/// Sets the value of the given instance variable on the recieving object to the
+/// \e size number of bytes of data at \e value.
+/// @discussion This is faster than creating an \c FLEXIvar and calling
+/// \c -setIvar:value:size if you already have an \c Ivar on hand.
+- (void)flex_setObjcIvar:(Ivar)ivar value:(void *)value size:(size_t)size;
+
+@end
+
+#pragma mark Properties
+@interface NSObject (Properties)
+
+/// All instance and class properties specific to the recieving class.
+/// @discussion This method will only retrieve properties specific to the recieving class.
+/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
+/// @return An array of \c FLEXProperty objects.
+@property (nonatomic, readonly, class) NSArray<FLEXProperty *> *flex_allProperties;
+/// All instance properties specific to the recieving class.
+/// @discussion This method will only retrieve properties specific to the recieving class.
+/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
+/// @return An array of \c FLEXProperty objects.
+@property (nonatomic, readonly, class) NSArray<FLEXProperty *> *flex_allInstanceProperties;
+/// All class properties specific to the recieving class.
+/// @discussion This method will only retrieve properties specific to the recieving class.
+/// To retrieve instance variables on a parent class, simply call this on \c [self superclass].
+/// @return An array of \c FLEXProperty objects.
+@property (nonatomic, readonly, class) NSArray<FLEXProperty *> *flex_allClassProperties;
+
+/// Retrieves the class's property with the given name.
+/// @return An initialized \c FLEXProperty object, or \c nil if the property wasn't found.
++ (FLEXProperty *)flex_propertyNamed:(NSString *)name;
+/// @return An initialized \c FLEXProperty object, or \c nil if the property wasn't found.
++ (FLEXProperty *)flex_classPropertyNamed:(NSString *)name;
+
+/// Replaces the given property on the recieving class.
++ (void)flex_replaceProperty:(FLEXProperty *)property;
+/// Replaces the given property on the recieving class. Useful for changing a property's attributes.
++ (void)flex_replaceProperty:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 350 - 0
Classes/Utility/Categories/NSObject+Reflection.m

@@ -0,0 +1,350 @@
+//
+//  NSObject+Reflection.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "NSObject+Reflection.h"
+#import "FLEXMirror.h"
+#import "FLEXProperty.h"
+#import "FLEXMethod.h"
+#import "FLEXIvar.h"
+#import "FLEXPropertyAttributes.h"
+
+
+NSString * FLEXTypeEncodingString(const char *returnType, NSUInteger count, ...) {
+    if (returnType == NULL) return nil;
+    
+    NSMutableString *encoding = [NSMutableString string];
+    [encoding appendFormat:@"%s%s%s", returnType, @encode(id), @encode(SEL)];
+    
+    va_list args;
+    va_start(args, count);
+    char *type = va_arg(args, char *);
+    for (NSUInteger i = 0; i < count; i++, type = va_arg(args, char *)) {
+        [encoding appendFormat:@"%s", type];
+    }
+    va_end(args);
+    
+    return encoding.copy;
+}
+
+#pragma mark Reflection
+
+@implementation NSObject (Reflection)
+
++ (FLEXMirror *)flex_reflection {
+    return [FLEXMirror reflect:self];
+}
+
+- (FLEXMirror *)flex_reflection {
+    return [FLEXMirror reflect:self];
+}
+
+/// Code borrowed from MAObjCRuntime by Mike Ash
++ (NSArray *)flex_allSubclasses {
+    Class *buffer = NULL;
+    
+    int count, size;
+    do {
+        count  = objc_getClassList(NULL, 0);
+        buffer = (Class *)realloc(buffer, count * sizeof(*buffer));
+        size   = objc_getClassList(buffer, count);
+    } while (size != count);
+    
+    NSMutableArray *array = [NSMutableArray array];
+    for (int i = 0; i < count; i++) {
+        Class candidate = buffer[i];
+        Class superclass = candidate;
+        while (superclass) {
+            if (superclass == self) {
+                [array addObject:candidate];
+                break;
+            }
+            superclass = class_getSuperclass(superclass);
+        }
+    }
+    
+    free(buffer);
+    return array;
+}
+
+- (Class)flex_setClass:(Class)cls {
+    return object_setClass(self, cls);
+}
+
++ (Class)flex_metaclass {
+    return objc_getMetaClass(NSStringFromClass(self.class).UTF8String);
+}
+
++ (size_t)flex_instanceSize {
+    return class_getInstanceSize(self.class);
+}
+
+#ifdef __clang__
+#pragma clang diagnostic push
+#endif
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
++ (Class)flex_setSuperclass:(Class)superclass {
+    return class_setSuperclass(self, superclass);
+}
+#ifdef __clang__
+#pragma clang diagnostic pop
+#endif
+
+@end
+
+
+#pragma mark Methods
+
+@implementation NSObject (Methods)
+
++ (NSArray<FLEXMethod *> *)flex_allMethods {
+    NSMutableArray *instanceMethods = (id)self.flex_allInstanceMethods;
+    [instanceMethods addObjectsFromArray:self.flex_allClassMethods];
+    return instanceMethods;
+}
+
++ (NSArray<FLEXMethod *> *)flex_allInstanceMethods {
+    unsigned int mcount;
+    Method *objcmethods = class_copyMethodList([self class], &mcount);
+
+    NSMutableArray *methods = [NSMutableArray array];
+    for (int i = 0; i < mcount; i++) {
+        FLEXMethod *m = [FLEXMethod method:objcmethods[i] isInstanceMethod:YES];
+        if (m) {
+            [methods addObject:m];
+        }
+    }
+
+    free(objcmethods);
+    return methods;
+}
+
++ (NSArray<FLEXMethod *> *)flex_allClassMethods {
+    unsigned int mcount;
+    Method *objcmethods = class_copyMethodList(self.flex_metaclass, &mcount);
+
+    NSMutableArray *methods = [NSMutableArray array];
+    for (int i = 0; i < mcount; i++) {
+        FLEXMethod *m = [FLEXMethod method:objcmethods[i] isInstanceMethod:NO];
+        if (m) {
+            [methods addObject:m];
+        }
+    }
+
+    free(objcmethods);
+    return methods;
+}
+
++ (FLEXMethod *)flex_methodNamed:(NSString *)name {
+    Method m = class_getInstanceMethod([self class], NSSelectorFromString(name));
+    if (m == NULL) {
+        return nil;
+    }
+
+    return [FLEXMethod method:m isInstanceMethod:YES];
+}
+
++ (FLEXMethod *)flex_classMethodNamed:(NSString *)name {
+    Method m = class_getClassMethod([self class], NSSelectorFromString(name));
+    if (m == NULL) {
+        return nil;
+    }
+
+    return [FLEXMethod method:m isInstanceMethod:NO];
+}
+
++ (BOOL)addMethod:(SEL)selector
+     typeEncoding:(NSString *)typeEncoding
+   implementation:(IMP)implementaiton
+      toInstances:(BOOL)instance {
+    return class_addMethod(instance ? self.class : self.flex_metaclass, selector, implementaiton, typeEncoding.UTF8String);
+}
+
++ (IMP)replaceImplementationOfMethod:(FLEXMethodBase *)method with:(IMP)implementation useInstance:(BOOL)instance {
+    return class_replaceMethod(instance ? self.class : self.flex_metaclass, method.selector, implementation, method.typeEncoding.UTF8String);
+}
+
++ (void)swizzle:(FLEXMethodBase *)original with:(FLEXMethodBase *)other onInstance:(BOOL)instance {
+    [self swizzleBySelector:original.selector with:other.selector onInstance:instance];
+}
+
++ (BOOL)swizzleByName:(NSString *)original with:(NSString *)other onInstance:(BOOL)instance {
+    SEL originalMethod = NSSelectorFromString(original);
+    SEL newMethod      = NSSelectorFromString(other);
+    if (originalMethod == 0 || newMethod == 0) {
+        return NO;
+    }
+
+    [self swizzleBySelector:originalMethod with:newMethod onInstance:instance];
+    return YES;
+}
+
++ (void)swizzleBySelector:(SEL)original with:(SEL)other onInstance:(BOOL)instance {
+    Class cls = instance ? self.class : self.flex_metaclass;
+    Method originalMethod = class_getInstanceMethod(cls, original);
+    Method newMethod = class_getInstanceMethod(cls, other);
+    if (class_addMethod(cls, original, method_getImplementation(newMethod), method_getTypeEncoding(newMethod))) {
+        class_replaceMethod(cls, other, method_getImplementation(originalMethod), method_getTypeEncoding(originalMethod));
+    } else {
+        method_exchangeImplementations(originalMethod, newMethod);
+    }
+}
+
+@end
+
+
+#pragma mark Ivars
+
+@implementation NSObject (Ivars)
+
++ (NSArray<FLEXIvar *> *)flex_allIvars {
+    unsigned int ivcount;
+    Ivar *objcivars = class_copyIvarList([self class], &ivcount);
+    
+    NSMutableArray *ivars = [NSMutableArray array];
+    for (int i = 0; i < ivcount; i++) {
+        [ivars addObject:[FLEXIvar ivar:objcivars[i]]];
+    }
+
+    free(objcivars);
+    return ivars;
+}
+
++ (FLEXIvar *)flex_ivarNamed:(NSString *)name {
+    Ivar i = class_getInstanceVariable([self class], name.UTF8String);
+    if (i == NULL) {
+        return nil;
+    }
+
+    return [FLEXIvar ivar:i];
+}
+
+#pragma mark Get address
+- (void *)flex_getIvarAddress:(FLEXIvar *)ivar {
+    return (uint8_t *)(__bridge void *)self + ivar.offset;
+}
+
+- (void *)flex_getObjcIvarAddress:(Ivar)ivar {
+    return (uint8_t *)(__bridge void *)self + ivar_getOffset(ivar);
+}
+
+- (void *)flex_getIvarAddressByName:(NSString *)name {
+    Ivar ivar = class_getInstanceVariable(self.class, name.UTF8String);
+    if (!ivar) return 0;
+    
+    return (uint8_t *)(__bridge void *)self + ivar_getOffset(ivar);
+}
+
+#pragma mark Set ivar object
+- (void)flex_setIvar:(FLEXIvar *)ivar object:(id)value {
+    object_setIvar(self, ivar.objc_ivar, value);
+}
+
+- (BOOL)flex_setIvarByName:(NSString *)name object:(id)value {
+    Ivar ivar = class_getInstanceVariable(self.class, name.UTF8String);
+    if (!ivar) return NO;
+    
+    object_setIvar(self, ivar, value);
+    return YES;
+}
+
+- (void)flex_setObjcIvar:(Ivar)ivar object:(id)value {
+    object_setIvar(self, ivar, value);
+}
+
+#pragma mark Set ivar value
+- (void)flex_setIvar:(FLEXIvar *)ivar value:(void *)value size:(size_t)size {
+    void *address = [self flex_getIvarAddress:ivar];
+    memcpy(address, value, size);
+}
+
+- (BOOL)flex_setIvarByName:(NSString *)name value:(void *)value size:(size_t)size {
+    Ivar ivar = class_getInstanceVariable(self.class, name.UTF8String);
+    if (!ivar) return NO;
+    
+    [self flex_setObjcIvar:ivar value:value size:size];
+    return YES;
+}
+
+- (void)flex_setObjcIvar:(Ivar)ivar value:(void *)value size:(size_t)size {
+    void *address = [self flex_getObjcIvarAddress:ivar];
+    memcpy(address, value, size);
+}
+
+@end
+
+
+#pragma mark Properties
+
+@implementation NSObject (Properties)
+
++ (NSArray<FLEXProperty *> *)flex_allProperties {
+    NSMutableArray *instanceProperties = (id)self.flex_allInstanceProperties;
+    [instanceProperties addObjectsFromArray:self.flex_allClassProperties];
+    return instanceProperties;
+}
+
++ (NSArray<FLEXProperty *> *)flex_allInstanceProperties {
+    unsigned int pcount;
+    objc_property_t *objcproperties = class_copyPropertyList(self, &pcount);
+    
+    NSMutableArray *properties = [NSMutableArray array];
+    for (int i = 0; i < pcount; i++) {
+        [properties addObject:[FLEXProperty property:objcproperties[i] onClass:self]];
+    }
+
+    free(objcproperties);
+    return properties;
+}
+
++ (NSArray<FLEXProperty *> *)flex_allClassProperties {
+    Class metaclass = self.flex_metaclass;
+    unsigned int pcount;
+    objc_property_t *objcproperties = class_copyPropertyList(metaclass, &pcount);
+
+    NSMutableArray *properties = [NSMutableArray array];
+    for (int i = 0; i < pcount; i++) {
+        [properties addObject:[FLEXProperty property:objcproperties[i] onClass:metaclass]];
+    }
+
+    free(objcproperties);
+    return properties;
+}
+
++ (FLEXProperty *)flex_propertyNamed:(NSString *)name {
+    objc_property_t p = class_getProperty([self class], name.UTF8String);
+    if (p == NULL) {
+        return nil;
+    }
+
+    return [FLEXProperty property:p onClass:self];
+}
+
++ (FLEXProperty *)flex_classPropertyNamed:(NSString *)name {
+    objc_property_t p = class_getProperty(object_getClass(self), name.UTF8String);
+    if (p == NULL) {
+        return nil;
+    }
+
+    return [FLEXProperty property:p onClass:object_getClass(self)];
+}
+
++ (void)flex_replaceProperty:(FLEXProperty *)property {
+    [self flex_replaceProperty:property.name attributes:property.attributes];
+}
+
++ (void)flex_replaceProperty:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
+    unsigned int count;
+    objc_property_attribute_t *objc_attributes = [attributes copyAttributesList:&count];
+    class_replaceProperty([self class], name.UTF8String, objc_attributes, count);
+    free(objc_attributes);
+}
+
+@end
+
+

+ 20 - 0
Classes/Utility/Categories/NSString+ObjcRuntime.h

@@ -0,0 +1,20 @@
+//
+//  NSString+ObjcRuntime.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/1/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@interface NSString (Utilities)
+
+/// A dictionary of property attributes if the receiver is a valid property attributes string.
+/// Values are either a string or \c @YES. Boolean attributes which are false will not be
+/// present in the dictionary. See this link on how to construct a proper attributes string:
+/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
+- (NSDictionary *)propertyAttributes;
+
+@end

+ 74 - 0
Classes/Utility/Categories/NSString+ObjcRuntime.m

@@ -0,0 +1,74 @@
+//
+//  NSString+ObjcRuntime.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/1/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "NSString+ObjcRuntime.h"
+#import "FLEXRuntimeUtility.h"
+
+@implementation NSString (Utilities)
+
+- (NSString *)stringbyDeletingCharacterAtIndex:(NSUInteger)idx {
+    NSMutableString *string = self.mutableCopy;
+    [string replaceCharactersInRange:NSMakeRange(idx, 1) withString:@""];
+    return string;
+}
+
+/// See this link on how to construct a proper attributes string:
+/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
+- (NSDictionary *)propertyAttributes {
+    if (!self.length) return nil;
+    
+    NSMutableDictionary *attributes = [NSMutableDictionary dictionary];
+    
+    NSArray *components = [self componentsSeparatedByString:@","];
+    for (NSString *attribute in components) {
+        FLEXPropertyAttribute c = (FLEXPropertyAttribute)[attribute characterAtIndex:0];
+        switch (c) {
+            case FLEXPropertyAttributeTypeEncoding:
+                attributes[kFLEXPropertyAttributeKeyTypeEncoding] = [attribute stringbyDeletingCharacterAtIndex:0];
+                break;
+            case FLEXPropertyAttributeBackingIvarName:
+                attributes[kFLEXPropertyAttributeKeyBackingIvarName] = [attribute stringbyDeletingCharacterAtIndex:0];
+                break;
+            case FLEXPropertyAttributeCopy:
+                attributes[kFLEXPropertyAttributeKeyCopy] = @YES;
+                break;
+            case FLEXPropertyAttributeCustomGetter:
+                attributes[kFLEXPropertyAttributeKeyCustomGetter] = [attribute stringbyDeletingCharacterAtIndex:0];
+                break;
+            case FLEXPropertyAttributeCustomSetter:
+                attributes[kFLEXPropertyAttributeKeyCustomSetter] = [attribute stringbyDeletingCharacterAtIndex:0];
+                break;
+            case FLEXPropertyAttributeDynamic:
+                attributes[kFLEXPropertyAttributeKeyDynamic] = @YES;
+                break;
+            case FLEXPropertyAttributeGarbageCollectible:
+                attributes[kFLEXPropertyAttributeKeyGarbageCollectable] = @YES;
+                break;
+            case FLEXPropertyAttributeNonAtomic:
+                attributes[kFLEXPropertyAttributeKeyNonAtomic] = @YES;
+                break;
+            case FLEXPropertyAttributeOldTypeEncoding:
+                attributes[kFLEXPropertyAttributeKeyOldStyleTypeEncoding] = [attribute stringbyDeletingCharacterAtIndex:0];
+                break;
+            case FLEXPropertyAttributeReadOnly:
+                attributes[kFLEXPropertyAttributeKeyReadOnly] = @YES;
+                break;
+            case FLEXPropertyAttributeRetain:
+                attributes[kFLEXPropertyAttributeKeyRetain] = @YES;
+                break;
+            case FLEXPropertyAttributeWeak:
+                attributes[kFLEXPropertyAttributeKeyWeak] = @YES;
+                break;
+        }
+    }
+
+    return attributes;
+}
+
+@end

+ 1 - 4
Classes/Utility/FLEXUtility.h

@@ -12,6 +12,7 @@
 #import <UIKit/UIKit.h>
 #import <objc/runtime.h>
 #import "FLEXAlert.h"
+#import "NSArray+Functional.h"
 
 #define FLEXFloor(x) (floor(UIScreen.mainScreen.scale * (x)) / UIScreen.mainScreen.scale)
 
@@ -53,10 +54,6 @@
 + (BOOL)isValidJSONData:(NSData *)data;
 + (NSData *)inflatedDataFromCompressedData:(NSData *)compressedData;
 
-/// Actually more like flatmap, but it seems like the objc way to allow returning nil to omit objects.
-/// So, return nil from the block to omit objects, and return an object to include it in the new array.
-+ (NSArray *)map:(NSArray *)array block:(id(^)(id obj, NSUInteger idx))mapFunc;
-
 + (NSArray<UIWindow *> *)allWindows;
 
 // Swizzling utilities

+ 7 - 42
Classes/Utility/FLEXUtility.m

@@ -123,34 +123,6 @@
     return [FLEXUtility applicationImageName].lastPathComponent;
 }
 
-+ (NSString *)safeDescriptionForObject:(id)object
-{
-    // Don't assume that we have an NSObject subclass.
-    // Check to make sure the object responds to the description methods.
-    NSString *description = nil;
-    if ([object respondsToSelector:@selector(debugDescription)]) {
-        description = [object debugDescription];
-    } else if ([object respondsToSelector:@selector(description)]) {
-        description = [object description];
-    }
-    return description;
-}
-
-+ (NSString *)safeDebugDescriptionForObject:(id)object
-{
-    NSString *description = [self safeDescriptionForObject:object];
-    if (!description) {
-        NSString *cls = NSStringFromClass(object_getClass(object));
-        if (object_isClass(object)) {
-            description = [cls stringByAppendingString:@" class (no description)"];
-       } else {
-           description = [cls stringByAppendingString:@" instance (no description)"];
-       }
-    }
-
-    return description;
-}
-
 + (NSString *)addressOfObject:(id)object
 {
     return [NSString stringWithFormat:@"%p", object];
@@ -163,7 +135,13 @@
 
 + (UIFont *)defaultTableViewCellLabelFont
 {
-    return [self defaultFontOfSize:12.0];
+    static UIFont *defaultTableViewCellLabelFont = nil;
+    static dispatch_once_t onceToken;
+    dispatch_once(&onceToken, ^{
+        defaultTableViewCellLabelFont = [self defaultFontOfSize:12.0];
+    });
+
+    return defaultTableViewCellLabelFont;
 }
 
 + (NSString *)stringByEscapingHTMLEntitiesInString:(NSString *)originalString
@@ -357,19 +335,6 @@
     return inflatedData;
 }
 
-+ (NSArray *)map:(NSArray *)array block:(id(^)(id obj, NSUInteger idx))mapFunc
-{
-    NSMutableArray *map = [NSMutableArray new];
-    [array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
-        id ret = mapFunc(obj, idx);
-        if (ret) {
-            [map addObject:ret];
-        }
-    }];
-
-    return map;
-}
-
 + (NSArray<UIWindow *> *)allWindows
 {
     BOOL includeInternalWindows = YES;

+ 80 - 0
Classes/Utility/Runtime/FLEXClassBuilder.h

@@ -0,0 +1,80 @@
+//
+//  FLEXClassBuilder.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/3/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@class FLEXIvarBuilder, FLEXMethodBase, FLEXProperty, FLEXProtocol;
+
+
+#pragma mark FLEXClassBuilder
+@interface FLEXClassBuilder : NSObject
+
+@property (nonatomic, readonly) Class workingClass;
+
+/// Begins constructing a class with the given name.
+///
+/// This new class will implicitly inherits from \c NSObject with \c 0 extra bytes.
+/// Classes created this way must be registered with \c -registerClass before being used.
++ (instancetype)allocateClass:(NSString *)name;
+/// Begins constructing a class with the given name and superclass.
+/// @discussion Calls \c -allocateClass:superclass:extraBytes: with \c 0 extra bytes.
+/// Classes created this way must be registered with \c -registerClass before being used.
++ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass;
+/// Begins constructing a new class object with the given name and superclass.
+/// @discussion Pass \c nil to \e superclass to create a new root class.
+/// Classes created this way must be registered with \c -registerClass before being used.
++ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes;
+/// Begins constructing a new root class object with the given name and \c 0 extra bytes.
+/// @discussion Classes created this way must be registered with \c -registerClass before being used.
++ (instancetype)allocateRootClass:(NSString *)name;
+/// Use this to modify existing classes. @warning You cannot add instance variables to existing classes.
++ (instancetype)builderForClass:(Class)cls;
+
+/// @return Any methods that failed to be added.
+- (NSArray<FLEXMethodBase *> *)addMethods:(NSArray<FLEXMethodBase *> *)methods;
+/// @return Any properties that failed to be added.
+- (NSArray<FLEXProperty *> *)addProperties:(NSArray<FLEXProperty *> *)properties;
+/// @return Any protocols that failed to be added.
+- (NSArray<FLEXProtocol *> *)addProtocols:(NSArray<FLEXProtocol *> *)protocols;
+/// @warning Adding Ivars to existing classes is not supported and will always fail.
+- (NSArray<FLEXIvarBuilder *> *)addIvars:(NSArray<FLEXIvarBuilder *> *)ivars;
+
+/// Finalizes construction of a new class.
+/// @discussion Once a class is registered, instance variables cannot be added.
+/// @note Raises an exception if called on a previously registered class.
+- (Class)registerClass;
+/// Uses \c objc_lookupClass to determine if the working class is registered.
+@property (nonatomic, readonly) BOOL isRegistered;
+
+@end
+
+
+#pragma mark FLEXIvarBuilder
+@interface FLEXIvarBuilder : NSObject
+
+/// Consider using the \c FLEXIvarBuilderWithNameAndType() macro below. 
+/// @param name The name of the Ivar, such as \c \@"_value".
+/// @param size The size of the Ivar. Usually \c sizeof(type). For objects, this is \c sizeof(id).
+/// @param alignment The alignment of the Ivar. Usually \c log2(sizeof(type)).
+/// @param encoding The type encoding of the Ivar. For objects, this is \c \@(\@encode(id)), and for others it is \c \@(\@encode(type)).
++ (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding;
+
+@property (nonatomic, readonly) NSString *name;
+@property (nonatomic, readonly) NSString *encoding;
+@property (nonatomic, readonly) size_t   size;
+@property (nonatomic, readonly) uint8_t  alignment;
+
+@end
+
+
+#define FLEXIvarBuilderWithNameAndType(nameString, type) [FLEXIvarBuilder \
+    name:nameString \
+    size:sizeof(type) \
+    alignment:log2(sizeof(type)) \
+    typeEncoding:@(@encode(type)) \
+]

+ 170 - 0
Classes/Utility/Runtime/FLEXClassBuilder.m

@@ -0,0 +1,170 @@
+//
+//  FLEXClassBuilder.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/3/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+//#import "MirrorKit.h"
+//#import "NSString+ObjcRuntime.h"
+#import "FLEXClassBuilder.h"
+#import "FLEXProperty.h"
+#import "FLEXMethodBase.h"
+#import "FLEXProtocol.h"
+#import <objc/runtime.h>
+
+
+#pragma mark FLEXClassBuilder
+
+@interface FLEXClassBuilder ()
+@property (nonatomic) NSString *name;
+@end
+
+@implementation FLEXClassBuilder
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
+#pragma mark Initializers
++ (instancetype)allocateClass:(NSString *)name {
+    return [self allocateClass:name superclass:NSObject.class];
+}
+
++ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass {
+    return [self allocateClass:name superclass:superclass extraBytes:0];
+}
+
++ (instancetype)allocateClass:(NSString *)name superclass:(Class)superclass extraBytes:(size_t)bytes {
+    NSParameterAssert(name);
+    return [[self alloc] initWithClass:objc_allocateClassPair(superclass, name.UTF8String, bytes)];
+}
+
++ (instancetype)allocateRootClass:(NSString *)name {
+    NSParameterAssert(name);
+    return [[self alloc] initWithClass:objc_allocateClassPair(Nil, name.UTF8String, 0)];
+}
+
++ (instancetype)builderForClass:(Class)cls {
+    return [[self alloc] initWithClass:cls];
+}
+
+- (id)initWithClass:(Class)cls {
+    NSParameterAssert(cls);
+    
+    self = [super init];
+    if (self) {
+        _workingClass = cls;
+        _name = NSStringFromClass(_workingClass);
+    }
+    
+    return self;
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>",
+            NSStringFromClass(self.class), self.name, self.isRegistered];
+}
+
+#pragma mark Building
+- (NSArray *)addMethods:(NSArray *)methods {
+    NSParameterAssert(methods.count);
+    
+    NSMutableArray *failed = [NSMutableArray array];
+    for (FLEXMethodBase *m in methods) {
+        if (!class_addMethod(self.workingClass, m.selector, m.implementation, m.typeEncoding.UTF8String)) {
+            [failed addObject:m];
+        }
+    }
+    
+    return failed;
+}
+
+- (NSArray *)addProperties:(NSArray *)properties {
+    NSParameterAssert(properties.count);
+    
+    NSMutableArray *failed = [NSMutableArray array];
+    for (FLEXProperty *p in properties) {
+        unsigned int pcount;
+        objc_property_attribute_t *attributes = [p copyAttributesList:&pcount];
+        if (!class_addProperty(self.workingClass, p.name.UTF8String, attributes, pcount)) {
+            [failed addObject:p];
+        }
+        free(attributes);
+    }
+    
+    return failed;
+}
+
+- (NSArray *)addProtocols:(NSArray *)protocols {
+    NSParameterAssert(protocols.count);
+    
+    NSMutableArray *failed = [NSMutableArray array];
+    for (FLEXProtocol *p in protocols) {
+        if (!class_addProtocol(self.workingClass, p.objc_protocol)) {
+            [failed addObject:p];
+        }
+    }
+    
+    return failed;
+}
+
+- (NSArray *)addIvars:(NSArray *)ivars {
+    NSParameterAssert(ivars.count);
+    
+    NSMutableArray *failed = [NSMutableArray array];
+    for (FLEXIvarBuilder *ivar in ivars) {
+        if (!class_addIvar(self.workingClass, ivar.name.UTF8String, ivar.size, ivar.alignment, ivar.encoding.UTF8String)) {
+            [failed addObject:ivar];
+        }
+    }
+    
+    return failed;
+}
+
+- (Class)registerClass {
+    if (self.isRegistered) {
+        [NSException raise:NSInternalInconsistencyException format:@"Class is already registered"];
+    }
+    
+    objc_registerClassPair(self.workingClass);
+    return self.workingClass;
+}
+
+- (BOOL)isRegistered {
+    return objc_lookUpClass(self.name.UTF8String) != nil;
+}
+
+@end
+
+
+#pragma mark FLEXIvarBuilder
+
+@implementation FLEXIvarBuilder
+
++ (instancetype)name:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding {
+    return [[self alloc] initWithName:name size:size alignment:alignment typeEncoding:encoding];
+}
+
+- (id)initWithName:(NSString *)name size:(size_t)size alignment:(uint8_t)alignment typeEncoding:(NSString *)encoding {
+    NSParameterAssert(name); NSParameterAssert(encoding);
+    NSParameterAssert(size > 0); NSParameterAssert(alignment > 0);
+    
+    self = [super init];
+    if (self) {
+        _name      = name;
+        _encoding  = encoding;
+        _size      = size;
+        _alignment = alignment;
+    }
+    
+    return self;
+}
+
+@end

+ 42 - 0
Classes/Utility/Runtime/FLEXIvar.h

@@ -0,0 +1,42 @@
+//
+//  FLEXIvar.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXRuntimeUtility.h"
+#import <objc/runtime.h>
+
+
+@interface FLEXIvar : NSObject
+
++ (instancetype)ivar:(Ivar)ivar;
++ (instancetype)named:(NSString *)name onClass:(Class)cls;
+
+/// The underlying \c Ivar data structure.
+@property (nonatomic, readonly) Ivar             objc_ivar;
+
+/// The name of the instance variable.
+@property (nonatomic, readonly) NSString         *name;
+/// The type of the instance variable.
+@property (nonatomic, readonly) FLEXTypeEncoding type;
+/// The type encoding string of the instance variable.
+@property (nonatomic, readonly) NSString         *typeEncoding;
+/// The offset of the instance variable.
+@property (nonatomic, readonly) NSInteger        offset;
+
+/// For internal use
+@property (nonatomic) id tag;
+
+- (id)getValue:(id)target;
+
+/// Calls into -getValue: and passes that value into
+/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
+/// and returns the result
+- (id)getPotentiallyUnboxedValue:(id)target;
+
+@end

+ 85 - 0
Classes/Utility/Runtime/FLEXIvar.m

@@ -0,0 +1,85 @@
+//
+//  FLEXIvar.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXIvar.h"
+#import "FLEXRuntimeUtility.h"
+
+@interface FLEXIvar () {
+    NSString *_flex_description;
+}
+@end
+
+@implementation FLEXIvar
+
+#pragma mark Initializers
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
++ (instancetype)ivar:(Ivar)ivar {
+    return [[self alloc] initWithIvar:ivar];
+}
+
++ (instancetype)named:(NSString *)name onClass:(Class)cls {
+    Ivar ivar = class_getInstanceVariable(cls, name.UTF8String);
+    return [self ivar:ivar];
+}
+
+- (id)initWithIvar:(Ivar)ivar {
+    NSParameterAssert(ivar);
+    
+    self = [super init];
+    if (self) {
+        _objc_ivar = ivar;
+        [self examine];
+    }
+    
+    return self;
+}
+
+#pragma mark Other
+
+- (NSString *)description {
+    if (!_flex_description) {
+        NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.typeEncoding];
+        _flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType];
+    }
+
+    return _flex_description;
+}
+
+- (NSString *)debugDescription {
+    return [NSString stringWithFormat:@"<%@ name=%@, encoding=%@, offset=%ld>",
+            NSStringFromClass(self.class), self.name, self.typeEncoding, (long)self.offset];
+}
+
+- (void)examine {
+    _name         = @(ivar_getName(self.objc_ivar));
+    _typeEncoding = @(ivar_getTypeEncoding(self.objc_ivar));
+    _type         = (FLEXTypeEncoding)[_typeEncoding characterAtIndex:0];
+    _offset       = ivar_getOffset(self.objc_ivar);
+}
+
+- (id)getValue:(id)target {
+    return [FLEXRuntimeUtility valueForIvar:self.objc_ivar onObject:target];
+}
+
+- (id)getPotentiallyUnboxedValue:(id)target {
+    return [FLEXRuntimeUtility
+        potentiallyUnwrapBoxedPointer:[self getValue:target]
+        type:self.typeEncoding.UTF8String
+    ];
+}
+
+@end

+ 95 - 0
Classes/Utility/Runtime/FLEXMethod.h

@@ -0,0 +1,95 @@
+//
+//  FLEXMethod.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXMethodBase.h"
+#import "FLEXRuntimeUtility.h"
+#import <objc/runtime.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A class representing a concrete method which already exists in a class.
+/// This class contains helper methods for swizzling or invoking the method.
+///
+/// Any of the initializers will return nil if the type encoding
+/// of the method is unsupported by `NSMethodSignature`. In general,
+/// any method whose return type or parameters involve a struct with
+/// bitfields or arrays is unsupported.
+///
+/// I do not remember why I didn't include \c signature in the base class
+/// when I originally wrote this, but I probably had a good reason. We can
+/// always go back and move it to \c FLEXMethodBase if we find we need to.
+@interface FLEXMethod : FLEXMethodBase
+
+/// Defaults to instance method
++ (nullable instancetype)method:(Method)method;
++ (nullable instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod;
+
+/// Constructs an \c FLEXMethod for the given method on the given class.
+/// @param cls the class, or metaclass if this is a class method
+/// @return The newly constructed \c FLEXMethod object, or \c nil if the
+/// specified class or its superclasses do not contain a method with the specified selector.
++ (nullable instancetype)selector:(SEL)selector class:(Class)cls;
+/// Constructs an \c FLEXMethod for the given method on the given class,
+/// only if the given class itself defines or overrides the desired method.
+/// @param cls the class, or metaclass if this is a class method
+/// @return The newly constructed \c FLEXMethod object, or \c nil \e if the
+/// specified class does not define or override, or if the specified class
+/// or its superclasses do not contain, a method with the specified selector.
++ (nullable instancetype)selector:(SEL)selector implementedInClass:(Class)cls;
+
+@property (nonatomic, readonly) Method            objc_method;
+/// The implementation of the method.
+/// @discussion Setting \c implementation will change the implementation of this method
+/// for the entire class which implements said method. It will also not modify the selector of said method.
+@property (nonatomic          ) IMP               implementation;
+/// Whether the method is an instance method or not.
+@property (nonatomic, readonly) BOOL              isInstanceMethod;
+/// The number of arguments to the method.
+@property (nonatomic, readonly) NSUInteger        numberOfArguments;
+/// The \c NSMethodSignature object corresponding to the method's type encoding.
+@property (nonatomic, readonly) NSMethodSignature *signature;
+/// Same as \e typeEncoding but with parameter sizes up front and offsets after the types.
+@property (nonatomic, readonly) NSString          *signatureString;
+/// The return type of the method.
+@property (nonatomic, readonly) FLEXTypeEncoding  *returnType;
+/// The return size of the method.
+@property (nonatomic, readonly) NSUInteger        returnSize;
+
+/// Like @code - (void)foo:(int)bar @endcode
+@property (nonatomic, readonly) NSString *description;
+/// Like @code -[Class foo:] @endcode
+- (NSString *)debugNameGivenClassName:(NSString *)name;
+
+/// Swizzles the recieving method with the given method.
+- (void)swapImplementations:(FLEXMethod *)method;
+
+#define FLEXMagicNumber 0xdeadbeef
+#define FLEXArg(expr) FLEXMagicNumber,/// @encode(__typeof__(expr)), (__typeof__(expr) []){ expr }
+
+/// Sends a message to \e target, and returns it's value, or \c nil if not applicable.
+/// @discussion You may send any message with this method. Primitive return values will be wrapped
+/// in instances of \c NSNumber and \c NSValue. \c void and bitfield returning methods return \c nil.
+/// \c SEL return types are converted to strings using \c NSStringFromSelector.
+/// @return The object returned by this method, or an instance of \c NSValue or \c NSNumber containing
+/// the primitive return type, or a string for \c SEL return types.
+- (id)sendMessage:(id)target, ...;
+/// Used internally by \c sendMessage:target,. Pass \c NULL to the first parameter for void methods.
+- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ...;
+
+@end
+
+
+@interface FLEXMethod (Comparison)
+
+- (NSComparisonResult)compare:(FLEXMethod *)method;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 404 - 0
Classes/Utility/Runtime/FLEXMethod.m

@@ -0,0 +1,404 @@
+//
+//  FLEXMethod.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXMethod.h"
+#import "FLEXMirror.h"
+
+
+@implementation FLEXMethod
+@dynamic implementation;
+
++ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation {
+    [NSException raise:NSInternalInconsistencyException format:@"Class instance should not be created with +buildMethodNamed:withTypes:implementation"]; return nil;
+}
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
+#pragma mark Initializers
+
++ (instancetype)method:(Method)method {
+    return [[self alloc] initWithMethod:method isInstanceMethod:YES];
+}
+
++ (instancetype)method:(Method)method isInstanceMethod:(BOOL)isInstanceMethod {
+    return [[self alloc] initWithMethod:method isInstanceMethod:isInstanceMethod];
+}
+
++ (instancetype)selector:(SEL)selector class:(Class)cls {
+    BOOL instance = class_isMetaClass(cls);
+    // class_getInstanceMethod will return an instance method if not given
+    // not given a metaclass, or a class method if given a metaclass, but
+    // this isn't documented so we just want to be safe here.
+    Method m = instance ? class_getInstanceMethod(cls, selector) : class_getClassMethod(cls, selector);
+    if (m == NULL) return nil;
+    
+    return [self method:m isInstanceMethod:instance];
+}
+
++ (instancetype)selector:(SEL)selector implementedInClass:(Class)cls {
+    if (![cls superclass]) { return [self selector:selector class:cls]; }
+    
+    BOOL unique = [cls methodForSelector:selector] != [[cls superclass] methodForSelector:selector];
+    
+    if (unique) {
+        return [self selector:selector class:cls];
+    }
+    
+    return nil;
+}
+
+- (id)initWithMethod:(Method)method isInstanceMethod:(BOOL)isInstanceMethod {
+    NSParameterAssert(method);
+    
+    self = [super init];
+    if (self) {
+        _objc_method = method;
+        _isInstanceMethod = isInstanceMethod;
+        _signatureString = @(method_getTypeEncoding(method));
+        @try {
+            _signature = [NSMethodSignature signatureWithObjCTypes:_signatureString.UTF8String];
+        } @catch (NSException *exception) {
+            _signature = nil;
+        }
+
+        [self examine];
+    }
+    
+    return self;
+}
+
+#pragma mark Other
+
+- (NSString *)description {
+    if (!_flex_description) {
+        _flex_description = [FLEXMethod prettyNameForMethod:self.objc_method isClassMethod:!_isInstanceMethod];
+    }
+    
+    return _flex_description;
+}
+
+- (NSString *)debugNameGivenClassName:(NSString *)name {
+    NSMutableString *string = [NSMutableString stringWithString:_isInstanceMethod ? @"-[" : @"+["];
+    [string appendString:name];
+    [string appendString:@" "];
+    [string appendString:self.selectorString];
+    [string appendString:@"]"];
+    return string;
+}
+
++ (NSString *)prettyNameForMethod:(Method)method isClassMethod:(BOOL)isClassMethod {
+    NSString *selectorName = NSStringFromSelector(method_getName(method));
+    NSString *methodTypeString = isClassMethod ? @"+" : @"-";
+    NSString *readableReturnType = ({
+        char *returnType = method_copyReturnType(method);
+        NSString *ret = [FLEXRuntimeUtility readableTypeForEncoding:@(returnType)];
+        free(returnType);
+        ret;
+    });
+    
+    NSString *prettyName = [NSString stringWithFormat:@"%@ (%@)", methodTypeString, readableReturnType];
+    NSArray *components = [self prettyArgumentComponentsForMethod:method];
+    if (components.count) {
+        prettyName = [prettyName stringByAppendingString:[components componentsJoinedByString:@" "]];
+    } else {
+        prettyName = [prettyName stringByAppendingString:selectorName];
+    }
+    
+    return prettyName;
+}
+
++ (NSArray *)prettyArgumentComponentsForMethod:(Method)method {
+    NSMutableArray *components = [NSMutableArray array];
+    
+    NSString *selectorName = NSStringFromSelector(method_getName(method));
+    NSArray *selectorComponents = [selectorName componentsSeparatedByString:@":"];
+    unsigned int numberOfArguments = method_getNumberOfArguments(method);
+    
+    for (unsigned int argIndex = 2; argIndex < numberOfArguments; argIndex++) {
+        char *argType = method_copyArgumentType(method, argIndex);
+        NSString *readableArgType = [FLEXRuntimeUtility readableTypeForEncoding:@(argType)];
+        free(argType);
+        NSString *prettyComponent = [NSString
+            stringWithFormat:@"%@:(%@) ",
+            selectorComponents[argIndex - 2],
+            readableArgType
+        ];
+        [components addObject:prettyComponent];
+    }
+    
+    return components;
+}
+
+- (NSString *)debugDescription {
+    return [NSString stringWithFormat:@"<%@ selector=%@, signature=%@>",
+            NSStringFromClass(self.class), self.selectorString, self.signatureString];
+}
+
+- (void)examine {
+    _implementation    = method_getImplementation(_objc_method);
+    _selector          = method_getName(_objc_method);
+    _numberOfArguments = method_getNumberOfArguments(_objc_method);
+    _name              = NSStringFromSelector(_selector);
+    _returnType        = (FLEXTypeEncoding *)_signature.methodReturnType;
+    _returnSize        = _signature.methodReturnLength;
+    _typeEncoding      = [_signatureString
+        stringByReplacingOccurrencesOfString:@"[0-9]"
+        withString:@""
+        options:NSRegularExpressionSearch
+        range:NSMakeRange(0, _signatureString.length)
+    ];
+}
+
+#pragma mark Setters
+
+- (void)setImplementation:(IMP)implementation {
+    NSParameterAssert(implementation);
+    method_setImplementation(self.objc_method, implementation);
+    [self examine];
+}
+
+#pragma mark Misc
+
+- (void)swapImplementations:(FLEXMethod *)method {
+    method_exchangeImplementations(self.objc_method, method.objc_method);
+    [self examine];
+    [method examine];
+}
+
+// Some code borrowed from MAObjcRuntime, by Mike Ash.
+- (id)sendMessage:(id)target, ... {
+    id ret = nil;
+    va_list args;
+    va_start(args, target);
+    
+    switch (self.returnType[0]) {
+        case FLEXTypeEncodingUnknown: {
+            [self getReturnValue:NULL forMessageSend:target arguments:args];
+            break;
+        }
+        case FLEXTypeEncodingChar: {
+            char val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingInt: {
+            int val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingShort: {
+            short val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingLong: {
+            long val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingLongLong: {
+            long long val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingUnsignedChar: {
+            unsigned char val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingUnsignedInt: {
+            unsigned int val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingUnsignedShort: {
+            unsigned short val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingUnsignedLong: {
+            unsigned long val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingUnsignedLongLong: {
+            unsigned long long val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingFloat: {
+            float val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingDouble: {
+            double val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingLongDouble: {
+            long double val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = [NSValue value:&val withObjCType:self.returnType];
+            break;
+        }
+        case FLEXTypeEncodingCBool: {
+            bool val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingVoid: {
+            [self getReturnValue:NULL forMessageSend:target arguments:args];
+            return nil;
+            break;
+        }
+        case FLEXTypeEncodingCString: {
+            char *val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = @(val);
+            break;
+        }
+        case FLEXTypeEncodingObjcObject: {
+            id val = nil;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = val;
+            break;
+        }
+        case FLEXTypeEncodingObjcClass: {
+            Class val = Nil;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = val;
+            break;
+        }
+        case FLEXTypeEncodingSelector: {
+            SEL val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = NSStringFromSelector(val);
+            break;
+        }
+        case FLEXTypeEncodingArrayBegin: {
+            void *val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType];
+            break;
+        }
+        case FLEXTypeEncodingUnionBegin:
+        case FLEXTypeEncodingStructBegin: {
+            if (self.signature.methodReturnLength) {
+                void * val = malloc(self.signature.methodReturnLength);
+                [self getReturnValue:val forMessageSend:target arguments:args];
+                ret = [NSValue valueWithBytes:val objCType:self.signature.methodReturnType];
+            } else {
+                [self getReturnValue:NULL forMessageSend:target arguments:args];
+            }
+            break;
+        }
+        case FLEXTypeEncodingBitField: {
+            [self getReturnValue:NULL forMessageSend:target arguments:args];
+            break;
+        }
+        case FLEXTypeEncodingPointer: {
+            void * val = 0;
+            [self getReturnValue:&val forMessageSend:target arguments:args];
+            ret = [NSValue valueWithPointer:val];
+            break;
+        }
+
+        default: {
+            [NSException raise:NSInvalidArgumentException
+                        format:@"Unsupported type encoding: %s", (char *)self.returnType];
+        }
+    }
+    
+    va_end(args);
+    return ret;
+}
+
+// Code borrowed from MAObjcRuntime, by Mike Ash.
+- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target, ... {
+    va_list args;
+    va_start(args, target);
+    [self getReturnValue:retPtr forMessageSend:target arguments:args];
+    va_end(args);
+}
+
+// Code borrowed from MAObjcRuntime, by Mike Ash.
+- (void)getReturnValue:(void *)retPtr forMessageSend:(id)target arguments:(va_list)args {
+    NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:_signature];
+    NSUInteger argumentCount = _signature.numberOfArguments;
+    
+    invocation.target = target;
+    
+    for (NSUInteger i = 2; i < argumentCount; i++) {
+        int cookie = va_arg(args, int);
+        if (cookie != FLEXMagicNumber) {
+            [NSException
+                raise:NSInternalInconsistencyException
+                format:@"%s: incorrect magic cookie %08x; make sure you didn't forget "
+                "any arguments and that all arguments are wrapped in FLEXArg().", __func__, cookie
+            ];
+        }
+        const char *typeString = va_arg(args, char *);
+        void *argPointer       = va_arg(args, void *);
+        
+        NSUInteger inSize, sigSize;
+        NSGetSizeAndAlignment(typeString, &inSize, NULL);
+        NSGetSizeAndAlignment([_signature getArgumentTypeAtIndex:i], &sigSize, NULL);
+        
+        if (inSize != sigSize) {
+            [NSException
+                raise:NSInternalInconsistencyException
+                format:@"%s:size mismatch between passed-in argument and "
+                "required argument; in type:%s (%lu) requested:%s (%lu)",
+                __func__, typeString, (long)inSize, [_signature getArgumentTypeAtIndex:i], (long)sigSize
+            ];
+        }
+        
+        [invocation setArgument:argPointer atIndex:i];
+    }
+    
+    // Hack to make NSInvocation invoke the desired implementation
+    IMP imp = [invocation methodForSelector:NSSelectorFromString(@"invokeUsingIMP:")];
+    void (*invokeWithIMP)(id, SEL, IMP) = (void *)imp;
+    invokeWithIMP(invocation, 0, _implementation);
+    
+    if (_signature.methodReturnLength && retPtr) {
+        [invocation getReturnValue:retPtr];
+    }
+}
+
+@end
+
+
+@implementation FLEXMethod (Comparison)
+
+- (NSComparisonResult)compare:(FLEXMethod *)method {
+    return [self.selectorString compare:method.selectorString];
+}
+
+@end

+ 43 - 0
Classes/Utility/Runtime/FLEXMethodBase.h

@@ -0,0 +1,43 @@
+//
+//  FLEXMethodBase.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/5/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+
+/// A base class for methods which encompasses those that may not
+/// have been added to a class yet. Useful on it's own for adding
+/// methods to a class, or building a new class from the ground up.
+@interface FLEXMethodBase : NSObject {
+@protected
+    SEL      _selector;
+    NSString *_name;
+    NSString *_typeEncoding;
+    IMP      _implementation;
+    
+    NSString *_flex_description;
+}
+
+/// Constructs and returns an \c FLEXSimpleMethod instance with the given name, type encoding, and implementation.
++ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation;
+
+/// The selector of the method.
+@property (nonatomic, readonly) SEL      selector;
+/// The selector string of the method.
+@property (nonatomic, readonly) NSString *selectorString;
+/// Same as selectorString.
+@property (nonatomic, readonly) NSString *name;
+/// The type encoding of the method.
+@property (nonatomic, readonly) NSString *typeEncoding;
+/// The implementation of the method.
+@property (nonatomic, readonly) IMP      implementation;
+
+/// For internal use
+@property (nonatomic) id tag;
+
+@end

+ 49 - 0
Classes/Utility/Runtime/FLEXMethodBase.m

@@ -0,0 +1,49 @@
+//
+//  FLEXMethodBase.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/5/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXMethodBase.h"
+
+
+@implementation FLEXMethodBase
+
+#pragma mark Initializers
+
++ (instancetype)buildMethodNamed:(NSString *)name withTypes:(NSString *)typeEncoding implementation:(IMP)implementation {
+    return [[self alloc] initWithSelector:sel_registerName(name.UTF8String) types:typeEncoding imp:implementation];
+}
+
+- (id)initWithSelector:(SEL)selector types:(NSString *)types imp:(IMP)imp {
+    NSParameterAssert(selector); NSParameterAssert(types); NSParameterAssert(imp);
+    
+    self = [super init];
+    if (self) {
+        _selector = selector;
+        _typeEncoding = types;
+        _implementation = imp;
+        _name = NSStringFromSelector(self.selector);
+    }
+    
+    return self;
+}
+
+- (NSString *)selectorString {
+    return _name;
+}
+
+#pragma mark Overrides
+
+- (NSString *)description {
+    if (!_flex_description) {
+        _flex_description = [NSString stringWithFormat:@"%@ '%@'", _name, _typeEncoding];
+    }
+
+    return _flex_description;
+}
+
+@end

+ 60 - 0
Classes/Utility/Runtime/FLEXMirror.h

@@ -0,0 +1,60 @@
+//
+//  FLEXMirror.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/29/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@class FLEXMethod, FLEXProperty, FLEXIvar, FLEXProtocol;
+#import <objc/runtime.h>
+
+
+#pragma mark FLEXMirror
+@interface FLEXMirror : NSObject
+
+/// Reflects an instance of an object or \c Class.
+/// @discussion \c FLEXMirror will immediately gather all useful information. Consider using the
+/// \c NSObject categories provided if your code will only use a few pieces of information,
+/// or if your code needs to run faster.
+///
+/// If you reflect an instance of a class then \c methods and \c properties will be populated
+/// with instance methods and properties. If you reflect a class itself, then \c methods
+/// and \c properties will be populated with class methods and properties as you'd expect.
+///
+/// @param objectOrClass An instance of an objct or a \c Class object.
+/// @return An instance of \c FLEXMirror.
++ (instancetype)reflect:(id)objectOrClass;
+
+/// The underlying object or \c Class used to create this \c FLEXMirror instance.
+@property (nonatomic, readonly) id   value;
+/// Whether the reflected thing was a class or a class instance.
+@property (nonatomic, readonly) BOOL isClass;
+/// The name of the \c Class of the \c value property.
+@property (nonatomic, readonly) NSString *className;
+
+@property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
+@property (nonatomic, readonly) NSArray<FLEXIvar *>     *ivars;
+@property (nonatomic, readonly) NSArray<FLEXMethod *>   *methods;
+@property (nonatomic, readonly) NSArray<FLEXProtocol *> *protocols;
+
+/// @return A reflection of \c value.superClass.
+@property (nonatomic, readonly) FLEXMirror *superMirror;
+
+@end
+
+
+@interface FLEXMirror (ExtendedMirror)
+
+/// @return The method with the given name, or \c nil if one does not exist.
+- (FLEXMethod *)methodNamed:(NSString *)name;
+/// @return The property with the given name, or \c nil if one does not exist.
+- (FLEXProperty *)propertyNamed:(NSString *)name;
+/// @return The instance variable with the given name, or \c nil if one does not exist.
+- (FLEXIvar *)ivarNamed:(NSString *)name;
+/// @return The protocol with the given name, or \c nil if one does not exist.
+- (FLEXProtocol *)protocolNamed:(NSString *)name;
+
+@end

+ 138 - 0
Classes/Utility/Runtime/FLEXMirror.m

@@ -0,0 +1,138 @@
+//
+//  FLEXMirror.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/29/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXMirror.h"
+#import "FLEXProperty.h"
+#import "FLEXMethod.h"
+#import "FLEXIvar.h"
+#import "FLEXProtocol.h"
+#import "FLEXUtility.h"
+//#import "MirrorKit.h"
+//#import "NSObject+Reflection.h"
+
+
+#pragma mark FLEXMirror
+
+@implementation FLEXMirror
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
+#pragma mark Initialization
++ (instancetype)reflect:(id)objectOrClass {
+    return [[self alloc] initWithValue:objectOrClass];
+}
+
+- (id)initWithValue:(id)value {
+    NSParameterAssert(value);
+    
+    self = [super init];
+    if (self) {
+        _value = value;
+        [self examine];
+    }
+    
+    return self;
+}
+
+- (NSString *)description {
+    NSString *type = self.isClass ? @"metaclass" : @"class";
+    return [NSString
+        stringWithFormat:@"<%@ %@=%@, %lu properties, %lu ivars, %lu methods, %lu protocols>",
+        NSStringFromClass(self.class),
+        type,
+        self.className,
+        (unsigned long)self.properties.count,
+        (unsigned long)self.ivars.count,
+        (unsigned long)self.methods.count,
+        (unsigned long)self.protocols.count
+    ];
+}
+
+- (void)examine {
+    // cls is a metaclass if self.value is a class
+    Class cls = object_getClass(self.value);
+    
+    unsigned int pcount, mcount, ivcount, pccount;
+    objc_property_t *objcproperties     = class_copyPropertyList(cls, &pcount);
+    Protocol*__unsafe_unretained *procs = class_copyProtocolList(cls, &pccount);
+    Method *objcmethods                 = class_copyMethodList(cls, &mcount);
+    Ivar *objcivars                     = class_copyIvarList(cls, &ivcount);
+    
+    _className = NSStringFromClass(cls);
+    _isClass   = class_isMetaClass(cls); // or object_isClass(self.value)
+    
+    NSMutableArray *properties = [NSMutableArray array];
+    for (int i = 0; i < pcount; i++)
+        [properties addObject:[FLEXProperty property:objcproperties[i]]];
+    _properties = properties;
+    
+    NSMutableArray *methods = [NSMutableArray array];
+    for (int i = 0; i < mcount; i++)
+        [methods addObject:[FLEXMethod method:objcmethods[i]]];
+    _methods = methods;
+    
+    NSMutableArray *ivars = [NSMutableArray array];
+    for (int i = 0; i < ivcount; i++)
+        [ivars addObject:[FLEXIvar ivar:objcivars[i]]];
+    _ivars = ivars;
+    
+    NSMutableArray *protocols = [NSMutableArray array];
+    for (int i = 0; i < pccount; i++)
+        [protocols addObject:[FLEXProtocol protocol:procs[i]]];
+    _protocols = protocols;
+    
+    // Cleanup
+    free(objcproperties);
+    free(objcmethods);
+    free(objcivars);
+    free(procs);
+    procs = NULL;
+}
+
+#pragma mark Misc
+
+- (FLEXMirror *)superMirror {
+    return [FLEXMirror reflect:[self.value superclass]];
+}
+
+@end
+
+
+#pragma mark ExtendedMirror
+
+@implementation FLEXMirror (ExtendedMirror)
+
+- (id)filter:(NSArray *)array forName:(NSString *)name {
+    NSPredicate *filter = [NSPredicate predicateWithFormat:@"%K = %@", @"name", name];
+    return [array filteredArrayUsingPredicate:filter].firstObject;
+}
+
+- (FLEXMethod *)methodNamed:(NSString *)name {
+    return [self filter:self.methods forName:name];
+}
+
+- (FLEXProperty *)propertyNamed:(NSString *)name {
+    return [self filter:self.properties forName:name];
+}
+
+- (FLEXIvar *)ivarNamed:(NSString *)name {
+    return [self filter:self.ivars forName:name];
+}
+
+- (FLEXProtocol *)protocolNamed:(NSString *)name {
+    return [self filter:self.protocols forName:name];
+}
+
+@end

Classes/Utility/FLEXObjcInternal.h → Classes/Utility/Runtime/FLEXObjcInternal.h


Classes/Utility/FLEXObjcInternal.mm → Classes/Utility/Runtime/FLEXObjcInternal.mm


+ 124 - 0
Classes/Utility/Runtime/FLEXProperty.h

@@ -0,0 +1,124 @@
+//
+//  FLEXProperty.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXRuntimeUtility.h"
+#import <objc/runtime.h>
+@class FLEXPropertyAttributes, FLEXMethodBase;
+
+
+#pragma mark FLEXProperty
+@interface FLEXProperty : NSObject
+
+/// You may use this initializer instead of \c property:onClass: if you don't need
+/// to know anything about the uniqueness of this property or where it comes from.
++ (instancetype)property:(objc_property_t)property;
+/// This initializer can be used to access additional information
+/// in an efficient manner. That information being whether this property
+/// is certainly not unique and the name of the binary image which declares it.
+/// @param cls the class, or metaclass if this is a class property.
++ (instancetype)property:(objc_property_t)property onClass:(Class)cls;
+/// @param cls the class, or metaclass if this is a class property
++ (instancetype)named:(NSString *)name onClass:(Class)cls;
+/// Constructs a new property with the given name and attributes.
++ (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes;
+
+/// \c 0 if the instance was created via \c +propertyWithName:attributes,
+/// otherwise this is the first property in \c objc_properties
+@property (nonatomic, readonly) objc_property_t  objc_property;
+@property (nonatomic, readonly) objc_property_t  *objc_properties;
+@property (nonatomic, readonly) NSInteger        objc_propertyCount;
+@property (nonatomic, readonly) BOOL             isClassProperty;
+
+/// The name of the property.
+@property (nonatomic, readonly) NSString         *name;
+/// The type of the property. Get the full type from the attributes.
+@property (nonatomic, readonly) FLEXTypeEncoding type;
+/// The property's attributes.
+@property (nonatomic          ) FLEXPropertyAttributes *attributes;
+/// The (likely) setter, regardless of whether the property is readonly.
+/// For example, this might be the custom setter.
+@property (nonatomic, readonly) SEL likelySetter;
+/// Not valid unless initialized with the owning class.
+@property (nonatomic, readonly) BOOL likelySetterExists;
+/// The (likely) getter. For example, this might be the custom getter.
+@property (nonatomic, readonly) SEL likelyGetter;
+/// Not valid unless initialized with the owning class.
+@property (nonatomic, readonly) BOOL likelyGetterExists;
+
+/// Whether there are certainly multiple definitions of this property,
+/// such as in categories in other binary images or something.
+/// @return Whether \c objc_property matches the return value of \c class_getProperty,
+/// or \c NO if this property was not created with \c property:onClass
+@property (nonatomic, readonly) BOOL multiple;
+/// @return The bundle of the image that contains this property definition,
+/// or \c nil if this property was not created with \c property:onClass or
+/// if this property is probably defined in the objc runtime.
+@property (nonatomic, readonly) NSString *imageName;
+
+/// For internal use
+@property (nonatomic) id tag;
+
+/// @return The value of this property on \c target as given by \c -valueForKey:
+- (id)getValue:(id)target;
+/// Calls into -getValue: and passes that value into
+/// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
+/// and returns the result
+- (id)getPotentiallyUnboxedValue:(id)target;
+
+/// Safe to use regardless of how the \c FLEXProperty instance was initialized.
+///
+/// This uses \c self.objc_property if it exists, otherwise it uses \c self.attributes
+- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount;
+
+/// Replace the attributes of the current property in the given class,
+/// using the attributes in \c self.attributes
+///
+/// What happens when the property does not exist is undocumented.
+- (void)replacePropertyOnClass:(Class)cls;
+
+#pragma mark Convenience getters and setters
+/// @return A getter for the property with the given implementation.
+/// @discussion Consider using the \c FLEXPropertyGetter macros.
+- (FLEXMethodBase *)getterWithImplementation:(IMP)implementation;
+/// @return A setter for the property with the given implementation.
+/// @discussion Consider using the \c FLEXPropertySetter macros.
+- (FLEXMethodBase *)setterWithImplementation:(IMP)implementation;
+
+#pragma mark FLEXMethod property getter / setter macros
+// Easier than using the above methods yourself in most cases
+
+/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and
+/// uses the \c FLEXProperty's \c attribute's \c backingIvarName to get the Ivar.
+#define FLEXPropertyGetter(FLEXProperty, type) [FLEXProperty \
+    getterWithImplementation:imp_implementationWithBlock(^(id self) { \
+        return *(type *)[self getIvarAddressByName:FLEXProperty.attributes.backingIvar]; \
+    }) \
+];
+/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and
+/// uses the \c FLEXProperty's \c attribute's \c backingIvarName to set the Ivar.
+#define FLEXPropertySetter(FLEXProperty, type) [FLEXProperty \
+    setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \
+        [self setIvarByName:FLEXProperty.attributes.backingIvar value:&value size:sizeof(type)]; \
+    }) \
+];
+/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to get the Ivar.
+#define FLEXPropertyGetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \
+    getterWithImplementation:imp_implementationWithBlock(^(id self) { \
+        return *(type *)[self getIvarAddressByName:ivarName]; \
+    }) \
+];
+/// Takes a \c FLEXProperty and a type (ie \c NSUInteger or \c id) and an Ivar name string to set the Ivar.
+#define FLEXPropertySetterWithIvar(FLEXProperty, ivarName, type) [FLEXProperty \
+    setterWithImplementation:imp_implementationWithBlock(^(id self, type value) { \
+        [self setIvarByName:ivarName value:&value size:sizeof(type)]; \
+    }) \
+];
+
+@end

+ 186 - 0
Classes/Utility/Runtime/FLEXProperty.m

@@ -0,0 +1,186 @@
+//
+//  FLEXProperty.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXProperty.h"
+#import "FLEXPropertyAttributes.h"
+#import "FLEXMethodBase.h"
+#import "FLEXRuntimeUtility.h"
+#include <dlfcn.h>
+
+
+@interface FLEXProperty () {
+    NSString *_flex_description;
+}
+@property (nonatomic          ) BOOL uniqueCheckFlag;
+@property (nonatomic, readonly) Class cls;
+@end
+
+@implementation FLEXProperty
+@synthesize multiple = _multiple;
+@synthesize imageName = _imageName;
+
+#pragma mark Initializers
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
++ (instancetype)property:(objc_property_t)property {
+    return [[self alloc] initWithProperty:property onClass:nil];
+}
+
++ (instancetype)property:(objc_property_t)property onClass:(Class)cls {
+    return [[self alloc] initWithProperty:property onClass:cls];
+}
+
++ (instancetype)named:(NSString *)name onClass:(Class)cls {
+    return [self property:class_getProperty(cls, name.UTF8String) onClass:cls];
+}
+
++ (instancetype)propertyWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
+    return [[self alloc] initWithName:name attributes:attributes];
+}
+
+- (id)initWithProperty:(objc_property_t)property onClass:(Class)cls {
+    NSParameterAssert(property);
+    
+    self = [super init];
+    if (self) {
+        _objc_property = property;
+        _attributes    = [FLEXPropertyAttributes attributesForProperty:property];
+        _name          = @(property_getName(property));
+        _cls           = cls;
+        
+        if (!_attributes) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property attributes"];
+        if (!_name) [NSException raise:NSInternalInconsistencyException format:@"Error retrieving property name"];
+        
+        [self examine];
+    }
+    
+    return self;
+}
+
+- (id)initWithName:(NSString *)name attributes:(FLEXPropertyAttributes *)attributes {
+    NSParameterAssert(name); NSParameterAssert(attributes);
+    
+    self = [super init];
+    if (self) {
+        _attributes    = attributes;
+        _name          = name;
+        
+        [self examine];
+    }
+    
+    return self;
+}
+
+#pragma mark Private
+
+- (void)examine {
+    _type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
+
+    _likelyGetter = self.attributes.customGetter ?: NSSelectorFromString(self.name);
+    _likelySetter = self.attributes.customSetter ?: NSSelectorFromString([NSString
+        stringWithFormat:@"set%c%@:",
+        (char)toupper([self.name characterAtIndex:0]),
+        [self.name substringFromIndex:1]
+    ]);
+
+    if (_cls) {
+        _likelySetterExists = [_cls instancesRespondToSelector:_likelySetter];
+        _likelyGetterExists = [_cls instancesRespondToSelector:_likelyGetter];
+        _isClassProperty = class_isMetaClass(_cls);
+    }
+}
+
+#pragma mark Overrides
+
+- (NSString *)description {
+    if (!_flex_description) {
+        NSString *readableType = [FLEXRuntimeUtility readableTypeForEncoding:self.attributes.typeEncoding];
+        _flex_description = [FLEXRuntimeUtility appendName:self.name toType:readableType];
+    }
+
+    return _flex_description;
+}
+
+- (NSString *)debugDescription {
+    return [NSString stringWithFormat:@"<%@ name=%@, property=%p, attributes:\n\t%@\n>",
+            NSStringFromClass(self.class), self.name, self.objc_property, self.attributes];
+}
+
+#pragma mark Public
+
+- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount {
+    if (self.objc_property) {
+        return property_copyAttributeList(self.objc_property, attributesCount);
+    } else {
+        return [self.attributes copyAttributesList:attributesCount];
+    }
+}
+
+- (void)replacePropertyOnClass:(Class)cls {
+    class_replaceProperty(cls, self.name.UTF8String, self.attributes.list, (unsigned int)self.attributes.count);
+}
+
+- (void)computeSymbolInfo:(BOOL)forceBundle {
+    if ((!_multiple || !_uniqueCheckFlag) && _cls) {
+        _multiple = _objc_property != class_getProperty(_cls, self.name.UTF8String);
+
+        if (_multiple || forceBundle) {
+            Dl_info exeInfo;
+            dladdr(_objc_property, &exeInfo);
+            NSString *path = @(exeInfo.dli_fname).stringByDeletingLastPathComponent;
+            _imageName = [NSBundle bundleWithPath:path].executablePath.lastPathComponent;
+        }
+    }
+}
+
+- (BOOL)multiple {
+    [self computeSymbolInfo:NO];
+    return _multiple;
+}
+
+- (NSString *)imageName {
+    [self computeSymbolInfo:YES];
+    return _imageName;
+}
+
+- (id)getValue:(id)target {
+    return [FLEXRuntimeUtility valueForProperty:self.objc_property onObject:target];
+}
+
+- (id)getPotentiallyUnboxedValue:(id)target {
+    return [FLEXRuntimeUtility
+        potentiallyUnwrapBoxedPointer:[self getValue:target]
+        type:self.attributes.typeEncoding.UTF8String
+    ];
+}
+
+#pragma mark Suggested getters and setters
+
+- (FLEXMethodBase *)getterWithImplementation:(IMP)implementation {
+    NSString *types        = [NSString stringWithFormat:@"%@%s%s", self.attributes.typeEncoding, @encode(id), @encode(SEL)];
+    NSString *name         = [NSString stringWithFormat:@"%@", self.name];
+    FLEXMethodBase *getter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
+    return getter;
+}
+
+- (FLEXMethodBase *)setterWithImplementation:(IMP)implementation {
+    NSString *types        = [NSString stringWithFormat:@"%s%s%s%@", @encode(void), @encode(id), @encode(SEL), self.attributes.typeEncoding];
+    NSString *name         = [NSString stringWithFormat:@"set%@:", self.name.capitalizedString];
+    FLEXMethodBase *setter = [FLEXMethodBase buildMethodNamed:name withTypes:types implementation:implementation];
+    return setter;
+}
+
+@end

+ 106 - 0
Classes/Utility/Runtime/FLEXPropertyAttributes.h

@@ -0,0 +1,106 @@
+//
+//  FLEXPropertyAttributes.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/5/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <objc/runtime.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+#pragma mark FLEXPropertyAttributes
+
+/// See \e FLEXRuntimeUtilitiy.h for valid string tokens.
+/// See this link on how to construct a proper attributes string:
+/// https://developer.apple.com/library/mac/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html
+@interface FLEXPropertyAttributes : NSObject <NSCopying, NSMutableCopying> {
+// These are necessary for the mutable subclass to function
+@protected
+    NSUInteger _count;
+    NSString *_string, *_backingIvar, *_typeEncoding, *_oldTypeEncoding;
+    NSDictionary *_dictionary;
+    objc_property_attribute_t *_list;
+    SEL _customGetter, _customSetter;
+    BOOL _isReadOnly, _isCopy, _isRetained, _isNonatomic, _isDynamic, _isWeak, _isGarbageCollectable;
+}
+
++ (instancetype)attributesForProperty:(objc_property_t)property;
+/// @warning Raises an exception if \e attributes is invalid or \c nil.
++ (instancetype)attributesFromString:(NSString *)attributes;
+/// @warning Raises an exception if \e attributes is invalid, \c nil, or contains unsupported keys.
++ (instancetype)attributesFromDictionary:(NSDictionary *)attributes;
+
+/// Copies the attributes list to a buffer you must \c free() yourself.
+/// Use \c list instead if you do not need more control over the lifetime of the list.
+/// @param attributesCountOut the number of attributes is returned in this parameter.
+- (objc_property_attribute_t *)copyAttributesList:(nullable unsigned int *)attributesCountOut;
+
+/// The number of property attributes.
+@property (nonatomic, readonly) NSUInteger count;
+/// For use with \c class_replaceProperty and the like.
+@property (nonatomic, readonly) objc_property_attribute_t *list;
+/// The string value of the property attributes.
+@property (nonatomic, readonly) NSString *string;
+/// A dictionary of the property attributes.
+/// Values are either a string or \c @YES. Boolean attributes
+/// which are false will not be present in the dictionary.
+@property (nonatomic, readonly) NSDictionary *dictionary;
+
+/// The name of the instance variable backing the property.
+@property (nonatomic, readonly, nullable) NSString *backingIvar;
+/// The type encoding of the property.
+@property (nonatomic, readonly, nullable) NSString *typeEncoding;
+/// The \e old type encoding of the property.
+@property (nonatomic, readonly, nullable) NSString *oldTypeEncoding;
+/// The property's custom getter, if any.
+@property (nonatomic, readonly, nullable) SEL customGetter;
+/// The property's custom setter, if any.
+@property (nonatomic, readonly, nullable) SEL customSetter;
+
+@property (nonatomic, readonly) BOOL isReadOnly;
+@property (nonatomic, readonly) BOOL isCopy;
+@property (nonatomic, readonly) BOOL isRetained;
+@property (nonatomic, readonly) BOOL isNonatomic;
+@property (nonatomic, readonly) BOOL isDynamic;
+@property (nonatomic, readonly) BOOL isWeak;
+@property (nonatomic, readonly) BOOL isGarbageCollectable;
+
+@end
+
+
+#pragma mark FLEXPropertyAttributes
+@interface FLEXMutablePropertyAttributes : FLEXPropertyAttributes
+
+/// Creates and returns an empty property attributes object.
++ (instancetype)attributes;
+
+/// The name of the instance variable backing the property.
+@property (nonatomic, nullable) NSString *backingIvar;
+/// The type encoding of the property.
+@property (nonatomic, nullable) NSString *typeEncoding;
+/// The \e old type encoding of the property.
+@property (nonatomic, nullable) NSString *oldTypeEncoding;
+/// The property's custom getter, if any.
+@property (nonatomic, nullable) SEL customGetter;
+/// The property's custom setter, if any.
+@property (nonatomic, nullable) SEL customSetter;
+
+@property (nonatomic) BOOL isReadOnly;
+@property (nonatomic) BOOL isCopy;
+@property (nonatomic) BOOL isRetained;
+@property (nonatomic) BOOL isNonatomic;
+@property (nonatomic) BOOL isDynamic;
+@property (nonatomic) BOOL isWeak;
+@property (nonatomic) BOOL isGarbageCollectable;
+
+/// A more convenient method of setting the \c typeEncoding property.
+/// @discussion This will not work for complex types like structs and primitive pointers.
+- (void)setTypeEncodingChar:(char)type;
+
+@end
+
+NS_ASSUME_NONNULL_END

+ 331 - 0
Classes/Utility/Runtime/FLEXPropertyAttributes.m

@@ -0,0 +1,331 @@
+//
+//  FLEXPropertyAttributes.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/5/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXPropertyAttributes.h"
+#import "FLEXRuntimeUtility.h"
+#import "NSString+ObjcRuntime.h"
+#import "NSDictionary+ObjcRuntime.h"
+
+
+#pragma mark FLEXPropertyAttributes
+
+@interface FLEXPropertyAttributes ()
+
+@property (nonatomic) NSString *backingIvar;
+@property (nonatomic) NSString *typeEncoding;
+@property (nonatomic) NSString *oldTypeEncoding;
+@property (nonatomic) SEL customGetter;
+@property (nonatomic) SEL customSetter;
+@property (nonatomic) BOOL isReadOnly;
+@property (nonatomic) BOOL isCopy;
+@property (nonatomic) BOOL isRetained;
+@property (nonatomic) BOOL isNonatomic;
+@property (nonatomic) BOOL isDynamic;
+@property (nonatomic) BOOL isWeak;
+@property (nonatomic) BOOL isGarbageCollectable;
+
+@end
+
+@implementation FLEXPropertyAttributes
+@synthesize list = _list;
+
+#pragma mark Initializers
+
++ (instancetype)attributesForProperty:(objc_property_t)property {
+    return [self attributesFromString:@(property_getAttributes(property))];
+}
+
++ (instancetype)attributesFromDictionary:(NSDictionary *)attributes {
+    NSString *attrs = attributes.propertyAttributesString;
+    if (!attrs) {
+        [NSException raise:NSInternalInconsistencyException
+                    format:@"Invalid property attributes dictionary: %@", attributes];
+    }
+    return [self attributesFromString:attrs];
+}
+
++ (instancetype)attributesFromString:(NSString *)attributes {
+    return [[self alloc] initWithstring:attributes];
+}
+
+- (id)initWithstring:(NSString *)string {
+    NSParameterAssert(string);
+    
+    self = [super init];
+    if (self) {
+        _string = string;
+        
+        _dictionary = string.propertyAttributes;
+        if (!_dictionary) {
+            [NSException raise:NSInternalInconsistencyException
+                        format:@"Invalid property attributes string: %@", string];
+        }
+        
+        _count                = _dictionary.count;
+        _typeEncoding         = _dictionary[kFLEXPropertyAttributeKeyTypeEncoding];
+        _backingIvar          = _dictionary[kFLEXPropertyAttributeKeyBackingIvarName];
+        _oldTypeEncoding      = _dictionary[kFLEXPropertyAttributeKeyOldStyleTypeEncoding];
+        _customGetter         = NSSelectorFromString(_dictionary[kFLEXPropertyAttributeKeyCustomGetter]);
+        _customSetter         = NSSelectorFromString(_dictionary[kFLEXPropertyAttributeKeyCustomSetter]);
+        _isReadOnly           = [_dictionary[kFLEXPropertyAttributeKeyReadOnly] boolValue];
+        _isCopy               = [_dictionary[kFLEXPropertyAttributeKeyCopy] boolValue];
+        _isRetained           = [_dictionary[kFLEXPropertyAttributeKeyRetain] boolValue];
+        _isNonatomic          = [_dictionary[kFLEXPropertyAttributeKeyNonAtomic] boolValue];
+        _isWeak               = [_dictionary[kFLEXPropertyAttributeKeyWeak] boolValue];
+        _isGarbageCollectable = [_dictionary[kFLEXPropertyAttributeKeyGarbageCollectable] boolValue];
+    }
+    
+    return self;
+}
+
+#pragma mark Misc
+
+- (NSString *)description {
+    return [NSString
+        stringWithFormat:@"<%@ ivar=%@, readonly=%d, nonatomic=%d, getter=%@, setter=%@>\n%@",
+        NSStringFromClass(self.class),
+        self.backingIvar ?: @"none",
+        self.isReadOnly,
+        self.isNonatomic,
+        NSStringFromSelector(self.customGetter) ?: @" ",
+        NSStringFromSelector(self.customSetter) ?: @" ",
+        self.string
+    ];
+}
+
+- (objc_property_attribute_t *)copyAttributesList:(unsigned int *)attributesCount {
+    NSDictionary *attrs = self.string.propertyAttributes;
+    objc_property_attribute_t *propertyAttributes = malloc(attrs.count * sizeof(objc_property_attribute_t));
+
+    if (attributesCount) {
+        *attributesCount = (unsigned int)attrs.count;
+    }
+    
+    NSUInteger i = 0;
+    for (NSString *key in attrs.allKeys) {
+        FLEXPropertyAttribute c = (FLEXPropertyAttribute)[key characterAtIndex:0];
+        switch (c) {
+            case FLEXPropertyAttributeTypeEncoding: {
+                objc_property_attribute_t pa = {
+                    kFLEXPropertyAttributeKeyTypeEncoding.UTF8String,
+                    self.typeEncoding.UTF8String
+                };
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeBackingIvarName: {
+                objc_property_attribute_t pa = {
+                    kFLEXPropertyAttributeKeyBackingIvarName.UTF8String,
+                    self.backingIvar.UTF8String
+                };
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeCopy: {
+                objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyCopy.UTF8String, ""};
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeCustomGetter: {
+                objc_property_attribute_t pa = {
+                    kFLEXPropertyAttributeKeyCustomGetter.UTF8String,
+                    NSStringFromSelector(self.customGetter).UTF8String ?: ""
+                };
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeCustomSetter: {
+                objc_property_attribute_t pa = {
+                    kFLEXPropertyAttributeKeyCustomSetter.UTF8String,
+                    NSStringFromSelector(self.customSetter).UTF8String ?: ""
+                };
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeDynamic: {
+                objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyDynamic.UTF8String, ""};
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeGarbageCollectible: {
+                objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyGarbageCollectable.UTF8String, ""};
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeNonAtomic: {
+                objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyNonAtomic.UTF8String, ""};
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeOldTypeEncoding: {
+                objc_property_attribute_t pa = {
+                    kFLEXPropertyAttributeKeyOldStyleTypeEncoding.UTF8String,
+                    self.oldTypeEncoding.UTF8String ?: ""
+                };
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeReadOnly: {
+                objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyReadOnly.UTF8String, ""};
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeRetain: {
+                objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyRetain.UTF8String, ""};
+                propertyAttributes[i] = pa;
+                break;
+            }
+            case FLEXPropertyAttributeWeak: {
+                objc_property_attribute_t pa = {kFLEXPropertyAttributeKeyWeak.UTF8String, ""};
+                propertyAttributes[i] = pa;
+                break;
+            }
+        }
+        i++;
+    }
+    
+    return propertyAttributes;
+}
+
+- (objc_property_attribute_t *)list {
+    if (!_list) {
+        _list = [self copyAttributesList:nil];
+    }
+
+    return _list;
+}
+
+- (void)dealloc {
+    if (_list) {
+        free(_list);
+        _list = nil;
+    }
+}
+
+#pragma mark Copying
+
+- (id)copyWithZone:(NSZone *)zone {
+    return [[FLEXPropertyAttributes class] attributesFromString:self.string];
+}
+
+- (id)mutableCopyWithZone:(NSZone *)zone {
+    return [[FLEXMutablePropertyAttributes class] attributesFromString:self.string];
+}
+
+@end
+
+
+
+#pragma mark FLEXMutablePropertyAttributes
+
+@interface FLEXMutablePropertyAttributes ()
+@property (nonatomic) BOOL countDelta;
+@property (nonatomic) BOOL stringDelta;
+@property (nonatomic) BOOL dictDelta;
+@property (nonatomic) BOOL listDelta;
+@end
+
+#define PropertyWithDeltaFlag(type, name, Name) @dynamic name; \
+- (void)set ## Name:(type)name { \
+    if (name != _ ## name) { \
+        _countDelta = _stringDelta = _dictDelta = _listDelta = YES; \
+        _ ## name = name; \
+    } \
+}
+
+@implementation FLEXMutablePropertyAttributes
+
+PropertyWithDeltaFlag(NSString *, backingIvar, BackingIvar);
+PropertyWithDeltaFlag(NSString *, typeEncoding, TypeEncoding);
+PropertyWithDeltaFlag(NSString *, oldTypeEncoding, OldTypeEncoding);
+PropertyWithDeltaFlag(SEL, customGetter, CustomGetter);
+PropertyWithDeltaFlag(SEL, customSetter, CustomSetter);
+PropertyWithDeltaFlag(BOOL, isReadOnly, IsReadOnly);
+PropertyWithDeltaFlag(BOOL, isCopy, IsCopy);
+PropertyWithDeltaFlag(BOOL, isRetained, IsRetained);
+PropertyWithDeltaFlag(BOOL, isNonatomic, IsNonatomic);
+PropertyWithDeltaFlag(BOOL, isDynamic, IsDynamic);
+PropertyWithDeltaFlag(BOOL, isWeak, IsWeak);
+PropertyWithDeltaFlag(BOOL, isGarbageCollectable, IsGarbageCollectable);
+
++ (instancetype)attributes {
+    return [self new];
+}
+
+- (void)setTypeEncodingChar:(char)type {
+    self.typeEncoding = [NSString stringWithFormat:@"%c", type];
+}
+
+- (NSUInteger)count {
+    // Recalculate attribute count after mutations
+    if (self.countDelta) {
+        self.countDelta = NO;
+        _count = self.dictionary.count;
+    }
+
+    return _count;
+}
+
+- (objc_property_attribute_t *)list {
+    // Regenerate list after mutations
+    if (self.listDelta) {
+        self.listDelta = NO;
+        if (_list) {
+            free(_list);
+            _list = nil;
+        }
+    }
+
+    // Super will generate the list if it isn't set
+    return super.list;
+}
+
+- (NSString *)string {
+    // Regenerate string after mutations
+    if (self.stringDelta || !_string) {
+        self.stringDelta = NO;
+        _string = self.dictionary.propertyAttributesString;
+    }
+
+    return _string;
+}
+
+- (NSDictionary *)dictionary {
+    // Regenerate dictionary after mutations
+    if (self.dictDelta || !_dictionary) {
+        // _stringa nd _dictionary depend on each other,
+        // so we must generate ONE by hand using our properties.
+        // We arbitrarily choose to generate the dictionary.
+        NSMutableDictionary *attrs = [NSMutableDictionary new];
+        if (self.typeEncoding)
+            attrs[kFLEXPropertyAttributeKeyTypeEncoding]         = self.typeEncoding;
+        if (self.backingIvar)
+            attrs[kFLEXPropertyAttributeKeyBackingIvarName]      = self.backingIvar;
+        if (self.oldTypeEncoding)
+            attrs[kFLEXPropertyAttributeKeyOldStyleTypeEncoding] = self.oldTypeEncoding;
+        if (self.customGetter)
+            attrs[kFLEXPropertyAttributeKeyCustomGetter]         = NSStringFromSelector(self.customGetter);
+        if (self.customSetter)
+            attrs[kFLEXPropertyAttributeKeyCustomSetter]         = NSStringFromSelector(self.customSetter);
+
+        if (self.isReadOnly)           attrs[kFLEXPropertyAttributeKeyReadOnly] = @YES;
+        if (self.isCopy)               attrs[kFLEXPropertyAttributeKeyCopy] = @YES;
+        if (self.isRetained)           attrs[kFLEXPropertyAttributeKeyRetain] = @YES;
+        if (self.isNonatomic)          attrs[kFLEXPropertyAttributeKeyNonAtomic] = @YES;
+        if (self.isDynamic)            attrs[kFLEXPropertyAttributeKeyDynamic] = @YES;
+        if (self.isWeak)               attrs[kFLEXPropertyAttributeKeyWeak] = @YES;
+        if (self.isGarbageCollectable) attrs[kFLEXPropertyAttributeKeyGarbageCollectable] = @YES;
+
+        _dictionary = attrs.copy;
+    }
+
+    return _dictionary;
+}
+
+@end

+ 55 - 0
Classes/Utility/Runtime/FLEXProtocol.h

@@ -0,0 +1,55 @@
+//
+//  FLEXProtocol.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import "FLEXRuntimeUtility.h"
+@class FLEXProperty, FLEXMethodDescription;
+
+#pragma mark FLEXProtocol
+@interface FLEXProtocol : NSObject
+
+/// Every protocol registered with the runtime.
++ (NSArray<FLEXProtocol *> *)allProtocols;
++ (instancetype)protocol:(Protocol *)protocol;
+
+/// The underlying protocol data structure.
+@property (nonatomic, readonly) Protocol *objc_protocol;
+
+/// The name of the protocol.
+@property (nonatomic, readonly) NSString *name;
+/// The properties in the protocol, if any.
+@property (nonatomic, readonly) NSArray<FLEXProperty *>  *properties;
+/// The required methods of the protocol, if any. This includes property getters and setters.
+@property (nonatomic, readonly) NSArray<FLEXMethodDescription *>  *requiredMethods;
+/// The optional methods of the protocol, if any. This includes property getters and setters.
+@property (nonatomic, readonly) NSArray<FLEXMethodDescription *>  *optionalMethods;
+/// All protocols that this protocol conforms to, if any.
+@property (nonatomic, readonly) NSArray<FLEXProtocol *>  *protocols;
+
+/// Not to be confused with \c -conformsToProtocol:, which refers to the current
+/// \c FLEXProtocol instance and not the underlying \c Protocol object.
+- (BOOL)conformsTo:(Protocol *)protocol;
+
+@end
+
+
+#pragma mark Method descriptions
+@interface FLEXMethodDescription : NSObject
+
++ (instancetype)description:(struct objc_method_description)methodDescription;
+
+/// The underlying method description data structure.
+@property (nonatomic, readonly) struct objc_method_description objc_description;
+/// The method's selector.
+@property (nonatomic, readonly) SEL selector;
+/// The method's type encoding.
+@property (nonatomic, readonly) NSString *typeEncoding;
+/// The method's return type.
+@property (nonatomic, readonly) FLEXTypeEncoding returnType;
+@end

+ 139 - 0
Classes/Utility/Runtime/FLEXProtocol.m

@@ -0,0 +1,139 @@
+//
+//  FLEXProtocol.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 6/30/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXProtocol.h"
+#import "FLEXProperty.h"
+#import <objc/runtime.h>
+
+
+@implementation FLEXProtocol
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
+#pragma mark Initializers
+
++ (NSArray *)allProtocols {
+    unsigned int prcount;
+    Protocol *__unsafe_unretained*protocols = objc_copyProtocolList(&prcount);
+    
+    NSMutableArray *all = [NSMutableArray array];
+    for(NSUInteger i = 0; i < prcount; i++)
+        [all addObject:[self protocol:protocols[i]]];
+    
+    free(protocols);
+    return all;
+}
+
++ (instancetype)protocol:(Protocol *)protocol {
+    return [[self alloc] initWithProtocol:protocol];
+}
+
+- (id)initWithProtocol:(Protocol *)protocol {
+    NSParameterAssert(protocol);
+    
+    self = [super init];
+    if (self) {
+        _objc_protocol = protocol;
+        [self examine];
+    }
+    
+    return self;
+}
+
+#pragma mark Other
+
+- (NSString *)description {
+    return [NSString stringWithFormat:@"<%@ name=%@, %lu properties, %lu required methods, %lu optional methods, %lu protocols>",
+            NSStringFromClass(self.class), self.name, (unsigned long)self.properties.count,
+            (unsigned long)self.requiredMethods.count, (unsigned long)self.optionalMethods.count, (unsigned long)self.protocols.count];
+}
+
+- (void)examine {
+    _name = @(protocol_getName(self.objc_protocol));
+    unsigned int prcount, pccount, mdrcount, mdocount;
+    
+    objc_property_t *objcproperties = protocol_copyPropertyList(self.objc_protocol, &prcount);
+    Protocol * __unsafe_unretained *objcprotocols = protocol_copyProtocolList(self.objc_protocol, &pccount);
+    struct objc_method_description *objcrmethods = protocol_copyMethodDescriptionList(self.objc_protocol, YES, YES, &mdrcount);
+    struct objc_method_description *objcomethods = protocol_copyMethodDescriptionList(self.objc_protocol, NO, YES, &mdocount);
+    
+    NSMutableArray *properties = [NSMutableArray array];
+    for (int i = 0; i < prcount; i++)
+        [properties addObject:[FLEXProperty property:objcproperties[i]]];
+    _properties = properties;
+    
+    NSMutableArray *protocols = [NSMutableArray array];
+    for (int i = 0; i < pccount; i++)
+        [protocols addObject:[FLEXProtocol protocol:objcprotocols[i]]];
+    _protocols = protocols;
+    
+    NSMutableArray *requiredMethods = [NSMutableArray array];
+    for (int i = 0; i < mdrcount; i++)
+        [requiredMethods addObject:[FLEXMethodDescription description:objcrmethods[i]]];
+    _requiredMethods = requiredMethods;
+    
+    NSMutableArray *optionalMethods = [NSMutableArray array];
+    for (int i = 0; i < mdocount; i++)
+        [optionalMethods addObject:[FLEXMethodDescription description:objcomethods[i]]];
+    _optionalMethods = optionalMethods;
+    
+    free(objcproperties);
+    free(objcprotocols);
+    free(objcrmethods);
+    free(objcomethods);
+}
+
+- (BOOL)conformsTo:(Protocol *)protocol {
+    return protocol_conformsToProtocol(self.objc_protocol, protocol);
+}
+
+@end
+
+#pragma mark FLEXMethodDescription
+
+@implementation FLEXMethodDescription
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
++ (instancetype)description:(struct objc_method_description)methodDescription {
+    return [[self alloc] initWithDescription:methodDescription];
+}
+
+- (id)initWithDescription:(struct objc_method_description)md {
+    NSParameterAssert(md.name != NULL);
+    
+    self = [super init];
+    if (self) {
+        _objc_description = md;
+        _selector         = md.name;
+        _typeEncoding     = @(md.types);
+        _returnType       = (FLEXTypeEncoding)[self.typeEncoding characterAtIndex:0];
+    }
+    
+    return self;
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:@"<%@ name=%@, type=%@>",
+            NSStringFromClass(self.class), NSStringFromSelector(self.selector), self.typeEncoding];
+}
+
+@end

+ 41 - 0
Classes/Utility/Runtime/FLEXProtocolBuilder.h

@@ -0,0 +1,41 @@
+//
+//  FLEXProtocolBuilder.h
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/4/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+@class FLEXProperty, FLEXProtocol, Protocol;
+
+@interface FLEXProtocolBuilder : NSObject
+
+/// Begins to construct a new protocol with the given name.
+/// @discussion You must register the protocol with the
+/// \c registerProtocol method before you can use it.
++ (instancetype)allocateProtocol:(NSString *)name;
+
+/// Adds a property to a protocol.
+/// @param property The property to add.
+/// @param isRequired Whether the property is required to implement the protocol.
+- (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired;
+/// Adds a property to a protocol.
+/// @param selector The selector of the method to add.
+/// @param typeEncoding The type encoding of the method to add.
+/// @param isRequired Whether the method is required to implement the protocol.
+/// @param isInstanceMethod \c YES if the method is an instance method, \c NO if it is a class method.
+- (void)addMethod:(SEL)selector
+     typeEncoding:(NSString *)typeEncoding
+       isRequired:(BOOL)isRequired
+ isInstanceMethod:(BOOL)isInstanceMethod;
+/// Makes the recieving protocol conform to the given protocol.
+- (void)addProtocol:(Protocol *)protocol;
+
+/// Registers and returns the recieving protocol, which was previously under construction.
+- (FLEXProtocol *)registerProtocol;
+/// Whether the protocol is still under construction or already registered.
+@property (nonatomic, readonly) BOOL isRegistered;
+
+@end

+ 93 - 0
Classes/Utility/Runtime/FLEXProtocolBuilder.m

@@ -0,0 +1,93 @@
+//
+//  FLEXProtocolBuilder.m
+//  FLEX
+//
+//  Derived from MirrorKit.
+//  Created by Tanner on 7/4/15.
+//  Copyright (c) 2015 Tanner Bennett. All rights reserved.
+//
+
+#import "FLEXProtocolBuilder.h"
+#import "FLEXProtocol.h"
+#import "FLEXProperty.h"
+#import <objc/runtime.h>
+
+#define MutationAssertion(msg) if (self.isRegistered) { \
+    [NSException \
+        raise:NSInternalInconsistencyException \
+        format:msg \
+    ]; \
+}
+
+@interface FLEXProtocolBuilder ()
+@property (nonatomic) Protocol *workingProtocol;
+@property (nonatomic) NSString *name;
+@end
+
+@implementation FLEXProtocolBuilder
+
+- (id)init {
+    [NSException
+        raise:NSInternalInconsistencyException
+        format:@"Class instance should not be created with -init"
+    ];
+    return nil;
+}
+
+#pragma mark Initializers
++ (instancetype)allocateProtocol:(NSString *)name {
+    NSParameterAssert(name);
+    return [[self alloc] initWithProtocol:objc_allocateProtocol(name.UTF8String)];
+    
+}
+
+- (id)initWithProtocol:(Protocol *)protocol {
+    NSParameterAssert(protocol);
+    
+    self = [super init];
+    if (self) {
+        _workingProtocol = protocol;
+        _name = NSStringFromProtocol(self.workingProtocol);
+    }
+    
+    return self;
+}
+
+- (NSString *)description {
+    return [NSString stringWithFormat:@"<%@ name=%@, registered=%d>",
+            NSStringFromClass(self.class), self.name, self.isRegistered];
+}
+
+#pragma mark Building
+
+- (void)addProperty:(FLEXProperty *)property isRequired:(BOOL)isRequired {
+    MutationAssertion(@"Properties cannot be added once a protocol has been registered");
+
+    unsigned int count;
+    objc_property_attribute_t *attributes = [property copyAttributesList:&count];
+    protocol_addProperty(self.workingProtocol, property.name.UTF8String, attributes, count, isRequired, YES);
+    free(attributes);
+}
+
+- (void)addMethod:(SEL)selector
+    typeEncoding:(NSString *)typeEncoding
+       isRequired:(BOOL)isRequired
+ isInstanceMethod:(BOOL)isInstanceMethod {
+    MutationAssertion(@"Methods cannot be added once a protocol has been registered");
+    protocol_addMethodDescription(self.workingProtocol, selector, typeEncoding.UTF8String, isRequired, isInstanceMethod);
+}
+
+- (void)addProtocol:(Protocol *)protocol {
+    MutationAssertion(@"Protocols cannot be added once a protocol has been registered");
+    protocol_addProtocol(self.workingProtocol, protocol);
+}
+
+- (FLEXProtocol *)registerProtocol {
+    MutationAssertion(@"Protocol is already registered");
+    
+    _isRegistered = YES;
+    objc_registerProtocol(self.workingProtocol);
+    return [FLEXProtocol protocol:self.workingProtocol];
+}
+
+@end

+ 39 - 14
Classes/Utility/FLEXRuntimeUtility.h

@@ -12,18 +12,34 @@
 extern const unsigned int kFLEXNumberOfImplicitArgs;
 
 // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
-extern NSString *const kFLEXUtilityAttributeTypeEncoding;
-extern NSString *const kFLEXUtilityAttributeBackingIvar;
-extern NSString *const kFLEXUtilityAttributeReadOnly;
-extern NSString *const kFLEXUtilityAttributeCopy;
-extern NSString *const kFLEXUtilityAttributeRetain;
-extern NSString *const kFLEXUtilityAttributeNonAtomic;
-extern NSString *const kFLEXUtilityAttributeCustomGetter;
-extern NSString *const kFLEXUtilityAttributeCustomSetter;
-extern NSString *const kFLEXUtilityAttributeDynamic;
-extern NSString *const kFLEXUtilityAttributeWeak;
-extern NSString *const kFLEXUtilityAttributeGarbageCollectable;
-extern NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding;
+extern NSString *const kFLEXPropertyAttributeKeyTypeEncoding;
+extern NSString *const kFLEXPropertyAttributeKeyBackingIvarName;
+extern NSString *const kFLEXPropertyAttributeKeyReadOnly;
+extern NSString *const kFLEXPropertyAttributeKeyCopy;
+extern NSString *const kFLEXPropertyAttributeKeyRetain;
+extern NSString *const kFLEXPropertyAttributeKeyNonAtomic;
+extern NSString *const kFLEXPropertyAttributeKeyCustomGetter;
+extern NSString *const kFLEXPropertyAttributeKeyCustomSetter;
+extern NSString *const kFLEXPropertyAttributeKeyDynamic;
+extern NSString *const kFLEXPropertyAttributeKeyWeak;
+extern NSString *const kFLEXPropertyAttributeKeyGarbageCollectable;
+extern NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding;
+
+typedef NS_ENUM(NSUInteger, FLEXPropertyAttribute)
+{
+    FLEXPropertyAttributeTypeEncoding       = 'T',
+    FLEXPropertyAttributeBackingIvarName    = 'V',
+    FLEXPropertyAttributeCopy               = 'C',
+    FLEXPropertyAttributeCustomGetter       = 'G',
+    FLEXPropertyAttributeCustomSetter       = 'S',
+    FLEXPropertyAttributeDynamic            = 'D',
+    FLEXPropertyAttributeGarbageCollectible = 'P',
+    FLEXPropertyAttributeNonAtomic          = 'N',
+    FLEXPropertyAttributeOldTypeEncoding    = 't',
+    FLEXPropertyAttributeReadOnly           = 'R',
+    FLEXPropertyAttributeRetain             = '&',
+    FLEXPropertyAttributeWeak               = 'W'
+};
 
 typedef NS_ENUM(char, FLEXTypeEncoding)
 {
@@ -71,6 +87,9 @@ typedef NS_ENUM(char, FLEXTypeEncoding)
 /// Some fields have a name in their encoded string (e.g. \"width\"d)
 /// @return the offset to skip the field name, 0 if there is no name
 + (NSUInteger)fieldNameOffsetForTypeEncoding:(const FLEXTypeEncoding *)typeEncoding;
+/// Given name "foo" and type "int" this would return "int foo", but
+/// given name "foo" and type "T *" it would return "T *foo"
++ (NSString *)appendName:(NSString *)name toType:(NSString *)typeEncoding;
 
 /// @return The class hierarchy for the given object or class,
 /// from the current class to the root-most class.
@@ -83,8 +102,10 @@ typedef NS_ENUM(char, FLEXTypeEncoding)
 + (SEL)setterSelectorForProperty:(objc_property_t)property;
 + (NSString *)fullDescriptionForProperty:(objc_property_t)property;
 + (id)valueForProperty:(objc_property_t)property onObject:(id)object;
-+ (NSString *)descriptionForIvarOrPropertyValue:(id)value;
-+ (void)tryAddPropertyWithName:(const char *)name attributes:(NSDictionary<NSString *, NSString *> *)attributePairs toClass:(__unsafe_unretained Class)theClass;
++ (NSString *)summaryForObject:(id)value;
++ (void)tryAddPropertyWithName:(const char *)name
+                    attributes:(NSDictionary<NSString *, NSString *> *)attributePairs
+                       toClass:(__unsafe_unretained Class)theClass;
 
 // Ivar Helpers
 + (NSString *)prettyNameForIvar:(Ivar)ivar;
@@ -112,4 +133,8 @@ typedef NS_ENUM(char, FLEXTypeEncoding)
                                                  NSUInteger fieldOffset))typeBlock;
 + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type;
 
+#pragma mark - Metadata Helpers
+
++ (NSString *)readableTypeForEncoding:(NSString *)encodingString;
+
 @end

+ 86 - 47
Classes/Utility/FLEXRuntimeUtility.m

@@ -11,18 +11,18 @@
 #import "FLEXObjcInternal.h"
 
 // See https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtPropertyIntrospection.html#//apple_ref/doc/uid/TP40008048-CH101-SW6
-NSString *const kFLEXUtilityAttributeTypeEncoding = @"T";
-NSString *const kFLEXUtilityAttributeBackingIvar = @"V";
-NSString *const kFLEXUtilityAttributeReadOnly = @"R";
-NSString *const kFLEXUtilityAttributeCopy = @"C";
-NSString *const kFLEXUtilityAttributeRetain = @"&";
-NSString *const kFLEXUtilityAttributeNonAtomic = @"N";
-NSString *const kFLEXUtilityAttributeCustomGetter = @"G";
-NSString *const kFLEXUtilityAttributeCustomSetter = @"S";
-NSString *const kFLEXUtilityAttributeDynamic = @"D";
-NSString *const kFLEXUtilityAttributeWeak = @"W";
-NSString *const kFLEXUtilityAttributeGarbageCollectable = @"P";
-NSString *const kFLEXUtilityAttributeOldStyleTypeEncoding = @"t";
+NSString *const kFLEXPropertyAttributeKeyTypeEncoding = @"T";
+NSString *const kFLEXPropertyAttributeKeyBackingIvarName = @"V";
+NSString *const kFLEXPropertyAttributeKeyReadOnly = @"R";
+NSString *const kFLEXPropertyAttributeKeyCopy = @"C";
+NSString *const kFLEXPropertyAttributeKeyRetain = @"&";
+NSString *const kFLEXPropertyAttributeKeyNonAtomic = @"N";
+NSString *const kFLEXPropertyAttributeKeyCustomGetter = @"G";
+NSString *const kFLEXPropertyAttributeKeyCustomSetter = @"S";
+NSString *const kFLEXPropertyAttributeKeyDynamic = @"D";
+NSString *const kFLEXPropertyAttributeKeyWeak = @"W";
+NSString *const kFLEXPropertyAttributeKeyGarbageCollectable = @"P";
+NSString *const kFLEXPropertyAttributeKeyOldStyleTypeEncoding = @"t";
 
 static NSString *const FLEXRuntimeUtilityErrorDomain = @"FLEXRuntimeUtilityErrorDomain";
 typedef NS_ENUM(NSInteger, FLEXRuntimeUtilityErrorCode) {
@@ -117,6 +117,43 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return superClasses;
 }
 
+/// Could be nil
++ (NSString *)safeDescriptionForObject:(id)object
+{
+    // Don't assume that we have an NSObject subclass.
+    // Check to make sure the object responds to the description method
+    if ([object respondsToSelector:@selector(description)]) {
+        return [object description];
+    }
+
+    return nil;
+}
+
+/// Never nil
++ (NSString *)safeDebugDescriptionForObject:(id)object
+{
+    NSString *description = nil;
+
+    // Don't assume that we have an NSObject subclass.
+    // Check to make sure the object responds to the description method
+    if ([object respondsToSelector:@selector(debugDescription)]) {
+        description = [object debugDescription];
+    } else {
+        description = [self safeDescriptionForObject:object];
+    }
+
+    if (!description.length) {
+        NSString *cls = NSStringFromClass(object_getClass(object));
+        if (object_isClass(object)) {
+            description = [cls stringByAppendingString:@" class (no description)"];
+        } else {
+            description = [cls stringByAppendingString:@" instance (no description)"];
+        }
+    }
+
+    return description;
+}
+
 #pragma mark - Property Helpers (Public)
 
 + (NSString *)prettyNameForProperty:(objc_property_t)property
@@ -129,19 +166,19 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
 
 + (NSString *)typeEncodingForProperty:(objc_property_t)property
 {
-    NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
-    return attributesDictionary[kFLEXUtilityAttributeTypeEncoding];
+    NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesForProperty:property];
+    return attributesDictionary[kFLEXPropertyAttributeKeyTypeEncoding];
 }
 
 + (BOOL)isReadonlyProperty:(objc_property_t)property
 {
-    return [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeReadOnly] != nil;
+    return [self attributesForProperty:property][kFLEXPropertyAttributeKeyReadOnly] != nil;
 }
 
 + (SEL)setterSelectorForProperty:(objc_property_t)property
 {
     SEL setterSelector = NULL;
-    NSString *setterSelectorString = [[self attributesDictionaryForProperty:property] objectForKey:kFLEXUtilityAttributeCustomSetter];
+    NSString *setterSelectorString = [self attributesForProperty:property][kFLEXPropertyAttributeKeyCustomSetter];
     if (!setterSelectorString) {
         NSString *propertyName = @(property_getName(property));
         setterSelectorString = [NSString
@@ -158,37 +195,37 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
 
 + (NSString *)fullDescriptionForProperty:(objc_property_t)property
 {
-    NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesDictionaryForProperty:property];
+    NSDictionary<NSString *, NSString *> *attributesDictionary = [self attributesForProperty:property];
     NSMutableArray<NSString *> *attributesStrings = [NSMutableArray array];
 
     // Atomicity
-    if (attributesDictionary[kFLEXUtilityAttributeNonAtomic]) {
+    if (attributesDictionary[kFLEXPropertyAttributeKeyNonAtomic]) {
         [attributesStrings addObject:@"nonatomic"];
     } else {
         [attributesStrings addObject:@"atomic"];
     }
 
     // Storage
-    if (attributesDictionary[kFLEXUtilityAttributeRetain]) {
+    if (attributesDictionary[kFLEXPropertyAttributeKeyRetain]) {
         [attributesStrings addObject:@"strong"];
-    } else if (attributesDictionary[kFLEXUtilityAttributeCopy]) {
+    } else if (attributesDictionary[kFLEXPropertyAttributeKeyCopy]) {
         [attributesStrings addObject:@"copy"];
-    } else if (attributesDictionary[kFLEXUtilityAttributeWeak]) {
+    } else if (attributesDictionary[kFLEXPropertyAttributeKeyWeak]) {
         [attributesStrings addObject:@"weak"];
     } else {
         [attributesStrings addObject:@"assign"];
     }
 
     // Mutability
-    if (attributesDictionary[kFLEXUtilityAttributeReadOnly]) {
+    if (attributesDictionary[kFLEXPropertyAttributeKeyReadOnly]) {
         [attributesStrings addObject:@"readonly"];
     } else {
         [attributesStrings addObject:@"readwrite"];
     }
 
     // Custom getter/setter
-    NSString *customGetter = attributesDictionary[kFLEXUtilityAttributeCustomGetter];
-    NSString *customSetter = attributesDictionary[kFLEXUtilityAttributeCustomSetter];
+    NSString *customGetter = attributesDictionary[kFLEXPropertyAttributeKeyCustomGetter];
+    NSString *customSetter = attributesDictionary[kFLEXPropertyAttributeKeyCustomSetter];
     if (customGetter) {
         [attributesStrings addObject:[NSString stringWithFormat:@"getter=%@", customGetter]];
     }
@@ -205,7 +242,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
 + (id)valueForProperty:(objc_property_t)property onObject:(id)object
 {
     NSString *customGetterString = nil;
-    char *customGetterName = property_copyAttributeValue(property, kFLEXUtilityAttributeCustomGetter.UTF8String);
+    char *customGetterName = property_copyAttributeValue(property, kFLEXPropertyAttributeKeyCustomGetter.UTF8String);
     if (customGetterName) {
         customGetterString = @(customGetterName);
         free(customGetterName);
@@ -222,7 +259,7 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return [self performSelector:getterSelector onObject:object withArguments:nil error:NULL];
 }
 
-+ (NSString *)descriptionForIvarOrPropertyValue:(id)value
++ (NSString *)summaryForObject:(id)value
 {
     NSString *description = nil;
 
@@ -232,20 +269,18 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
         if (strcmp(type, @encode(BOOL)) == 0) {
             BOOL boolValue = NO;
             [value getValue:&boolValue];
-            description = boolValue ? @"YES" : @"NO";
+            return boolValue ? @"YES" : @"NO";
         } else if (strcmp(type, @encode(SEL)) == 0) {
             SEL selector = NULL;
             [value getValue:&selector];
-            description = NSStringFromSelector(selector);
+            return NSStringFromSelector(selector);
         }
     }
 
     @try {
-        if (!description) {
-            // Single line display - replace newlines and tabs with spaces.
-            description = [[value description] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
-            description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
-        }
+        // Single line display - replace newlines and tabs with spaces.
+        description = [[self safeDescriptionForObject:value] stringByReplacingOccurrencesOfString:@"\n" withString:@" "];
+        description = [description stringByReplacingOccurrencesOfString:@"\t" withString:@" "];
     } @catch (NSException *e) {
         description = [@"Thrown: " stringByAppendingString:e.reason ?: @"(nil exception reason)"];
     }
@@ -283,16 +318,6 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
 
 #pragma mark - Ivar Helpers (Public)
 
-+ (NSString *)prettyNameForIvar:(Ivar)ivar
-{
-    const char *nameCString = ivar_getName(ivar);
-    NSString *name = nameCString ? @(nameCString) : nil;
-    const char *encodingCString = ivar_getTypeEncoding(ivar);
-    NSString *encoding = encodingCString ? @(encodingCString) : nil;
-    NSString *readableType = [self readableTypeForEncoding:encoding];
-    return [self appendName:name toType:readableType];
-}
-
 + (id)valueForIvar:(Ivar)ivar onObject:(id)object
 {
     id value = nil;
@@ -732,16 +757,16 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
 }
 
 
-#pragma mark - Internal Helpers
+#pragma mark - Metadata Helpers
 
-+ (NSDictionary<NSString *, NSString *> *)attributesDictionaryForProperty:(objc_property_t)property
++ (NSDictionary<NSString *, NSString *> *)attributesForProperty:(objc_property_t)property
 {
     NSString *attributes = @(property_getAttributes(property));
     // Thanks to MAObjcRuntime for inspiration here.
     NSArray<NSString *> *attributePairs = [attributes componentsSeparatedByString:@","];
-    NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary dictionaryWithCapacity:attributePairs.count];
+    NSMutableDictionary<NSString *, NSString *> *attributesDictionary = [NSMutableDictionary new];
     for (NSString *attributePair in attributePairs) {
-        [attributesDictionary setObject:[attributePair substringFromIndex:1] forKey:[attributePair substringToIndex:1]];
+        attributesDictionary[[attributePair substringToIndex:1]] = [attributePair substringFromIndex:1];
     }
     return attributesDictionary;
 }
@@ -903,6 +928,20 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
     return encodingString;
 }
 
+
+#pragma mark - Internal Helpers
+
++ (NSString *)appendName:(NSString *)name toType:(NSString *)type
+{
+    NSString *combined = nil;
+    if ([type characterAtIndex:type.length - 1] == FLEXTypeEncodingCString) {
+        combined = [type stringByAppendingString:name];
+    } else {
+        combined = [type stringByAppendingFormat:@" %@", name];
+    }
+    return combined;
+}
+
 + (NSValue *)valueForPrimitivePointer:(void *)pointer objCType:(const char *)type
 {
     // Remove the field name if there is any (e.g. \"width\"d -> d)

+ 126 - 6
FLEX.xcodeproj/project.pbxproj

@@ -175,9 +175,29 @@
 		C33E46B0223B02CD004BD0E6 /* FLEXASLLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */; };
 		C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */; };
 		C34A70A122B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34A709F22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m */; };
+		C34C9BDD23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h in Headers */ = {isa = PBXBuildFile; fileRef = C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */; };
+		C34C9BDE23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m in Sources */ = {isa = PBXBuildFile; fileRef = C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */; };
 		C34EE30821CB23CC00BD3A7C /* FLEXOSLogController.h in Headers */ = {isa = PBXBuildFile; fileRef = C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */; };
 		C3511B9122D7C99E0057BAB7 /* FLEXTableViewSection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3511B8F22D7C99E0057BAB7 /* FLEXTableViewSection.h */; };
 		C3511B9222D7C99E0057BAB7 /* FLEXTableViewSection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */; };
+		C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */; };
+		C36FBFCC230F3B98008D95D5 /* FLEXProtocolBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */; };
+		C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */; };
+		C36FBFCE230F3B98008D95D5 /* FLEXProperty.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFBC230F3B97008D95D5 /* FLEXProperty.h */; };
+		C36FBFCF230F3B98008D95D5 /* FLEXProtocol.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFBD230F3B97008D95D5 /* FLEXProtocol.h */; };
+		C36FBFD0230F3B98008D95D5 /* FLEXProperty.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFBE230F3B97008D95D5 /* FLEXProperty.m */; };
+		C36FBFD1230F3B98008D95D5 /* FLEXClassBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFBF230F3B98008D95D5 /* FLEXClassBuilder.m */; };
+		C36FBFD2230F3B98008D95D5 /* FLEXMethodBase.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFC0230F3B98008D95D5 /* FLEXMethodBase.h */; };
+		C36FBFD3230F3B98008D95D5 /* FLEXMethod.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFC1230F3B98008D95D5 /* FLEXMethod.h */; };
+		C36FBFD4230F3B98008D95D5 /* FLEXProtocol.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFC2230F3B98008D95D5 /* FLEXProtocol.m */; };
+		C36FBFD5230F3B98008D95D5 /* FLEXMethodBase.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFC3230F3B98008D95D5 /* FLEXMethodBase.m */; };
+		C36FBFD6230F3B98008D95D5 /* FLEXIvar.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFC4230F3B98008D95D5 /* FLEXIvar.h */; };
+		C36FBFD7230F3B98008D95D5 /* FLEXMirror.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFC5230F3B98008D95D5 /* FLEXMirror.h */; };
+		C36FBFD8230F3B98008D95D5 /* FLEXPropertyAttributes.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFC6230F3B98008D95D5 /* FLEXPropertyAttributes.h */; };
+		C36FBFD9230F3B98008D95D5 /* FLEXProtocolBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFC7230F3B98008D95D5 /* FLEXProtocolBuilder.m */; };
+		C36FBFDA230F3B98008D95D5 /* FLEXPropertyAttributes.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFC8230F3B98008D95D5 /* FLEXPropertyAttributes.m */; };
+		C36FBFDB230F3B98008D95D5 /* FLEXClassBuilder.h in Headers */ = {isa = PBXBuildFile; fileRef = C36FBFC9230F3B98008D95D5 /* FLEXClassBuilder.h */; };
+		C36FBFDC230F3B98008D95D5 /* FLEXIvar.m in Sources */ = {isa = PBXBuildFile; fileRef = C36FBFCA230F3B98008D95D5 /* FLEXIvar.m */; };
 		C37A0C93218BAC9600848CA7 /* FLEXObjcInternal.h in Headers */ = {isa = PBXBuildFile; fileRef = C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */; };
 		C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */ = {isa = PBXBuildFile; fileRef = C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */; };
 		C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */; };
@@ -199,6 +219,8 @@
 		C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */ = {isa = PBXBuildFile; fileRef = C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */; };
 		C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */ = {isa = PBXBuildFile; fileRef = C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */; };
 		C3DC287C223ED5F200F48AA6 /* FLEXOSLogController.m in Sources */ = {isa = PBXBuildFile; fileRef = C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */; };
+		C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */ = {isa = PBXBuildFile; fileRef = C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */; };
+		C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */ = {isa = PBXBuildFile; fileRef = C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */; };
 		C3EE76BF22DFC63600EC0AA0 /* FLEXScopeCarousel.h in Headers */ = {isa = PBXBuildFile; fileRef = C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */; };
 		C3EE76C022DFC63600EC0AA0 /* FLEXScopeCarousel.m in Sources */ = {isa = PBXBuildFile; fileRef = C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */; };
 		C3F31D3D2267D883003C991A /* FLEXSubtitleTableViewCell.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */; };
@@ -211,6 +233,12 @@
 		C3F31D442267D883003C991A /* FLEXTableView.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F31D3C2267D883003C991A /* FLEXTableView.m */; };
 		C3F646C1239EAA8F00D4A011 /* UIPasteboard+FLEX.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */; };
 		C3F646C2239EAA8F00D4A011 /* UIPasteboard+FLEX.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */; };
+		C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */; };
+		C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */; };
+		C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */; };
+		C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */; };
+		C3F977872311B38F0032776D /* NSObject+Reflection.h in Headers */ = {isa = PBXBuildFile; fileRef = C3F977812311B38F0032776D /* NSObject+Reflection.h */; };
+		C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */ = {isa = PBXBuildFile; fileRef = C3F977822311B38F0032776D /* NSObject+Reflection.m */; };
 /* End PBXBuildFile section */
 
 /* Begin PBXContainerItemProxy section */
@@ -396,11 +424,31 @@
 		C33E46AE223B02CD004BD0E6 /* FLEXASLLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXASLLogController.m; sourceTree = "<group>"; };
 		C34A709E22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXBundleExplorerViewController.h; sourceTree = "<group>"; };
 		C34A709F22B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXBundleExplorerViewController.m; sourceTree = "<group>"; };
+		C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXRuntime+UIKitHelpers.h"; sourceTree = "<group>"; };
+		C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXRuntime+UIKitHelpers.m"; sourceTree = "<group>"; };
 		C34EE30621CB23CC00BD3A7C /* FLEXOSLogController.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXOSLogController.h; sourceTree = "<group>"; };
 		C34EE30721CB23CC00BD3A7C /* FLEXOSLogController.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXOSLogController.m; sourceTree = "<group>"; };
 		C34EE30A21CB249E00BD3A7C /* ActivityStreamAPI.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = ActivityStreamAPI.h; sourceTree = "<group>"; };
 		C3511B8F22D7C99E0057BAB7 /* FLEXTableViewSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXTableViewSection.h; sourceTree = "<group>"; };
 		C3511B9022D7C99E0057BAB7 /* FLEXTableViewSection.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXTableViewSection.m; sourceTree = "<group>"; };
+		C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMirror.m; sourceTree = "<group>"; };
+		C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXProtocolBuilder.h; sourceTree = "<group>"; };
+		C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMethod.m; sourceTree = "<group>"; };
+		C36FBFBC230F3B97008D95D5 /* FLEXProperty.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXProperty.h; sourceTree = "<group>"; };
+		C36FBFBD230F3B97008D95D5 /* FLEXProtocol.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXProtocol.h; sourceTree = "<group>"; };
+		C36FBFBE230F3B97008D95D5 /* FLEXProperty.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXProperty.m; sourceTree = "<group>"; };
+		C36FBFBF230F3B98008D95D5 /* FLEXClassBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXClassBuilder.m; sourceTree = "<group>"; };
+		C36FBFC0230F3B98008D95D5 /* FLEXMethodBase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMethodBase.h; sourceTree = "<group>"; };
+		C36FBFC1230F3B98008D95D5 /* FLEXMethod.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMethod.h; sourceTree = "<group>"; };
+		C36FBFC2230F3B98008D95D5 /* FLEXProtocol.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXProtocol.m; sourceTree = "<group>"; };
+		C36FBFC3230F3B98008D95D5 /* FLEXMethodBase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXMethodBase.m; sourceTree = "<group>"; };
+		C36FBFC4230F3B98008D95D5 /* FLEXIvar.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXIvar.h; sourceTree = "<group>"; };
+		C36FBFC5230F3B98008D95D5 /* FLEXMirror.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXMirror.h; sourceTree = "<group>"; };
+		C36FBFC6230F3B98008D95D5 /* FLEXPropertyAttributes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXPropertyAttributes.h; sourceTree = "<group>"; };
+		C36FBFC7230F3B98008D95D5 /* FLEXProtocolBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXProtocolBuilder.m; sourceTree = "<group>"; };
+		C36FBFC8230F3B98008D95D5 /* FLEXPropertyAttributes.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXPropertyAttributes.m; sourceTree = "<group>"; };
+		C36FBFC9230F3B98008D95D5 /* FLEXClassBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXClassBuilder.h; sourceTree = "<group>"; };
+		C36FBFCA230F3B98008D95D5 /* FLEXIvar.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXIvar.m; sourceTree = "<group>"; };
 		C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjcInternal.h; sourceTree = "<group>"; };
 		C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.objcpp; path = FLEXObjcInternal.mm; sourceTree = "<group>"; };
 		C387C87822DFCD6A00750E58 /* FLEXCarouselCell.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXCarouselCell.h; sourceTree = "<group>"; };
@@ -421,6 +469,8 @@
 		C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSArray+Functional.m"; sourceTree = "<group>"; };
 		C3DB9F622107FC9600B46809 /* FLEXObjectRef.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXObjectRef.h; sourceTree = "<group>"; };
 		C3DB9F632107FC9600B46809 /* FLEXObjectRef.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXObjectRef.m; sourceTree = "<group>"; };
+		C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "FLEXRuntime+Compare.h"; sourceTree = "<group>"; };
+		C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "FLEXRuntime+Compare.m"; sourceTree = "<group>"; };
 		C3EE76BD22DFC63600EC0AA0 /* FLEXScopeCarousel.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = FLEXScopeCarousel.h; sourceTree = "<group>"; };
 		C3EE76BE22DFC63600EC0AA0 /* FLEXScopeCarousel.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = FLEXScopeCarousel.m; sourceTree = "<group>"; };
 		C3F31D342267D883003C991A /* FLEXSubtitleTableViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FLEXSubtitleTableViewCell.h; sourceTree = "<group>"; };
@@ -433,6 +483,12 @@
 		C3F31D3C2267D883003C991A /* FLEXTableView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = FLEXTableView.m; sourceTree = "<group>"; };
 		C3F646BF239EAA8F00D4A011 /* UIPasteboard+FLEX.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIPasteboard+FLEX.h"; sourceTree = "<group>"; };
 		C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIPasteboard+FLEX.m"; sourceTree = "<group>"; };
+		C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSString+ObjcRuntime.h"; sourceTree = "<group>"; };
+		C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSDictionary+ObjcRuntime.m"; sourceTree = "<group>"; };
+		C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSDictionary+ObjcRuntime.h"; sourceTree = "<group>"; };
+		C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSString+ObjcRuntime.m"; sourceTree = "<group>"; };
+		C3F977812311B38F0032776D /* NSObject+Reflection.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSObject+Reflection.h"; sourceTree = "<group>"; };
+		C3F977822311B38F0032776D /* NSObject+Reflection.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSObject+Reflection.m"; sourceTree = "<group>"; };
 /* End PBXFileReference section */
 
 /* Begin PBXFrameworksBuildPhase section */
@@ -527,25 +583,22 @@
 		3A4C94541B5B21410088C3F2 /* Utility */ = {
 			isa = PBXGroup;
 			children = (
+				C36FBFB8230F3B52008D95D5 /* Runtime */,
 				C387C88022E0D22600750E58 /* Categories */,
 				3A4C94551B5B21410088C3F2 /* FLEXHeapEnumerator.h */,
 				3A4C94561B5B21410088C3F2 /* FLEXHeapEnumerator.m */,
 				3A4C94591B5B21410088C3F2 /* FLEXResources.h */,
 				3A4C945A1B5B21410088C3F2 /* FLEXResources.m */,
-				C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */,
-				C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */,
-				3A4C945B1B5B21410088C3F2 /* FLEXRuntimeUtility.h */,
-				3A4C945C1B5B21410088C3F2 /* FLEXRuntimeUtility.m */,
 				3A4C945D1B5B21410088C3F2 /* FLEXUtility.h */,
 				3A4C945E1B5B21410088C3F2 /* FLEXUtility.m */,
 				C38F3F2F230C958F004E3731 /* FLEXAlert.h */,
 				C38F3F30230C958F004E3731 /* FLEXAlert.m */,
+				7349FD6822B93CDF00051810 /* FLEXColor.h */,
+				7349FD6922B93CDF00051810 /* FLEXColor.m */,
 				942DCD821BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.h */,
 				942DCD831BAE0AD300DB5DC2 /* FLEXKeyboardShortcutManager.m */,
 				94AAF0361BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h */,
 				94AAF0371BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m */,
-				7349FD6822B93CDF00051810 /* FLEXColor.h */,
-				7349FD6922B93CDF00051810 /* FLEXColor.m */,
 			);
 			path = Utility;
 			sourceTree = "<group>";
@@ -798,6 +851,35 @@
 			path = Globals;
 			sourceTree = "<group>";
 		};
+		C36FBFB8230F3B52008D95D5 /* Runtime */ = {
+			isa = PBXGroup;
+			children = (
+				C37A0C91218BAC9600848CA7 /* FLEXObjcInternal.h */,
+				C37A0C92218BAC9600848CA7 /* FLEXObjcInternal.mm */,
+				3A4C945B1B5B21410088C3F2 /* FLEXRuntimeUtility.h */,
+				3A4C945C1B5B21410088C3F2 /* FLEXRuntimeUtility.m */,
+				C36FBFC5230F3B98008D95D5 /* FLEXMirror.h */,
+				C36FBFB9230F3B97008D95D5 /* FLEXMirror.m */,
+				C36FBFC0230F3B98008D95D5 /* FLEXMethodBase.h */,
+				C36FBFC3230F3B98008D95D5 /* FLEXMethodBase.m */,
+				C36FBFC1230F3B98008D95D5 /* FLEXMethod.h */,
+				C36FBFBB230F3B97008D95D5 /* FLEXMethod.m */,
+				C36FBFC4230F3B98008D95D5 /* FLEXIvar.h */,
+				C36FBFCA230F3B98008D95D5 /* FLEXIvar.m */,
+				C36FBFBC230F3B97008D95D5 /* FLEXProperty.h */,
+				C36FBFBE230F3B97008D95D5 /* FLEXProperty.m */,
+				C36FBFC6230F3B98008D95D5 /* FLEXPropertyAttributes.h */,
+				C36FBFC8230F3B98008D95D5 /* FLEXPropertyAttributes.m */,
+				C36FBFBD230F3B97008D95D5 /* FLEXProtocol.h */,
+				C36FBFC2230F3B98008D95D5 /* FLEXProtocol.m */,
+				C36FBFBA230F3B97008D95D5 /* FLEXProtocolBuilder.h */,
+				C36FBFC7230F3B98008D95D5 /* FLEXProtocolBuilder.m */,
+				C36FBFC9230F3B98008D95D5 /* FLEXClassBuilder.h */,
+				C36FBFBF230F3B98008D95D5 /* FLEXClassBuilder.m */,
+			);
+			path = Runtime;
+			sourceTree = "<group>";
+		};
 		C387C88022E0D22600750E58 /* Categories */ = {
 			isa = PBXGroup;
 			children = (
@@ -807,6 +889,16 @@
 				C3F646C0239EAA8F00D4A011 /* UIPasteboard+FLEX.m */,
 				C3BFD06E233C23ED0015FB82 /* NSArray+Functional.h */,
 				C3BFD06F233C23ED0015FB82 /* NSArray+Functional.m */,
+				C3F977812311B38F0032776D /* NSObject+Reflection.h */,
+				C3F977822311B38F0032776D /* NSObject+Reflection.m */,
+				C3F9777F2311B38F0032776D /* NSDictionary+ObjcRuntime.h */,
+				C3F9777E2311B38E0032776D /* NSDictionary+ObjcRuntime.m */,
+				C3F9777D2311B38E0032776D /* NSString+ObjcRuntime.h */,
+				C3F977802311B38F0032776D /* NSString+ObjcRuntime.m */,
+				C3E5D9FB2316E83700E655DB /* FLEXRuntime+Compare.h */,
+				C3E5D9FC2316E83700E655DB /* FLEXRuntime+Compare.m */,
+				C34C9BDB23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h */,
+				C34C9BDC23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m */,
 			);
 			path = Categories;
 			sourceTree = "<group>";
@@ -900,6 +992,7 @@
 				3A4C94DD1B5B21410088C3F2 /* FLEXHeapEnumerator.h in Headers */,
 				3A4C94DB1B5B21410088C3F2 /* FLEXViewExplorerViewController.h in Headers */,
 				3A4C95321B5B21410088C3F2 /* FLEXSystemLogTableViewCell.h in Headers */,
+				C3F977852311B38F0032776D /* NSDictionary+ObjcRuntime.h in Headers */,
 				3A4C94F91B5B21410088C3F2 /* FLEXArgumentInputNumberView.h in Headers */,
 				C387C87A22DFCD6A00750E58 /* FLEXCarouselCell.h in Headers */,
 				C3511B9122D7C99E0057BAB7 /* FLEXTableViewSection.h in Headers */,
@@ -908,11 +1001,14 @@
 				779B1ED01C0C4D7C001F5E49 /* FLEXMultiColumnTableView.h in Headers */,
 				3A4C94D31B5B21410088C3F2 /* FLEXObjectExplorerFactory.h in Headers */,
 				C38DF0EA22CFE4370077B4AD /* FLEXTableViewController.h in Headers */,
+				C36FBFCF230F3B98008D95D5 /* FLEXProtocol.h in Headers */,
 				94A515271C4CA2080063292F /* FLEXToolbarItem.h in Headers */,
 				3A4C94E31B5B21410088C3F2 /* FLEXRuntimeUtility.h in Headers */,
 				3A4C95341B5B21410088C3F2 /* FLEXSystemLogTableViewController.h in Headers */,
+				C34C9BDD23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.h in Headers */,
 				3A4C95091B5B21410088C3F2 /* FLEXFieldEditorView.h in Headers */,
 				3A4C950D1B5B21410088C3F2 /* FLEXIvarEditorViewController.h in Headers */,
+				C3E5D9FD2316E83700E655DB /* FLEXRuntime+Compare.h in Headers */,
 				3A4C95281B5B21410088C3F2 /* FLEXInstancesTableViewController.h in Headers */,
 				3A4C950F1B5B21410088C3F2 /* FLEXMethodCallingViewController.h in Headers */,
 				3A4C94F51B5B21410088C3F2 /* FLEXArgumentInputObjectView.h in Headers */,
@@ -930,19 +1026,25 @@
 				94A5151D1C4CA1F10063292F /* FLEXExplorerViewController.h in Headers */,
 				C3F31D3F2267D883003C991A /* FLEXMultilineTableViewCell.h in Headers */,
 				71E1C2132307FBB800F5032A /* FLEXKeychain.h in Headers */,
+				C36FBFD7230F3B98008D95D5 /* FLEXMirror.h in Headers */,
 				3A4C94C51B5B21410088C3F2 /* FLEXArrayExplorerViewController.h in Headers */,
 				C3F31D432267D883003C991A /* FLEXTableView.h in Headers */,
 				3A4C94CB1B5B21410088C3F2 /* FLEXDictionaryExplorerViewController.h in Headers */,
 				3A4C95071B5B21410088C3F2 /* FLEXDefaultEditorViewController.h in Headers */,
 				C309B82F223ED64400B228EC /* FLEXLogController.h in Headers */,
+				C3F977832311B38F0032776D /* NSString+ObjcRuntime.h in Headers */,
 				94A5151F1C4CA1F10063292F /* FLEXWindow.h in Headers */,
 				779B1ECE1C0C4D7C001F5E49 /* FLEXDatabaseManager.h in Headers */,
 				3A4C94D51B5B21410088C3F2 /* FLEXObjectExplorerViewController.h in Headers */,
 				3A4C95011B5B21410088C3F2 /* FLEXArgumentInputTextView.h in Headers */,
 				3A4C952A1B5B21410088C3F2 /* FLEXLibrariesTableViewController.h in Headers */,
+				C36FBFCC230F3B98008D95D5 /* FLEXProtocolBuilder.h in Headers */,
+				C36FBFDB230F3B98008D95D5 /* FLEXClassBuilder.h in Headers */,
 				779B1ED41C0C4D7C001F5E49 /* FLEXTableContentCell.h in Headers */,
 				3A4C952C1B5B21410088C3F2 /* FLEXLiveObjectsTableViewController.h in Headers */,
 				3A4C94EF1B5B21410088C3F2 /* FLEXArgumentInputDateView.h in Headers */,
+				C36FBFCE230F3B98008D95D5 /* FLEXProperty.h in Headers */,
+				C36FBFD2230F3B98008D95D5 /* FLEXMethodBase.h in Headers */,
 				71E1C2142307FBB800F5032A /* FLEXKeychainTableViewController.h in Headers */,
 				3A4C94C71B5B21410088C3F2 /* FLEXClassExplorerViewController.h in Headers */,
 				3A4C94F71B5B21410088C3F2 /* FLEXArgumentInputNotSupportedView.h in Headers */,
@@ -965,16 +1067,20 @@
 				3A4C95261B5B21410088C3F2 /* FLEXGlobalsTableViewController.h in Headers */,
 				224D49A81C673AB5000EAB86 /* FLEXRealmDatabaseManager.h in Headers */,
 				7349FD6A22B93CDF00051810 /* FLEXColor.h in Headers */,
+				C36FBFD3230F3B98008D95D5 /* FLEXMethod.h in Headers */,
+				C36FBFD8230F3B98008D95D5 /* FLEXPropertyAttributes.h in Headers */,
 				3A4C94CD1B5B21410088C3F2 /* FLEXGlobalsEntry.h in Headers */,
 				3A4C94FB1B5B21410088C3F2 /* FLEXArgumentInputStringView.h in Headers */,
 				3A4C95421B5B21410088C3F2 /* FLEXNetworkObserver.h in Headers */,
 				C34A70A022B2EC8D009C2C5F /* FLEXBundleExplorerViewController.h in Headers */,
 				679F64861BD53B7B00A8C94C /* FLEXCookiesTableViewController.h in Headers */,
+				C3F977872311B38F0032776D /* NSObject+Reflection.h in Headers */,
 				C3DB9F642107FC9600B46809 /* FLEXObjectRef.h in Headers */,
 				3A4C95401B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.h in Headers */,
 				3A4C95241B5B21410088C3F2 /* FLEXFileBrowserTableViewController.h in Headers */,
 				94AAF0381BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.h in Headers */,
 				71E1C2152307FBB800F5032A /* FLEXKeychainQuery.h in Headers */,
+				C36FBFD6230F3B98008D95D5 /* FLEXIvar.h in Headers */,
 				94AAF03A1BAF2F0300DE8760 /* FLEXKeyboardShortcutManager.h in Headers */,
 				C395D6D921789BD800BEAD4D /* FLEXColorExplorerViewController.h in Headers */,
 				94A515181C4CA1D70063292F /* FLEXManager+Private.h in Headers */,
@@ -1093,8 +1199,10 @@
 			buildActionMask = 2147483647;
 			files = (
 				942DCD871BAE0CA300DB5DC2 /* FLEXKeyboardShortcutManager.m in Sources */,
+				C3F977862311B38F0032776D /* NSString+ObjcRuntime.m in Sources */,
 				224D49A91C673AB5000EAB86 /* FLEXRealmDatabaseManager.m in Sources */,
 				C39ED92922D63F3200B5773A /* FLEXAddressExplorerCoordinator.m in Sources */,
+				C34C9BDE23A7F2740031CA3E /* FLEXRuntime+UIKitHelpers.m in Sources */,
 				2EF6B04D1D494BE50006BDA5 /* FLEXNetworkCurlLogger.m in Sources */,
 				94A515201C4CA1F10063292F /* FLEXWindow.m in Sources */,
 				3A4C95121B5B21410088C3F2 /* FLEXPropertyEditorViewController.m in Sources */,
@@ -1111,22 +1219,27 @@
 				94AAF0391BAF2E1F00DE8760 /* FLEXKeyboardHelpViewController.m in Sources */,
 				3A4C951F1B5B21410088C3F2 /* FLEXClassesTableViewController.m in Sources */,
 				3A4C94C61B5B21410088C3F2 /* FLEXArrayExplorerViewController.m in Sources */,
+				C36FBFD4230F3B98008D95D5 /* FLEXProtocol.m in Sources */,
 				C38F3F32230C958F004E3731 /* FLEXAlert.m in Sources */,
 				C3DA55FF21A76406005DDA60 /* FLEXMutableFieldEditorViewController.m in Sources */,
 				3A4C95081B5B21410088C3F2 /* FLEXDefaultEditorViewController.m in Sources */,
 				779B1ED11C0C4D7C001F5E49 /* FLEXMultiColumnTableView.m in Sources */,
+				C36FBFD0230F3B98008D95D5 /* FLEXProperty.m in Sources */,
 				C3F31D412267D883003C991A /* FLEXMultilineTableViewCell.m in Sources */,
 				3A4C94EE1B5B21410088C3F2 /* FLEXArgumentInputColorView.m in Sources */,
 				C38DF0EB22CFE4370077B4AD /* FLEXTableViewController.m in Sources */,
 				C387C87B22DFCD6A00750E58 /* FLEXCarouselCell.m in Sources */,
 				3A4C94F01B5B21410088C3F2 /* FLEXArgumentInputDateView.m in Sources */,
 				3A4C94CA1B5B21410088C3F2 /* FLEXDefaultsExplorerViewController.m in Sources */,
+				C36FBFDC230F3B98008D95D5 /* FLEXIvar.m in Sources */,
 				3A4C94D01B5B21410088C3F2 /* FLEXImageExplorerViewController.m in Sources */,
 				679F64871BD53B7B00A8C94C /* FLEXCookiesTableViewController.m in Sources */,
 				3A4C94CE1B5B21410088C3F2 /* FLEXGlobalsEntry.m in Sources */,
+				C3E5D9FE2316E83700E655DB /* FLEXRuntime+Compare.m in Sources */,
 				71E1C2192307FBB800F5032A /* FLEXKeychainQuery.m in Sources */,
 				3A4C94FE1B5B21410088C3F2 /* FLEXArgumentInputStructView.m in Sources */,
 				C3F31D402267D883003C991A /* FLEXSubtitleTableViewCell.m in Sources */,
+				C36FBFD9230F3B98008D95D5 /* FLEXProtocolBuilder.m in Sources */,
 				3A4C95431B5B21410088C3F2 /* FLEXNetworkObserver.m in Sources */,
 				3A4C94D21B5B21410088C3F2 /* FLEXLayerExplorerViewController.m in Sources */,
 				779B1EDB1C0C4D7C001F5E49 /* FLEXTableListViewController.m in Sources */,
@@ -1136,10 +1249,12 @@
 				3A4C952F1B5B21410088C3F2 /* FLEXWebViewController.m in Sources */,
 				3A4C94DC1B5B21410088C3F2 /* FLEXViewExplorerViewController.m in Sources */,
 				3A4C95041B5B21410088C3F2 /* FLEXArgumentInputView.m in Sources */,
+				C3F977842311B38F0032776D /* NSDictionary+ObjcRuntime.m in Sources */,
 				3A4C95411B5B21410088C3F2 /* FLEXNetworkTransactionTableViewCell.m in Sources */,
 				3A4C94D61B5B21410088C3F2 /* FLEXObjectExplorerViewController.m in Sources */,
 				3A4C94DE1B5B21410088C3F2 /* FLEXHeapEnumerator.m in Sources */,
 				3A4C95251B5B21410088C3F2 /* FLEXFileBrowserTableViewController.m in Sources */,
+				C36FBFD1230F3B98008D95D5 /* FLEXClassBuilder.m in Sources */,
 				3A4C94DA1B5B21410088C3F2 /* FLEXViewControllerExplorerViewController.m in Sources */,
 				779B1ED51C0C4D7C001F5E49 /* FLEXTableContentCell.m in Sources */,
 				3A4C94E21B5B21410088C3F2 /* FLEXResources.m in Sources */,
@@ -1155,15 +1270,19 @@
 				3A4C95271B5B21410088C3F2 /* FLEXGlobalsTableViewController.m in Sources */,
 				71E1C2172307FBB800F5032A /* FLEXKeychain.m in Sources */,
 				C387C88422E0D24A00750E58 /* UIView+FLEX_Layout.m in Sources */,
+				C36FBFCD230F3B98008D95D5 /* FLEXMethod.m in Sources */,
 				779B1ED31C0C4D7C001F5E49 /* FLEXTableColumnHeader.m in Sources */,
 				C37A0C94218BAC9600848CA7 /* FLEXObjcInternal.mm in Sources */,
 				3A4C94EA1B5B21410088C3F2 /* FLEXHierarchyTableViewController.m in Sources */,
 				3A4C95331B5B21410088C3F2 /* FLEXSystemLogTableViewCell.m in Sources */,
 				C3DB9F652107FC9600B46809 /* FLEXObjectRef.m in Sources */,
 				3A4C95021B5B21410088C3F2 /* FLEXArgumentInputTextView.m in Sources */,
+				C3F977882311B38F0032776D /* NSObject+Reflection.m in Sources */,
 				94A515261C4CA2080063292F /* FLEXExplorerToolbar.m in Sources */,
 				3A4C94FA1B5B21410088C3F2 /* FLEXArgumentInputNumberView.m in Sources */,
 				779B1ED71C0C4D7C001F5E49 /* FLEXTableContentViewController.m in Sources */,
+				C36FBFCB230F3B98008D95D5 /* FLEXMirror.m in Sources */,
+				C36FBFD5230F3B98008D95D5 /* FLEXMethodBase.m in Sources */,
 				C395D6DA21789BD800BEAD4D /* FLEXColorExplorerViewController.m in Sources */,
 				C34A70A122B2EC8D009C2C5F /* FLEXBundleExplorerViewController.m in Sources */,
 				3A4C95001B5B21410088C3F2 /* FLEXArgumentInputSwitchView.m in Sources */,
@@ -1171,6 +1290,7 @@
 				C3F31D442267D883003C991A /* FLEXTableView.m in Sources */,
 				3A4C953F1B5B21410088C3F2 /* FLEXNetworkTransactionDetailTableViewController.m in Sources */,
 				224D49AB1C673AB5000EAB86 /* FLEXSQLiteDatabaseManager.m in Sources */,
+				C36FBFDA230F3B98008D95D5 /* FLEXPropertyAttributes.m in Sources */,
 				779B1ED91C0C4D7C001F5E49 /* FLEXTableLeftCell.m in Sources */,
 				3A4C94E61B5B21410088C3F2 /* FLEXUtility.m in Sources */,
 				3A4C95231B5B21410088C3F2 /* FLEXFileBrowserSearchOperation.m in Sources */,