Christopher
Christopher

Reputation: 193

Obj-c protocol properties are not implemented in conforming class

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.

Upvotes: 7

Views: 1280

Answers (3)

Grady Player
Grady Player

Reputation: 14549

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

Upvotes: 2

Christopher
Christopher

Reputation: 193

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;
}

Upvotes: 0

Sulthan
Sulthan

Reputation: 130102

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.

Upvotes: 1

Related Questions