snej / MYUtilities

Objective-C Cocoa utility functions/methods I can't live without
Other
58 stars 21 forks source link

MYClassFromType doesn't handle model properties with protocols #10

Closed saltzmanjoelh closed 9 years ago

saltzmanjoelh commented 9 years ago

@property (weak) JSModel *someModelProp;

Class MYClassFromType(const char* propertyType) {
    size_t len = strlen(propertyType);
    if (propertyType[0] != _C_ID || propertyType[1] != '"' || propertyType[len-1] != '"')
        return NULL;
    char className[len - 2];
    strlcpy(className, propertyType + 2, len - 2);
    return objc_getClass(className);//className == "@\"JSModel<JSProtocol>\""
}

NULL is returned

snej commented 9 years ago

It's not clear from the code what the actual problem is that you're describing. Could you explain it in English?

saltzmanjoelh commented 9 years ago

Normally, model properties do not have a protocol listed after the model class @property (weak) JSModel *someProperty;

When you call myVar.someProperty, the framework creates an instance of JSModel instead of the documentID string.

If you add a protocol name after the model class, when you call myVar.someProperty, the documentID string is returned instead of an instance of your model class (JSModel). @property (weak) JSModel *someProperty;

I traced it down to the MYClassFromType function. If you don’t have a protocol listed next to the model class in the property, the function works as expected: objc_getClass(className);//className is the string "@\"JSModel\”"

If you do have a protocol listed next to the model class in the property, objc_getClass returns NULL: objc_getClass(className);//className is the string "@\"JSModel\””

On Feb 11, 2015, at 11:50 PM, Jens Alfke notifications@github.com wrote:

It's not clear from the code what the actual problem is that you're describing. Could you explain it in English?

— Reply to this email directly or view it on GitHub https://github.com/snej/MYUtilities/issues/10#issuecomment-74030607.

snej commented 9 years ago

Got it. But I don't think JSModel<JSProtocol> is a reasonable type to use for a dynamic CBLModel property — it means "a class compatible with JSModel that implements JSProtocol". If JSModel itself implements JSProtocol, then just use JSModel as the type. If JSModel doesn't actually implement JSProtocol but there's a subclass that does, then you'll need to explicitly declare the property as being of that subclass, otherwise CBLModel won't know to instantiate that particular subclass.

Or is there something fancy you're doing that requires you to declare the property that way? I can't figure out what it could be...

saltzmanjoelh commented 9 years ago

Here is a simplified example of why I would need a protocol. I have 2 different classes that implement a protocol and I want to make sure that only a class that implements the protocol gets used for the property.

screen shot 2015-02-12 at 10 00 49 pm

I added some obj-c to parse the protocol out of the property name and everything seems to be working;

static const char* MYGetPropertyType(objc_property_t property, BOOL *outIsSettable) {
    *outIsSettable = YES;
    const char *result = "@";

    // Copy property attributes into a writeable buffer:
    const char *attributes = property_getAttributes(property);
    //handle protocol in property name
    const char *protocolStr = strchr(attributes, '<');
    if(protocolStr){
        NSString *attributesStr = [NSString stringWithUTF8String:attributes];
        NSInteger location = [attributesStr rangeOfString:@"<"].location;
        NSInteger length = [attributesStr rangeOfString:@">"].location - location+1;
        attributes = [attributesStr stringByReplacingCharactersInRange:NSMakeRange(location, length) withString:@""].UTF8String;
    }
    char buffer[1 + strlen(attributes)];
    strcpy(buffer, attributes);

    // Scan the comma-delimited sections of the string:
    char *state = buffer, *attribute;
    while ((attribute = strsep(&state, ",")) != NULL) {
        switch (attribute[0]) {
            case 'T':       // Property type in @encode format
                result = (const char *)[[NSData dataWithBytes: (attribute + 1) 
                                                       length: strlen(attribute)] bytes];
                break;
            case 'R':       // Read-only indicator
                *outIsSettable = NO;
                break;
        }
    }
    return result;
}
snej commented 9 years ago

For unrelated reasons I suddenly have a need to support interface-valued properties (i.e. @property id<Something> something) so it looks like I'll be adopting something like your code above. Nice timing!