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

Add support for class properties

Tanner Bennett лет назад: 6
Родитель
Сommit
c098bb2c5e

+ 3 - 0
Classes/ObjectExplorers/FLEXObjectExplorer.h

@@ -34,6 +34,9 @@
 @property (nonatomic, readonly) NSArray<NSArray<FLEXProperty *> *> *allProperties;
 @property (nonatomic, readonly) NSArray<NSArray<FLEXProperty *> *> *allProperties;
 @property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
 @property (nonatomic, readonly) NSArray<FLEXProperty *> *properties;
 
 
+@property (nonatomic, readonly) NSArray<NSArray<FLEXProperty *> *> *allClassProperties;
+@property (nonatomic, readonly) NSArray<FLEXProperty *> *classProperties;
+
 @property (nonatomic, readonly) NSArray<NSArray<FLEXIvar *> *> *allIvars;
 @property (nonatomic, readonly) NSArray<NSArray<FLEXIvar *> *> *allIvars;
 @property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
 @property (nonatomic, readonly) NSArray<FLEXIvar *> *ivars;
 
 

+ 23 - 7
Classes/ObjectExplorers/FLEXObjectExplorer.m

@@ -18,6 +18,7 @@
 
 
 @interface FLEXObjectExplorer () {
 @interface FLEXObjectExplorer () {
     NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
     NSMutableArray<NSArray<FLEXProperty *> *> *_allProperties;
+    NSMutableArray<NSArray<FLEXProperty *> *> *_allClassProperties;
     NSMutableArray<NSArray<FLEXIvar *> *> *_allIvars;
     NSMutableArray<NSArray<FLEXIvar *> *> *_allIvars;
     NSMutableArray<NSArray<FLEXMethod *> *> *_allMethods;
     NSMutableArray<NSArray<FLEXMethod *> *> *_allMethods;
     NSMutableArray<NSArray<FLEXMethod *> *> *_allClassMethods;
     NSMutableArray<NSArray<FLEXMethod *> *> *_allClassMethods;
@@ -82,6 +83,7 @@
 
 
 - (void)reloadMetadata {
 - (void)reloadMetadata {
     _allProperties = [NSMutableArray new];
     _allProperties = [NSMutableArray new];
+    _allClassProperties = [NSMutableArray new];
     _allIvars = [NSMutableArray new];
     _allIvars = [NSMutableArray new];
     _allMethods = [NSMutableArray new];
     _allMethods = [NSMutableArray new];
     _allClassMethods = [NSMutableArray new];
     _allClassMethods = [NSMutableArray new];
@@ -102,6 +104,11 @@
             superclass:superclass
             superclass:superclass
             kind:FLEXMetadataKindProperties
             kind:FLEXMetadataKindProperties
         ]];
         ]];
+        [_allClassProperties addObject:[self
+            metadataUniquedByName:[cls flex_allClassProperties]
+            superclass:superclass
+            kind:FLEXMetadataKindClassProperties
+        ]];
         [_allIvars addObject:[self
         [_allIvars addObject:[self
             metadataUniquedByName:[cls flex_allIvars]
             metadataUniquedByName:[cls flex_allIvars]
             superclass:nil
             superclass:nil
@@ -136,6 +143,7 @@
 
 
 - (void)reloadScopedMetadata {
 - (void)reloadScopedMetadata {
     _properties = self.allProperties[self.classScope];
     _properties = self.allProperties[self.classScope];
+    _classProperties = self.allClassProperties[self.classScope];
     _ivars = self.allIvars[self.classScope];
     _ivars = self.allIvars[self.classScope];
     _methods = self.allMethods[self.classScope];
     _methods = self.allMethods[self.classScope];
     _classMethods = self.allClassMethods[self.classScope];
     _classMethods = self.allClassMethods[self.classScope];
@@ -161,11 +169,11 @@
                         return nil;
                         return nil;
                     }
                     }
                     break;
                     break;
-//                case FLEXMetadataKindClassProperties:
-//                    if ([superclass instancesRespondToSelector:[obj likelyGetter]]) {
-//                        return nil;
-//                    }
-//                    break;
+                case FLEXMetadataKindClassProperties:
+                    if ([superclass respondsToSelector:[obj likelyGetter]]) {
+                        return nil;
+                    }
+                    break;
                 case FLEXMetadataKindMethods:
                 case FLEXMetadataKindMethods:
                     if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
                     if ([superclass instancesRespondToSelector:NSSelectorFromString(name)]) {
                         return nil;
                         return nil;
@@ -191,10 +199,18 @@
 
 
 - (id)valueForProperty:(FLEXProperty *)property {
 - (id)valueForProperty:(FLEXProperty *)property {
     if (self.objectIsInstance) {
     if (self.objectIsInstance) {
+        if (property.isClassProperty) {
+            return [property getPotentiallyUnboxedValue:[self.object class]];
+        }
         return [property getPotentiallyUnboxedValue:self.object];
         return [property getPotentiallyUnboxedValue:self.object];
+    } else {
+        if (property.isClassProperty) {
+            return [property getPotentiallyUnboxedValue:self.object];
+        } else {
+            // Instance property with a class object
+            return nil;
+        }
     }
     }
-    
-    return nil;
 }
 }
 
 
 - (id)valueForIvar:(FLEXIvar *)ivar {
 - (id)valueForIvar:(FLEXIvar *)ivar {

+ 2 - 1
Classes/ObjectExplorers/FLEXObjectExplorerViewController.m

@@ -164,6 +164,7 @@
     _referencesSection = [FLEXSingleRowSection
     _referencesSection = [FLEXSingleRowSection
         title:@"Object Graph" reuse:kFLEXDefaultCell cell:^(FLEXTableViewCell *cell) {
         title:@"Object Graph" reuse:kFLEXDefaultCell cell:^(FLEXTableViewCell *cell) {
             cell.titleLabel.text = @"Other objects with ivars referencing this object";
             cell.titleLabel.text = @"Other objects with ivars referencing this object";
+            cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
         }
         }
     ];
     ];
     self.referencesSection.selectionAction = ^(UIViewController *host) {
     self.referencesSection.selectionAction = ^(UIViewController *host) {
@@ -175,7 +176,7 @@
 
 
     NSMutableArray *sections = [NSMutableArray arrayWithArray:@[
     NSMutableArray *sections = [NSMutableArray arrayWithArray:@[
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindProperties],
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindProperties],
-//        [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindClassProperties],
+        [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindClassProperties],
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindIvars],
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindIvars],
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindMethods],
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindMethods],
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindClassMethods],
         [FLEXMetadataSection explorer:self.explorer kind:FLEXMetadataKindClassMethods],

+ 1 - 1
Classes/ObjectExplorers/Sections/FLEXMetadataSection.h

@@ -9,9 +9,9 @@
 #import "FLEXExplorerSection.h"
 #import "FLEXExplorerSection.h"
 #import "FLEXObjectExplorer.h"
 #import "FLEXObjectExplorer.h"
 
 
-#warning Missing ClassProperties
 typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
 typedef NS_ENUM(NSUInteger, FLEXMetadataKind) {
     FLEXMetadataKindProperties = 1,
     FLEXMetadataKindProperties = 1,
+    FLEXMetadataKindClassProperties,
     FLEXMetadataKindIvars,
     FLEXMetadataKindIvars,
     FLEXMetadataKindMethods,
     FLEXMetadataKindMethods,
     FLEXMetadataKindClassMethods
     FLEXMetadataKindClassMethods

+ 38 - 26
Classes/ObjectExplorers/Sections/FLEXMetadataSection.m

@@ -61,6 +61,11 @@
     id target = self.explorer.object;
     id target = self.explorer.object;
 
 
     switch (self.metadataKind) {
     switch (self.metadataKind) {
+        case FLEXMetadataKindClassProperties:
+            if (self.explorer.objectIsInstance) {
+                target = [target class];
+            }
+            // Fallthrough
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindIvars: {
         case FLEXMetadataKindIvars: {
             // This works for both properties and ivars, we just
             // This works for both properties and ivars, we just
@@ -70,11 +75,10 @@
             return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
             return [FLEXObjectExplorerFactory explorerViewControllerForObject:currentValue];
         }
         }
         case FLEXMetadataKindClassMethods:
         case FLEXMetadataKindClassMethods:
-            // Make sure the target is a class
-            // for class methods, then fall through
-            if (self.explorer.objectIsInstance && ![metadata isInstanceMethod]) {
+            if (self.explorer.objectIsInstance) {
                 target = [target class];
                 target = [target class];
             }
             }
+            // Fallthrough
         case FLEXMetadataKindMethods: {
         case FLEXMetadataKindMethods: {
             return [FLEXMethodCallingViewController target:target method:metadata];
             return [FLEXMethodCallingViewController target:target method:metadata];
         }
         }
@@ -91,6 +95,8 @@
     switch (self.metadataKind) {
     switch (self.metadataKind) {
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindProperties:
             return [self.explorer valueForProperty:metadata];
             return [self.explorer valueForProperty:metadata];
+        case FLEXMetadataKindClassProperties:
+            return [self.explorer valueForProperty:metadata];
         case FLEXMetadataKindIvars:
         case FLEXMetadataKindIvars:
             return [self.explorer valueForIvar:metadata];
             return [self.explorer valueForIvar:metadata];
 
 
@@ -125,25 +131,17 @@
     // because we want to "preview" what object is being stored if this is
     // because we want to "preview" what object is being stored if this is
     // a void * or something and we're given an NSValue back from getValue:
     // a void * or something and we're given an NSValue back from getValue:
     switch (self.metadataKind) {
     switch (self.metadataKind) {
+        case FLEXMetadataKindClassProperties:
+            return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindIvars:
         case FLEXMetadataKindIvars:
-            #warning TODO: fix this logic for class properties
             if (!self.explorer.objectIsInstance) {
             if (!self.explorer.objectIsInstance) {
-                if (self.metadataKind == FLEXMetadataKindProperties) {
-                    if (![metadata isClassProperty]) {
-                        return nil;
-                    }
-                } else {
-                    return nil;
-                }
+                return nil;
             }
             }
             return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
             return [FLEXRuntimeUtility summaryForObject:[self valueForRow:row]];
         case FLEXMetadataKindMethods:
         case FLEXMetadataKindMethods:
         case FLEXMetadataKindClassMethods:
         case FLEXMetadataKindClassMethods:
             return [metadata selectorString];
             return [metadata selectorString];
-
-        default:
-            return nil;
     }
     }
 }
 }
 
 
@@ -151,14 +149,14 @@
     switch (self.metadataKind) {
     switch (self.metadataKind) {
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindProperties:
             return [self titleWithBaseName:@"Properties"];
             return [self titleWithBaseName:@"Properties"];
+        case FLEXMetadataKindClassProperties:
+            return [self titleWithBaseName:@"Class Properties"];
         case FLEXMetadataKindIvars:
         case FLEXMetadataKindIvars:
             return [self titleWithBaseName:@"Ivars"];
             return [self titleWithBaseName:@"Ivars"];
         case FLEXMetadataKindMethods:
         case FLEXMetadataKindMethods:
             return [self titleWithBaseName:@"Methods"];
             return [self titleWithBaseName:@"Methods"];
         case FLEXMetadataKindClassMethods:
         case FLEXMetadataKindClassMethods:
-            return [self titleWithBaseName:@"Class methods"];
-
-        default: return nil;
+            return [self titleWithBaseName:@"Class Methods"];
     }
     }
 }
 }
 
 
@@ -183,6 +181,9 @@
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindProperties:
             self.allMetadata = self.explorer.properties;
             self.allMetadata = self.explorer.properties;
             break;
             break;
+        case FLEXMetadataKindClassProperties:
+            self.allMetadata = self.explorer.classProperties;
+            break;
         case FLEXMetadataKindIvars:
         case FLEXMetadataKindIvars:
             self.allMetadata = self.explorer.ivars;
             self.allMetadata = self.explorer.ivars;
             break;
             break;
@@ -220,9 +221,8 @@
         case FLEXMetadataKindMethods:
         case FLEXMetadataKindMethods:
             return self.explorer.objectIsInstance;
             return self.explorer.objectIsInstance;
         case FLEXMetadataKindClassMethods:
         case FLEXMetadataKindClassMethods:
+        case FLEXMetadataKindClassProperties:
             return YES;
             return YES;
-
-        default: return NO;
     }
     }
 }
 }
 
 
@@ -243,21 +243,33 @@
 - (UIViewController *)editorForRow:(NSInteger)row {
 - (UIViewController *)editorForRow:(NSInteger)row {
     NSAssert(
     NSAssert(
         self.metadataKind == FLEXMetadataKindIvars ||
         self.metadataKind == FLEXMetadataKindIvars ||
-        self.metadataKind == FLEXMetadataKindProperties, // ||,
-//        self.metadataKind == FLEXMetadataKindClassProperties,
+        self.metadataKind == FLEXMetadataKindProperties ||
+        self.metadataKind == FLEXMetadataKindClassProperties,
         @"Only ivars or properties can be edited"
         @"Only ivars or properties can be edited"
     );
     );
 
 
     id metadata = self.metadata[row];
     id metadata = self.metadata[row];
 
 
     // Nil editor means unsupported ivar or property type, or nil value
     // Nil editor means unsupported ivar or property type, or nil value
-    if (self.metadataKind == FLEXMetadataKindProperties) {
-        return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
-    } else if (self.metadataKind == FLEXMetadataKindIvars) {
-        return [FLEXFieldEditorViewController target:self.explorer.object ivar:metadata];
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+            NSAssert(self.explorer.objectIsInstance, @"Unreachable state: class object editing instance property");
+            return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
+        case FLEXMetadataKindClassProperties:
+            if (self.explorer.objectIsInstance) {
+                return [FLEXFieldEditorViewController target:[self.explorer.object class] property:metadata];
+            } else {
+                return [FLEXFieldEditorViewController target:self.explorer.object property:metadata];
+            }
+        case FLEXMetadataKindIvars:
+            NSAssert(self.explorer.objectIsInstance, @"Unreachable state: class object editing ivar");
+            return [FLEXFieldEditorViewController target:self.explorer.object ivar:metadata];
+
+        default:
+            break;
     }
     }
 
 
-    // TODO: support class properties
+    // Methods can't be edited
     @throw NSInternalInconsistencyException;
     @throw NSInternalInconsistencyException;
     return nil;
     return nil;
 }
 }

+ 55 - 11
Classes/ObjectExplorers/Sections/Shortcuts/FLEXShortcut.m

@@ -39,8 +39,11 @@
     shortcut->_item = item;
     shortcut->_item = item;
 
 
     if ([item isKindOfClass:[FLEXProperty class]]) {
     if ([item isKindOfClass:[FLEXProperty class]]) {
-        // We don't care if it's a class property or not
-        shortcut->_metadataKind = FLEXMetadataKindProperties;
+        if (shortcut.property.isClassProperty) {
+            shortcut->_metadataKind =  FLEXMetadataKindClassProperties;
+        } else {
+            shortcut->_metadataKind =  FLEXMetadataKindProperties;
+        }
     }
     }
     if ([item isKindOfClass:[FLEXIvar class]]) {
     if ([item isKindOfClass:[FLEXIvar class]]) {
         shortcut->_metadataKind = FLEXMetadataKindIvars;
         shortcut->_metadataKind = FLEXMetadataKindIvars;
@@ -53,15 +56,41 @@
     return shortcut;
     return shortcut;
 }
 }
 
 
-- (id)propertyOrIvarValue:(id)fromObject {
+- (id)propertyOrIvarValue:(id)object {
+    BOOL objectIsInstance = !object_isClass(object);
+
     // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
     // We use -[FLEXObjectExplorer valueFor...:] instead of getValue: below
     // because we want to "preview" what object is being stored if this is
     // because we want to "preview" what object is being stored if this is
     // a void * or something and we're given an NSValue back from getValue:
     // a void * or something and we're given an NSValue back from getValue:
     switch (self.metadataKind) {
     switch (self.metadataKind) {
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindProperties:
-            return [self.property getPotentiallyUnboxedValue:fromObject];
-        case FLEXMetadataKindIvars:
-            return [self.ivar getPotentiallyUnboxedValue:fromObject];
+        case FLEXMetadataKindClassProperties: {
+            // Decide whether to use object or [object class] to check for a potential value
+            id targetForValueCheck = nil;
+            if (objectIsInstance) {
+                if (self.property.isClassProperty) {
+                    targetForValueCheck = [object class];
+                } else {
+                    targetForValueCheck = object;
+                }
+            } else {
+                if (self.property.isClassProperty) {
+                    targetForValueCheck = object;
+                } else {
+                    // Instance property with a class object
+                    return nil;
+                }
+            }
+
+            return [self.property getPotentiallyUnboxedValue:targetForValueCheck];
+        }
+        case FLEXMetadataKindIvars: {
+            if (objectIsInstance) {
+                return [self.ivar getPotentiallyUnboxedValue:object];
+            }
+
+            return nil;
+        }
 
 
         // Methods: nil
         // Methods: nil
         case FLEXMetadataKindMethods:
         case FLEXMetadataKindMethods:
@@ -72,6 +101,7 @@
 
 
 - (NSString *)titleWith:(id)object {
 - (NSString *)titleWith:(id)object {
     switch (self.metadataKind) {
     switch (self.metadataKind) {
+        case FLEXMetadataKindClassProperties:
         case FLEXMetadataKindProperties:
         case FLEXMetadataKindProperties:
             // Since we're outside of the "properties" section, prepend @property for clarity.
             // Since we're outside of the "properties" section, prepend @property for clarity.
             return [@"@property " stringByAppendingString:[_item description]];
             return [@"@property " stringByAppendingString:[_item description]];
@@ -146,13 +176,27 @@
 }
 }
 
 
 - (UIViewController *)editorWith:(id)object {
 - (UIViewController *)editorWith:(id)object {
-    // Nil editor means unsupported ivar or property type, or nil value
-    if (self.metadataKind == FLEXMetadataKindProperties) {
-        return [FLEXFieldEditorViewController target:object property:self.property];
-    } else if (self.metadataKind == FLEXMetadataKindIvars) {
-        return [FLEXFieldEditorViewController target:object ivar:self.ivar];
+    BOOL objectIsInstance = !object_isClass(object);
+    switch (self.metadataKind) {
+        case FLEXMetadataKindProperties:
+            NSAssert(objectIsInstance, @"Unreachable state: class object editing instance property");
+            return [FLEXFieldEditorViewController target:object property:self.property];
+        case FLEXMetadataKindClassProperties:
+            if (objectIsInstance) {
+                return [FLEXFieldEditorViewController target:[object class] property:self.property];
+            } else {
+                return [FLEXFieldEditorViewController target:object property:self.property];
+            }
+        case FLEXMetadataKindIvars:
+            NSAssert(objectIsInstance, @"Unreachable state: class object editing ivar");
+            return [FLEXFieldEditorViewController target:object ivar:self.ivar];
+
+        default:
+            break;
     }
     }
 
 
+    // Methods can't be edited
+    @throw NSInternalInconsistencyException;
     return nil;
     return nil;
 }
 }
 
 

+ 18 - 3
Classes/Utility/Categories/FLEXRuntime+UIKitHelpers.m

@@ -27,13 +27,28 @@
 }
 }
 
 
 - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
 - (UITableViewCellAccessoryType)suggestedAccessoryTypeWithTarget:(id)object {
-    if (object_isClass(object) && !self.isClassProperty) {
-        return UITableViewCellAccessoryNone;
+    BOOL objectIsInstance = !object_isClass(object);
+
+    // Decide whether to use object or [object class] to check for a potential value
+    id targetForValueCheck = nil;
+    if (objectIsInstance) {
+        if (self.isClassProperty) {
+            targetForValueCheck = [object class];
+        } else {
+            targetForValueCheck = object;
+        }
+    } else {
+        if (self.isClassProperty) {
+            targetForValueCheck = object;
+        } else {
+            // Instance property with a class object
+            return UITableViewCellAccessoryNone;
+        }
     }
     }
 
 
     // We use .tag to store the cached value of .isEditable that is
     // We use .tag to store the cached value of .isEditable that is
     // initialized by FLEXObjectExplorer in -reloadMetada
     // initialized by FLEXObjectExplorer in -reloadMetada
-    if ([self getPotentiallyUnboxedValue:object]) {
+    if ([self getPotentiallyUnboxedValue:targetForValueCheck]) {
         if (self.tag) {
         if (self.tag) {
             // Editable non-nil value, both
             // Editable non-nil value, both
             return UITableViewCellAccessoryDetailDisclosureButton;
             return UITableViewCellAccessoryDetailDisclosureButton;

+ 4 - 1
Classes/Utility/Runtime/FLEXProperty.h

@@ -69,10 +69,13 @@
 /// A source-like description of the property, with all of its attributes.
 /// A source-like description of the property, with all of its attributes.
 @property (nonatomic, readonly) NSString *fullDescription;
 @property (nonatomic, readonly) NSString *fullDescription;
 
 
+/// If this is a class property, you must class the class object.
 - (id)getValue:(id)target;
 - (id)getValue:(id)target;
 /// Calls into -getValue: and passes that value into
 /// Calls into -getValue: and passes that value into
 /// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
 /// -[FLEXRuntimeUtility potentiallyUnwrapBoxedPointer:type:]
-/// and returns the result
+/// and returns the result.
+///
+/// If this is a class property, you must class the class object.
 - (id)getPotentiallyUnboxedValue:(id)target;
 - (id)getPotentiallyUnboxedValue:(id)target;
 
 
 /// Safe to use regardless of how the \c FLEXProperty instance was initialized.
 /// Safe to use regardless of how the \c FLEXProperty instance was initialized.

+ 35 - 13
Classes/Utility/Runtime/FLEXProperty.m

@@ -89,18 +89,34 @@
 - (void)examine {
 - (void)examine {
     _type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
     _type = (FLEXTypeEncoding)[self.attributes.typeEncoding characterAtIndex:0];
 
 
-    _likelyGetter = self.attributes.customGetter ?: NSSelectorFromString(self.name);
-    _likelySetter = self.attributes.customSetter ?: NSSelectorFromString([NSString
+    // Return the given selector if the class responds to it
+    Class cls = _cls;
+    SEL (^selectorIfValid)() = ^SEL(SEL sel) {
+        if (!sel || !cls) return nil;
+        return [cls instancesRespondToSelector:sel] ? sel : nil;
+    };
+
+    SEL customGetter = self.attributes.customGetter;
+    SEL customSetter = self.attributes.customSetter;
+    SEL defaultGetter = NSSelectorFromString(self.name);
+    SEL defaultSetter = NSSelectorFromString([NSString
         stringWithFormat:@"set%c%@:",
         stringWithFormat:@"set%c%@:",
         (char)toupper([self.name characterAtIndex:0]),
         (char)toupper([self.name characterAtIndex:0]),
         [self.name substringFromIndex:1]
         [self.name substringFromIndex:1]
     ]);
     ]);
 
 
-    if (_cls) {
-        _likelySetterExists = [_cls instancesRespondToSelector:_likelySetter];
-        _likelyGetterExists = [_cls instancesRespondToSelector:_likelyGetter];
-        _isClassProperty = class_isMetaClass(_cls);
-    }
+    // Check if the likely getters/setters exist
+    SEL validGetter = selectorIfValid(customGetter) ?: selectorIfValid(defaultGetter);
+    SEL validSetter = selectorIfValid(customSetter) ?: selectorIfValid(defaultSetter);
+    _likelyGetterExists = validGetter != nil;
+    _likelySetterExists = validSetter != nil;
+
+    // Assign likely getters and setters to the valid one,
+    // or the default, regardless of whether the default exists
+    _likelyGetter = validGetter ?: defaultGetter;
+    _likelySetter = validSetter ?: defaultSetter;
+
+    _isClassProperty = _cls ? class_isMetaClass(_cls) : NO;
 }
 }
 
 
 #pragma mark Overrides
 #pragma mark Overrides
@@ -200,12 +216,18 @@
 }
 }
 
 
 - (id)getValue:(id)target {
 - (id)getValue:(id)target {
-    // Try custom getter first, then property name
-    SEL customGetter = self.attributes.customGetter;
-    if (customGetter && [target respondsToSelector:customGetter]) {
-        return [target valueForKey:NSStringFromSelector(customGetter)];
-    } else if ([target respondsToSelector:NSSelectorFromString(self.name)]) {
-        return [target valueForKey:self.name];
+    // We don't care about checking dynamically whether the getter
+    // _now_ exists on this object. If the getter doesn't exist
+    // when this property is initialized, it will never call it.
+    // Just re-create the property object if you need to call it.
+    if (self.likelyGetterExists) {
+        BOOL objectIsClass = object_isClass(target);
+        BOOL instanceAndInstanceProperty = !objectIsClass && !self.isClassProperty;
+        BOOL classAndClassProperty = objectIsClass && self.isClassProperty;
+
+        if (instanceAndInstanceProperty || classAndClassProperty) {
+            return [FLEXRuntimeUtility performSelector:self.likelyGetter onObject:target];
+        }
     }
     }
 
 
     return nil;
     return nil;

+ 1 - 0
Classes/Utility/Runtime/FLEXRuntimeUtility.h

@@ -110,6 +110,7 @@ typedef NS_ENUM(char, FLEXTypeEncoding)
 + (NSArray *)prettyArgumentComponentsForMethod:(Method)method;
 + (NSArray *)prettyArgumentComponentsForMethod:(Method)method;
 
 
 // Method Calling/Field Editing
 // Method Calling/Field Editing
++ (id)performSelector:(SEL)selector onObject:(id)object;
 + (id)performSelector:(SEL)selector
 + (id)performSelector:(SEL)selector
              onObject:(id)object
              onObject:(id)object
         withArguments:(NSArray *)arguments
         withArguments:(NSArray *)arguments

+ 5 - 0
Classes/Utility/Runtime/FLEXRuntimeUtility.m

@@ -274,6 +274,11 @@ const unsigned int kFLEXNumberOfImplicitArgs = 2;
 
 
 #pragma mark - Method Calling/Field Editing (Public)
 #pragma mark - Method Calling/Field Editing (Public)
 
 
++ (id)performSelector:(SEL)selector onObject:(id)object
+{
+    return [self performSelector:selector onObject:object withArguments:@[] error:nil];
+}
+
 + (id)performSelector:(SEL)selector
 + (id)performSelector:(SEL)selector
              onObject:(id)object
              onObject:(id)object
         withArguments:(NSArray *)arguments
         withArguments:(NSArray *)arguments