Why shouldn't you use objc_msgSend() in Objective C?
Asked Answered
V

3

24

The Objective C Runtime Guide from Apple, states that you should never use objc_msgSend() in your own code, and recommends using methodForSelector: instead. However, it doesn't provide any reason for this.

What are the dangers of calling objc_msgSend() in your code?

Visceral answered 23/6, 2013 at 17:26 Comment(3)
Because it will cause the end of the Universe as we know it. And will be impossible to decipher 6 months from now, especially if someone else is reading the code.Opsis
Because it's like if you disassemble a mountain bike, and use it's wheels and singular pieces to move around.Chronometer
(And if you do use it, comment the hell out of it.)Opsis
E
42

Reason #1: Bad style - it's redundant and unreadable.

The compiler automatically generates calls to objc_msgSend() (or some variant thereof) when it encounters Objective-C messaging expressions. If you know the class and the selector to be sent at compile-time, there's no reason to write

id obj = objc_msgSend(objc_msgSend([NSObject class], @selector(alloc)), @selector(init));

instead of

id obj = [[NSObject alloc] init];

Even if you don't know the class or the selector (or even both), it's still safer (at least the compiler has a chance to warn you if you are doing something potentially nasty/wrong) to obtain a correctly typed function pointer to the implementation itself and use that function pointer instead:

const char *(*fptr)(NSString *, SEL) = [NSString instanceMethodForSelector:@selector(UTF8String)];
const char *cstr = fptr(@"Foo");

This is especially true when the types of the arguments of a method are sensitive to default promotions - if they are, then you don't want to pass them through the variadic arguments objc_msgSend() takes, because your program will quickly invoke undefined behavior.

Reason #2: dangerous and error-prone.

Notice the "or some variant thereof" part in #1. Not all message sends use the objc_msgSend() function itself. Due to complications and requirements in the ABI (in the calling convention of functions, in particular), there are separate functions for returning, for example, floating-point values or structures. For example, in the case of a method that performs some sort of searching (substrings, etc.), and it returns an NSRange structure, depending on the platform, it may be necessary to use the structure-returning version of the messenger function:

NSRange retval;
objc_msgSend_stret(&retval, @"FooBar", @selector(rangeOfString:), @"Bar");

And if you get this wrong (e. g. you use the inappropriate messenger function, you mix up the pointers to the return value and to self, etc.), your program will likely behave incorrectly and/or crash. (And you will most probably get it wrong, because it's not even that simple - not all methods returning a struct use this variant, since small structures will fit into one or two processor registers, eliminating the need for using the stack as the place of the return value. That's why - unless you are a hardcore ABI hacker - you rather want to let the compiler do its job, or there be dragons.)

Enharmonic answered 23/6, 2013 at 17:34 Comment(3)
It is important to emphasize that objc_msgSend() -- the varargs base function -- is not compatible with a strongly typed method call. Which is why casting is required when using objc_msgSend(). Which is also exactly why you should avoid it as much as possible. As soon as you cast an expression, you are basically telling the compiler that you are smarter than it, which is rarely the case.Poetaster
@Enharmonic It's faster and only errors if the system swipes your memory. Not validating a pointer as your implying is never a good idea. Plus, the code is objc_msgSend([NSObject class], @selector(alloc)), @selector(init); not objc_msgSend(objc_msgSend([NSObject class], @selector(alloc)), @selector(init)); But you're using the the class method so you just negated all benefits of using 0bjc_msgSend. the correct way is "(*p_objc_msgSend)( p, mySelector);" And that code is pasted from how a call is made in ARC.Jackshaft
Also #2 is great example of how not to program. You have experience with pointers you never do that, also don't write 1 liners. You verify address exists and call immediately. You also use pointers and validate pointers. The example you pasted in #2 makes 4 calls to objc_msgSend for FooBar and Bar. The point of calling it specifically is performance, but your example waste performance benefits.Jackshaft
D
11

You ask "what are the dangers?" and @H2CO3 has listed some ending with "unless you are a hardcore ABI hacker"...

As with many rules there are exceptions (and possibly a few more under ARC). So your reasoning for using msgSend should go something along the lines of:

[ 1] I think I should use msgSend - don't.

[2] But I've a case here... - you probably haven't, keep looking for another solution.

...

[10] I really think I should use it here - think again.

...

[100] Really, this looks like a case for msgSend, I can't see any other solution! OK, go read Document.m in the TextEdit code sample from Apple. Do you know why they used msgSend? Are you sure... think again...

...

[1000] I understand why Apple used it, and my case is similar... You've found and understood the exception that proves the rule and your case matches, use it!

HTH

Damnify answered 23/6, 2013 at 19:10 Comment(12)
I will now have to think about why they used objc_msgSend() directly in that piece of code. I'm also surprised it's called immediately after the disposal of ownership (release). Maybe it's just that it's 10PM here, but I don't really get it, in fact.Enharmonic
If you need to send a message with an arbitrary selector and non-id arguments, you can't use performSelector:…. You can either use objc_msgSend or you can use NSInvocation. Using objc_msgSend is substantially simpler.Levite
That code is unfortunately magical. Bugs will be filed to remove the magic at the cost of a few more lines of penetrable code.Poetaster
@Poetaster - Magical? First time I ever heard anyone call TextEdit that ;-)Damnify
@H2CO3 If you find the reasons, please share themCelisse
@robmayoff why not instanceMethodForSelector: and a function pointer then?Enharmonic
@Celisse Sample code, in general, is documented. TextEdit is less sample code and more a reference implementation of the state of the art Cocoa app. To that ends, it is more complex and atypical than most samples. Still, that particular construct could be better expressed.Poetaster
@H2CO3 That would work too, and you wouldn't have to worry about which version of objc_msgSend to use, but you'd still need to specify the signature of the function, just as you do with objc_msgSend, and the compiler still wouldn't be able to check that you've specified the right signature. It would be better if NSErrorRecoveryAttempting specified a method taking a block instead of a selector. (The protocol predates blocks.)Levite
@H2CO3 Actually, after reviewing the implementation of instanceMethodForSelector:, I'm not sure that method does save you from the _stret problem. The method always calls class_getMethodImplementation, but the runtime also provides class_getMethodImplementation_stret, which returns a different value when the message will be forwarded. Seems like instanceMethodForSelector: is broken in that case.Levite
This discussion revolves around engineering solutions to a created problem. It is interesting to ask how can a C compiler construct an appropriate call to a function pointer? Why can't it do the same for a selector? Type erasure... (a thorn to ARC as well) Anyway, over here at the Unseen Uni the TextEdit code is not magical; it's simple, direct & clear for the context - and I expect the same solution is used in similar contexts elsewhere. @H2CO3, maybe you should ask not why the release is there, but why the retain is - but is this not an orthogonal issue? Happy ruminating to all :-)Damnify
@robmayoff Thanks. Now that's interesting. Actually, I'd have expected that the returned function pointer is called differently (and hence correctly, according to the ABI) when cast to the correct function pointer type... I mean can't the compiler just deduce whether to look for the return value in r0 or eax if the return type is an int and pop it off of the stack when it's declared to be a struct?Enharmonic
You use it for performance to avoid the 16 calls that Arc makes for one instruction. Unless you're an advanced programer it should be avoided. Typically I use it when I have a widely used function that is fast and logic is isolated.Jackshaft
I
4

I can make a case. We used msgSend in one of our C++ files (before we switched to ARC) that's in a cross-platform project (Windows, Mac and Linux). We use it to ref count a reference in the backed (the shared code) that's used later to go from frontend to backend and vice versa. Very special case, admittedly.

Incorruptible answered 25/6, 2013 at 12:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.