7

Problem


I've come across an interesting issue and wasn't able to find any documentation on it... Sometimes properties declared in a protocol are not implemented in a particular class conforming to that protocol and a runtime exception occurs. Are dynamic property definitions optimized away under some strange circumstance? Can protocols not be used with properties that were made to be declared dynamic? Any insight into this would be greatly appreciated.

Below are some more details.

Given a protocol:

@protocol MyProtocol <NSObject>
    @property (nonatomic, strong) id someProperty;
@end

and a class implementing the protocol like so:

@interface MyClass <MyProtocol>
@end

@implementation MyClass
@dynamic someProperty;
@end

I've noticed that sometimes I am unable to get any information from calling

class_getProperty(myClass, propertyName);

for the properties in the protocol. This only happens to some classes and seems to be sporadic.

I'm running the latest Xcode 4 and linking against the iOS 6 SDK. I do have the pre-release Xcode 5 installed on the same machine though it is not the default (via xcode-select).

Example


If you run this code:

@protocol MyProtocol <NSObject>

@property (nonatomic, strong) id someData;

@end

@interface MyObject : NSObject <MyProtocol>

@end

@implementation MyObject

@dynamic someData;

@end

and then you run

const char *name = [@"someData" UTF8String];
objc_property_t property = class_getProperty([MyObject class], name);
const char *attributes = property_getAttributes(property);

You WILL get meta data on the property EVEN THOUGH the property doesn't exist. In other words you don't need to synthesize the property to get it's attributes. The runtime still knows about it. Try it for yourself. The problem is that sometimes this doesn't happen. I want to know the conditions that cause the runtime to be unaware of the property attributes.

Temporary Fix


My temporary fix is to just copy all the property definitions in the protocol and paste them into the .h file:

@interface MyClass <MyProtocol>
    @property (nonatomic, strong) id someProperty;
@end

@implementation MyClass
@dynamic someProperty;
@end

This runs fine, though it is far from ideal. However, it suggests that my code is working correctly and the issue lies elsewhere.

I'd be happy to provide more details or background if needed.

Community
  • 1
  • 1
Christopher
  • 193
  • 7
  • The runtime doesn't optimize anything. The only thing that can optimize is the compiler (and sometimes the linker), but it's not that. A protocol is just a collection of declarations, there are no definitions inside whatsoever. **You** have to provide those, either by synthesizing the property or by implementing the accessor and the mutator manually. –  Jul 10 '13 at 17:38
  • By the way, this is not a question about Xcode. –  Jul 10 '13 at 17:39
  • The code you described is supposed to work. If you can write a test that doesn't work, please file a bug report and include that test. – Greg Parker Jul 10 '13 at 19:33
  • @GregParker I think you were the only one who read my entire question. Thanks for the comment! I'm on the same page with you about that I just wanted to get some feedback. – Christopher Jul 11 '13 at 13:59

3 Answers3

2

protocols define methods, optional methods and required methods.

properties are abstracted methods, if the protocol defines a property as being required then you must implement the required methods: typically with @synthesize... but can be done in other ways

(assuming non-fragile ABI / Modern Runtime) Using readonly for simplicity

@property(readonly)int dog;

could be implimented:

@synthesize dog;

or

@synthesize dog = _dog; // synthesize standard getter for the iVar _dog

or

- (int) dog
{
    return _dog; // or dog, or cat/5 or 5 or whatever
}

EDIT: re dynamic properties

@dynamic is a keyword that does nothing to generate methods to satisfy the requirement of a property, what it does do is inform the compiler that it is "taken care of" in some other way...

this dynamic dispatch can be accomplished by a few different methods at runtime, one would be by adding method implementations at run time, another would be by using the runtime for unresolved selectors. (I had a similar question about using dynamic properties to use a generic KV store in a Dictionary)

see: Using NSMutableDictionary as backing store for properties

Community
  • 1
  • 1
Grady Player
  • 14,399
  • 2
  • 48
  • 76
1

There seems to be a confusion:

  1. Declaring a property is enough for the property to exist at runtime. There is no need for implementation. This is how objective-c works. Methods don't have to exist at compile time, you can add them dynamically (e.g. what Core Data does).

  2. @dynamic does absolutely nothing during runtime. At compile-time it's a placeholder which says "don't give me compiler warnings that the getter/setter is not defined here". On the newest LLVM it also says "don't synthesize automatically".

My suggestions:

  1. If you are adding the protocol via a category, make sure the category is loaded. This seems to be the most usual problem with runtime reflection.

  2. To debug, also try to use class_conformsToProtocol. It would be strange to have a class that conforms to a protocol without it having properties declared by the protocol.

Sulthan
  • 128,090
  • 22
  • 218
  • 270
  • Very helpful suggestions, thank you. I am doing something similar to what Core Data does with it's models. Thank you for these suggestions. I'll most likely mark this as the answer if nobody else has any input. – Christopher Jul 11 '13 at 14:53
  • @Christopher Well, core data doesn't use the property declarations as far as I know. The beauty of core data is they can use the data model to generate property getters/setters. – Sulthan Jul 11 '13 at 14:55
  • I'm sure I'm not mimicking Core Data under the hood but I am automatically generating accessor methods for classes (though based on the types of their properties not data models). For reference, the code that I'm actually using this in is here: https://github.com/FuturaIO/ParseModel-iOS – Christopher Jul 11 '13 at 15:26
  • `class_conformsToProtocol` tells me it is conforming yet the property attributes cannot be retrieved. As Greg mentioned in another comment, this may be a bug. Thanks for your feedback. – Christopher Jul 11 '13 at 18:16
0

After much debugging and testing I've concluded that this is a bug. If anyone has any contrary evidence or suggestions feel free to post. The bug is this:

Sometimes when a property is defined in a protocol and then a class conforms to said protocol, the runtime is unaware of the property's attributes (e.g. class_getProperty fails) if the property is flagged as dynamic.

Rememeber, dynamic provides no implementation, it simply suppresses warnings, however, the property attributes should still be retrievable via the runtime.

I wanted to add some useful code snippets to solving / debugging these types of problems:

- (NSArray *)propertyNamesForClass:(Class)aClass includeInherited:(BOOL)shouldIncludeInherited;
{
    NSMutableArray *names = [NSMutableArray array];
    uint propertyCount = 0;
    objc_property_t *properties = class_copyPropertyList(aClass, &propertyCount);
    for (uint i = 0; i < propertyCount; i++) {
        [names addObject:[NSString stringWithUTF8String:property_getName(properties[i])]];
    }

    if (shouldIncludeInherited) {
        Class superClass = aClass;
        while ((superClass = class_getSuperclass(superClass))) {
            uint superPropertyCount = 0;
            objc_property_t *superProperties = class_copyPropertyList(superClass, &superPropertyCount);
            for (uint i = 0; i < superPropertyCount; i++) {
                [names addObject:[NSString stringWithUTF8String:property_getName(superProperties[i])]];
            }
        }
    }

    NSArray *sortedNames = [names sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    return sortedNames;
}

- (NSArray *)protocolNamesForClass:(Class)aClass includeInherited:(BOOL)shouldIncludeInherited;
{
    NSMutableArray *names = [NSMutableArray array];
    uint protocolCount = 0;
    __unsafe_unretained Protocol **protocolArray = class_copyProtocolList(aClass, &protocolCount);
    for (uint i = 0; i < protocolCount; i++) {
        [names addObject:NSStringFromProtocol(protocolArray[i])];
    }

    if (shouldIncludeInherited) {
        Class superClass = aClass;
        while ((superClass = class_getSuperclass(superClass))) {
            uint superProtocolCount = 0;
            __unsafe_unretained Protocol **superProtocolArray = class_copyProtocolList(superClass, &superProtocolCount);
            for (uint j = 0; j < superProtocolCount; j++) {
              [names addObject:NSStringFromProtocol(superProtocolArray[j])];
            }
        }
    }

    NSArray *sortedNames = [names sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    return sortedNames;
}

- (NSArray *)propertyNamesForProtocol:(Protocol *)aProtocol
{
    NSMutableArray *names = [NSMutableArray array];
    uint protocolPropertyCount = 0;
    objc_property_t *properties = protocol_copyPropertyList(aProtocol, &protocolPropertyCount);
    for (uint j = 0; j < protocolPropertyCount; j++) {
        [names addObject:[NSString stringWithUTF8String:property_getName(properties[j])]];
    }

    NSArray *sortedNames = [names sortedArrayUsingSelector:@selector(localizedCaseInsensitiveCompare:)];
    return sortedNames;
}
Christopher
  • 193
  • 7
  • I've packaged these and other related methods up for convenience here: https://github.com/mstrchrstphr/NSRuntime – Christopher Jul 11 '13 at 18:38