7

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?

Community
  • 1
  • 1
Eliza Wilson
  • 1,031
  • 1
  • 13
  • 38

3 Answers3

6

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.

Raphael Schweikert
  • 18,244
  • 6
  • 55
  • 75
  • Is there a way to make it so ARC can determine if `value` is an object or not, so that my class can handle objects *and* non objects? I know I could make the property be a `NSNumber`, as @VincentGeurci said, but that would be rather inconvenient as I need to use the value frequently in other places in the application. – Eliza Wilson Jun 15 '14 at 18:14
  • The only solution in objective-c to hold object and non objects... is to hold objects :D i.e NSNumbers. Note that in recent llvm versions you can do `NSNumber *x = @(5)`, look for literals. For more, wait for Swift which kinda solves this :) – Vincent Guerci Jun 15 '14 at 18:33
  • You could also introspect value to determine its objective-C type to automatically encode/decode it. But that's non trivial (low level objc_xxx methods), NSNumber way much much easier. – Vincent Guerci Jun 15 '14 at 18:36
  • @EthenA.Wilson You could use `[[object methodSignatureForSelector:selector] methodReturnType]` to determine the return type. However, this return type is implementation-specific so it would be best to call this for a selector whose result you know for comparison. On my machine, selectors that return objects have a return type of `'@'`. – Raphael Schweikert Jun 15 '14 at 19:39
  • 1
    "Just change the type of value to be NSUInteger" I think that will make it not compile, as `obj_msgSend` is declared with `id` as return type. – newacct Jun 16 '14 at 04:54
  • @newacct Yes, you’re right, there’s more to do than “just” changing the type, there’s a cast and possibly some ARC compiler directive involved. – Raphael Schweikert Jun 16 '14 at 07:43
4

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);
newacct
  • 119,665
  • 29
  • 163
  • 224
  • Reference is here https://issues.apache.org/jira/browse/CB-6150 and here https://developer.apple.com/library/content/documentation/General/Conceptual/CocoaTouch64BitGuide/ConvertingYourAppto64-Bit/ConvertingYourAppto64-Bit.html – isaacselement May 27 '17 at 07:11
3

Could it be that your value is 8, an Integer?

Thanks to arc, value is attempted to be retained, i.e. retain at adress 0x8, which would explain that EXC_BAD_ACCESS, 0x8 being a protected address.

If that is your problem, just wrap your value as a NSNumber

Vincent Guerci
  • 14,379
  • 4
  • 50
  • 56