Search code examples
iosobjective-cobjective-c-runtime

Objective-C runtime crash


I am trying out Objective-C runtime methods for the first time. I have been reading a chapter(24) on this from iOS 7 programming pushing the limits. As per the example in the book I have implemented a message dispatcher function which is as below:

static const void *myMsgSend(id receiver, const char *name) {
    SEL selector = sel_registerName(name);
    Class receiverClass = object_getClass(receiver);
    IMP methodIMP = class_getMethodImplementation(receiverClass, selector);
    return CFBridgingRetain(methodIMP(receiver, selector));
}

I test this dispatcher in the function below:

void runMyMsgSend() {
    // NSObject *object = [[NSObject alloc] init];
    Class class = (Class)objc_getClass("NSObject");
    id object = class_createInstance(class, 0);
    myMsgSend(object, "init");

    // id description = [object description];
    id description = (__bridge id)myMsgSend(object, "description");

    // const char *cstr = [description UTF8String];
    const char *cstr = myMsgSend(description, "UTF8String");

    printf("---------------------");
    printf("%s\n", cstr);
}

The function works well for init and description for object instance of type NSObject. When the dispatcher function is called on object pointed by description with UTF8String as the method to run it crashes on

return CFBridgingRetain(methodIMP(receiver, selector));

Now I know that NSString is a cluster and actually an object of __NSCFString is used. I think this may be the problem while calling CFBridgingRetain. I need a better understanding of what is actually causing the crash. Thanks in advance.


Solution

  • You are trying to call CFBridgingRetain on all return types..

    The documentation explicitly states that if the IMP returns anything that is NOT void, then you must explicitly cast it to the right function pointer type.

    Your function is:

    static const void *myMsgSend(id receiver, const char *name) {
        SEL selector = sel_registerName(name);
        Class receiverClass = object_getClass(receiver);
        IMP methodIMP = class_getMethodImplementation(receiverClass, selector);
        id (*imp)(id, SEL) = (id (*)(id, SEL))methodIMP;
        return CFBridgingRetain(imp(receiver, selector));
    }
    

    When you called UTF8String, it returns a const char* which you are trying to retain. Not only that, but you are saying that the IMP returns an id and you retain that.. You can only retain Objective-C class objects. You never casted to the correct function pointer type so it crashes trying to implicitly convert const char* to id..

    Therefore your method needs to be:

    static const void *myMsgSend(id receiver, const char *name) {
        SEL selector = sel_registerName(name);
        Class receiverClass = object_getClass(receiver);
        IMP methodIMP = class_getMethodImplementation(receiverClass, selector);
    
        void* (*imp)(id, SEL) = (void* (*)(id, SEL))methodIMP;
        return imp(receiver, selector, args);
    }
    

    where it returns a simple void* which can be NULL or BOOL or int or w/e type (primitive).. If it needed to return an object, the object is implicitly bridged so no need to retain (just cast the result to the right type).

    However, it is probably a much better idea to use NSInvocation as the implementation of this function because then you don't have to worry about type and arguments and so on..