C is a language that is turing-complete. So is Objective-C. So shouldn’t it be possible to recreate an Objective-C program in straight C? Isn’t that how the Objective-C compiler was basically made?

The Objective-C preprocessor

Fun fact: The Objective-C compiler started out as a preprocessor on top of C (so did C++, by the way). That means, it used to be a program that took your .m files, rewrote them into straight .c files, and then fed those into your regular C compiler, adding the Objective-C runtime-library to the linker statements to help it do its work.

So that means the question above can be answered with “yes”, right ?

objc_msgSend()

No, not quite. You see, there is one weird detail of Objective-C that can’t be replicated in straight C, but has to be done on the assembler level: Message forwarding.

Now while message sending in Objective-C could be approximated in C by having a struct (your object) that contains a hash-table that maps method names (SELs) to function pointers (IMPs), Objective-C under the hood compiles every method call into a call to the function objc_msgSend(). This function in turn looks up the right function pointer and calls it, passing it the parameters you passed to it.

And that is something that straight C doesn’t let you do. You can call a function, you can look at parameters, but the C standard defines no way to just take whatever parameters the current function was given and hand them to another function. You can make a C program execute the corresponding machine code, either using in-line assembly, or if you’re a sucker for punishment, by filling a byte array with the raw bytes for the required instructions and then typecasting it to a function pointer and running it.

NSInvocation

Objective-C also lets you forward a message. If your object doesn’t handle a message sent to it, the call is wrapped in an NSInvocation, and the method -forwardInvocation: is called. You can then change the NSInvocation’s target to e.g. your object’s delegate before you run it, or stick the NSInvocation in your “undo” array, or whatever else makes sense for your use case.

Again, while looking up a method’s underlying function on another object can be done in straight C, and you can pass it whatever parameters you received manually by implementing the same method on your object, forwarding arbitrary function calls is something that needs help from assembler.

But apart from that, Objective-C is just C?

Yup. You could totally write a transpiler now that turns an Objective-C file into a C file, particularly given they made parsing so easy by prefixing most important things with an @ sign, so you probably wouldn’t even need a proper C parser to do it. (Though you will need to be able to skip nested brackets, so you can’t just point a regular expression at the whole thing, you’ll have to do a bit of legwork).