The Objective-C Runtime in Practice

May, 2020

I really like Objective-C.

It’s been my primary programming language for more than a decade now, and I’m still finding out new, interesting things to solve with it. Sadly, it seems the language is entering its sunset years, and while there’s a lot to like about Swift, I feel like some of the fun will be lost on the move away from dynamic dispatching and typing.

So I plan to write about some of the useful things I’ve discovered over the years, before they become ancient history 😄

What’s so great about Objective-C?

In a nutshell: the runtime.

It’s what sets ObjC apart from other C-based object oriented languages like C++. Instead of connecting up function calls during compilation, ObjC leaves most of that until your app is actually running. This makes it much more dynamic and lets us examine and modify the behaviour of our apps on the fly; something very difficult to do in most other compiled languages.

The runtime is what lets us access this power — it exposes the low-level wiring of the language.

Of course, there are trade-offs. Objective-C bakes a lot more metadata about our code into the app, which increases the binary size. Message sending also adds a level of indirection that has a performance cost. However these downsides are negligible for most apps, and well worth the flexibility they add.

So what is the runtime?

The runtime is like a mini operating system for the Objective-C language, which your app relies on to do anything. Even a simple method call like [self description] gets converted by the compiler into this:

objc_msgSend(self, @selector(description))

That objc_msgSend() function is part of the runtime… actually, the most important part. Objective-C is a message-based language, and this is what sends the messages. Your code will normally call this function tens of millions of times per second!

There are many great resources about the ObjC runtime which I’ve listed below. The runtime is even open source, which comes in handy if you really need to check how something works.

In these series of posts, I’m going to focus on some practical uses of the runtime instead of how it works, but I encourage you to dig into those links.

What does it let us do?

The runtime enables many Objective-C language features, like being able to message nil, Key-Value Coding and Observing, the responder chain, NSUndoManager, message forwarding and object proxying.

In general, the main powers that the runtime unlocks for us are introspection and reflection.

Introspection

Introspection is the ability for a program to examine itself while it’s running, such as finding out what classes are defined, and which methods and properties they contain.

You would have already used introspection in Objective-C and maybe not even realised it. Methods like respondsToSelector:, isKindOfClass: and even the NSStringFromClass function are examples of examining your app at run time.

The runtime functions go way beyond these. You can not only list all the classes, protocols, properties, methods and ivars in your app, but get details on their types and implementations.

Reflection

While introspection gives us a read-only view of our app’s internals, reflection extends this by allowing write access. This means we can change how the app works while it’s running!

Perhaps you’ve even used some of these reflection functions directly before. Associated objects (objc_getAssociatedObject) and swizzling (method_exchangeImplementations) are two of the more common usages.

A very useful feature of reflection is being able to implement properties and methods at run time. For example, you might have seen the @dynamic keyword used in Core Data classes. This tells the compiler not to generate the normal getter and setter methods for properties, because NSManagedObject will provide its own custom behavior when the app runs. We’ll learn how to provide those dynamic implementations in a future post.

You can even create new classes and protocols at run time. KVO uses this to transparently create subclasses of observed objects, which then masquerade as the original class.

Many of these things could be done without the runtime, but they’d require a lot of repetitive and fragile boilerplate code to implement, and we programmers never like writing repetitive code when we can make the computer do it 😁

Up next

I’m going to write a series of posts on practical uses of the runtime, starting with inspecting the properties of any class. Then we’ll look at how to create a class with dynamic storage (like NSManagedObject), and how to introspect the ivars of a class, which will let us dynamically compare and hash objects.

We’ll also see how to automatically encode and decode objects from JSON, simulate immutable classes, and finally how we might build an app that can be patched to modify its behaviour after release! 🤯

You’ll find new posts under the runtime tag as they’re published.

Articles

References


Any comments or questions about this post? ✉️ nick @ this domain.

— Nick Randall

Tagged with: