Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is objc_msgSend causing an EXC_BAD_ACCESS?

I'm making a class, that given an object target, a selector to watch for, and a displayTitle will output a string in this format: @"displayTitle: object.selector". It then registers itself through KVO so that anytime the value of object.selector changes, it can notify a view controller to update the view. I am using this as an abstract and reusable way to show a description of various properties of an object to a user.

When I try to get the value of object.selector, I can't do [object performSelector:selector] because LLVM gives errors when you use performSelector with a dynamic selector. So, I did exactly what this answer suggested: I used objc_msgSend(object, selector).

- (instancetype)initWithSelector:(SEL)selector onObject:(NSObject*)object displayTitle:(NSString*)displayTitle {
    self = [super init];

    if (self) {
        id value;

        if ([object respondsToSelector:selector) {
            // Used objc_msgSend instead of performSelector to suppress a LLVM warning which was caused by using a dynamic selector.
            value = objc_msgSend(object, selector);
        } else {
            return nil;
        }

        [self setItemDescription:[NSString stringWithFormat:@"%@: %@", displayTitle, value]];
    }

    return self;
}

And I got an EXC_BAD_ACCESS!

Screenshot

As you can see in the screenshot, I made sure that doing [object selector] works.

What is going on, and how can I fix it?

like image 212
Eliza Wilson Avatar asked Jun 15 '14 17:06

Eliza Wilson


2 Answers

You assign the result of your objc_msgSend call to a variable of type id so ARC kicks in and tries to retain the resulting object (crash is in objc_retain as you can see in the stack to the left). However, the result isn’t an object but an integer of value 8, which objc_retain takes to be a pointer. But there are no valid pointers this low, so you get the EXC_BAD_ACCESS.

Just change the type of value to be NSUInteger (or any other non-object type). But make sure all potential selectors return data of the same type. Alternatively, make sure to always return an object (or nil), which can be retained by ARC.

like image 173
Raphael Schweikert Avatar answered Nov 07 '22 13:11

Raphael Schweikert


Just like with calling a function in C, in order to call a method in Objective-C, you need to know the exact type signature of the method.

When you use objc_msgSend and related functions, you need to cast it to the correct function pointer type before calling it. From the other answers and comments, it appears that your method takes no parameters and has return type NSInteger. In that case, in order to use it, you must do something like:

NSInteger value;
NSInteger (*f)(id, SEL) = (NSInteger (*)(id, SEL))objc_msgSend;
value = f(object, selector);
like image 4
newacct Avatar answered Nov 07 '22 14:11

newacct