As for whether casts add much safety, I admitted it's debatable. C++ prefers casts (e.g. on malloc) whereas C doesn't. A downside of using explicit casts is that if the type changes elsewhere in the program, the casts (spread throughout the code) can mask errors. If you write code such that casts are unnecessary (which I'm agreeing is not possible in this case!), you will get a compile warning/error right away.
I think both perspectives are valid and there are risks either way. I try to avoid casts when writing plain C though.
C++ doesn't prefer casts, it requires them in places where C doesn't. Casting the return value from malloc isn't optional in C++ (unless you're assigning to a void *, anyway).
> It's also easy to make mistakes because of type mismatches. For example, if you pass `42` to a CGFloat parameter, this works fine when you cast msgSend to the right type, but will fail amusingly if you rely on varargs to make it through.
All of your other claims are completely valid. This one, I'd say, is partially valid.
No need to pick nits with my usage of the word "prefers."
objc_msgSend(window, sel_registerName("setAlphaValue:"), 1);
Versus: ((void (*)(id, SEL, CGFloat))objc_msgSend)(window, sel_registerName("setAlphaValue:"), 1);
The former will set a garbage alpha value, the latter works. More generally, the former requires much more care and attention to get the types right, whereas the latter just requires that the cast match the method declaration. ((void (*)(id, SEL, double))objc_msgSend)(window, sel_registerName("setAlphaValue:"), 1);
If the method signature changes (unlikely for a system header, but possible if you're calling into your own code) then you're hosed. Or you could just mess up and copy/paste the wrong signature, or forget to change it.Basically, it's pretty error-prone no matter what you do. Using Objective-C directly is much safer (and luckily you usually can, since ObjC is pretty much a drop-in replacement for C).
For one, C vararg type promotion makes it impossible to pass certain types in as parameters otherwise. For example, you can't pass a float.
It's also easy to make mistakes because of type mismatches. For example, if you pass `42` to a CGFloat parameter, this works fine when you cast msgSend to the right type, but will fail amusingly if you rely on varargs to make it through.
Perhaps most importantly, vararg calls aren't guaranteed to be compatible with a non-vararg target (as most/all methods you call will be), and you'll see this in action on ARM64.
Currently, you can #define OBJC_OLD_DISPATCH_PROTOTYPES 0 to have the compiler enforce this requirement. (This declares the functions to take (void) rather than (id, SEL, ...).) It's likely that Apple will eventually make this the default.