The down low on Objective-C Runtime

profile picture

David

Team lead - iOS

4 Oct  ·  9 min read


Were you expecting a blog post about the recently released Xcode 8 and Swift 3.0? Not this time: we’re talking good old Objective-C today!

But why, I hear you ask? Didn’t Swift 3.0 pretty much kill Objective-C?

Not quite. Swift may be the cool new kid in town, but surely that doesn’t mean we should completely abandon our close, reliable childhood friend? Why can’t we all share the playground (pun intended) and live together in perfect harmony?

Cheesy metaphors aside, Swift was designed with that exact purpose in mind. One of Swift’s core fundamentals is the ability to work with Objective-C, side by side in the same project. Both languages may be fundamentally different, but they actually complement each other quite nicely.

While it’s true that Swift was created to be Objective-C’s successor in this modern era of Apple development, Apple still continues to support Objective-C to this day. Although it is mainly to improve compatibility with Swift and features such as nullability and generics don’t really change the way an Objective-C application behaves, it is still a nice readability improvement for a developer using Objective-C.

Old and new languages

And let’s not forget that most existing codebases are written in Objective-C, like Cocoa itself, many third-party libraries, and November Five’s own internal libraries (and boy do we have a lot of those).

In other words, while it’s obvious that Swift will gradually take up an increasingly large role in iOS development, Objective-C will likely remain relevant for years to come. The older language still has a few useful tricks up its sleeve that not everyone is aware of. My favourite, and the one we’ll be talking about here, is the Objective-C Runtime – something that has always seemed a bit mysterious to many developers.

Let’s start with a story…

Not too long ago, while browsing the ios-developers Slack community (check it out, it’s a great place for Apple developers to gather) I saw someone asking a question in the Swift channel. When another community member mentioned the Objective-C Runtime as a possible solution, at least half a dozen people responded with question marks.

Most Apple developers have probably heard of Runtime at some point – they are aware it exists – but most have probably never worked with it. And the topic does not come up often, either in the community or in Apple’s own communication and materials. In fact, Apple specifically states:

“You do not need to use the Objective-C Runtime library when programming in Objective-C.”

And it’s understandable why they would say that: using the Objective-C Runtime can be dangerous if you don’t know what you’re doing (leading to exceptions and crashes), because it allows you to access a lot of low-level features of Cocoa or third-party libraries that developers may not want you to see.

But what exactly is the Objective-C runtime?

The Objective-C Runtime is an open source library written in C and Assembler that adds the Object Oriented capabilities to C to create the Objective-C language.

I’ll simply quote the Objective-C Runtime reference – I can’t explain it any better myself:

“The Objective-C languages defers as many decisions as it can from compile time and link time to runtime. Whenever possible, it does things dynamically. This means that the language requires not just a compiler, but also a runtime system to execute the compiled code. The runtime system acts as a kind of operating system for the Objective-C language, it’s what makes the language work.”

The statement that Objective-C does things dynamically means that Objective-C is a dynamically typed language, as opposed to Swift, C++, Java, etc. What’s the defining factor to whether a language is statically typed or dynamically typed? For the most part, it’s method dispatching (when and by whom is decided which code is executed when a method is called), and type binding (when is decided what type a variable will have).

Statically typed languages use static dispatching and early type binding, meaning it’s the compiler which will decide at compile time. This means that when an application is running, you can always be sure that the exact code that the developer intended is being executed.

For a dynamically typed language such as Objective-C, things are a little bit different. All of these decisions are made at runtime by the Objective-C Runtime library. And because of that library, we can manipulate method dispatching and type binding ourselves.

In other words, the Objective-C Runtime allows us to create, modify, or remove any of the following at runtime:

  • Class
  • Method
  • Implementation
  • Properties
  • Instance variables

And not only can you modify these elements for your own code; the Objective-C Runtime also lets you manipulate closed source libraries and even Apple’s own frameworks.

With great power…

Now you’re probably thinking: “wow, that sounds powerful”. And you’d be right. But more importantly, if you have ever worked in software development, you’re most likely thinking: “wow ,that sounds dangerous”. And you’d be right again.

The Objective-C Runtime is a bit of a double-edged sword, creating a lot of high-risk, high-reward scenarios. It puts a lot of power at your fingertips, but does so at the risk of breaking your app if you’ve made only the slightest mistake. It gives you the power to modify code that was not meant to be modified and access things that were meant to be private.

However, that does not mean it shouldn’t be used – ever, at all. A famous super hero once said: “With great power comes a great responsibility” and here at November Five we always try to use our superpowers for good! Here are some real world examples of how we’ve used the Objective-C Runtime in the past.

Looking under the hood & learning from it: Because the Objective-C Runtime allows you to inspect, override, modify, … methods in private or closed source frameworks, it can be a valuable learning tool to see how someone else’s code works under the hood. For example, let’s say you want to create your own component that should work similarly but slightly different to a UITableView, you could take a look at how it is built under the hood. A convenient method we like to use for this is:

+(NSArray*)objcruntime_getMethodNames
{
    Class class = [self class];
    NSMutableArray* names = [NSMutableArray new];

    uint count;
    Method* methods = class_copyMethodList(class, &count);

    for (NSUInteger i = 0; i < count; ++i)
    {
        Method property = methods[i];

        SEL methodSelector = method_getName(property);
        NSString* methodName = NSStringFromSelector(methodSelector);
        if (methodName)
            [names addObject:methodName];
    }
    free(methods);

    return [names copy];
}

This allows you to quickly retrieve all method names of a class directly from your console. So if you were to use it on UITableView, you’d see this result:

(lldb) po [UITableView objcruntime_getMethodNames]
<__NSArrayI 0x148316000>(
indexPathIsFirstRowInSection:,
indexPathIsLastRowInSection:,
indexPathIsFirstSection:,
indexPathIsLastSection:,
sectionIsLastSection:,
_cnui_applyContactStyle,
_cnui_applyContactStyleStark,
_cnui_adjustContentInset:,
ab_delayedScrollRespectingCaretOfActiveTextViewToCell:atIndexPath:atScrollPosition:animated:,
ab_internalScrollToRowAtIndexPathRespectingCaretOfActiveTextView:atScrollPosition:animated:,
ab_scrollToRowAtIndexPathRespectingCaretOfActiveTextView:atScrollPosition:animated:,
_mapkit_dequeueReusableCellWithIdentifier:,
initWithFrame:,
setFrame:,
layoutSubviews,
.cxx_destruct,
_contentSize,
setBackgroundColor:,
traitCollection,
initWithCoder:,
_populateArchivedSubviews:,

And you’ll notice that you can see far more methods than you can normally find in the UITableView header file. You can do same thing for iVars, properties, method implementations, … We created a category on NSObject for these convenient methods, which you can find here.

Working with associated objects: Sometimes you find yourself in situations where you need to add a property to a category of a class, but unfortunately that’s not possible in Objective-C. Fortunately, you have associated objects, which allow you to associate arbitrary values with a certain object at runtime. Let’s say you’d like to create a category on UIImageView that downloads an image. In such a case you could use associated objects to add a property to store the NSURL to the image. To store the URL, you would do something like this:

NSURL* imageURL = [NSURL URLWithString:@"https://www.trythisforexample.com/images/example_logo.png"];
objc_setAssociatedObject(self.imageView, (__bridge CFStringRef)@"imageURL", imageURL, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

To retrieve the URL later, you can do this:

NSURL* imageURL = objc_getAssociatedObject(self.imageView, (__bridge CFStringRef)@"imageURL");

In the category above you can find some convenient methods for associated objects as well.

Debugging closed source code: Sometimes you encounter a crash in code that isn’t your own. If it’s an open source library, the solution is simple: you report the issue and ideally fix it yourself and create a pull request. But for closed source frameworks, it’s not so easy. You can of course report the issue and hope that enough people have the same issue for the authors to quickly deploy a fix, but there are no guarantees – and you might not have the time to wait.

Not too long ago, we encountered a crash in our Appmiral applications which originated from the (closed source) Spotify SDK, in which an unrecognized selector was sent to instance on a non-exposed object in the SDK. We reported the issue, and received word that the fix would be included in the next release of the SDK – but unfortunately with no indication as to when that would be. During the festival season we usually submit multiple festival applications each week, so patiently waiting for a fix was not an option. Thanks to the Objective-C Runtime, we were able to add the missing method to that object (with an empty implementation) at runtime, thus preventing the crash. While this was not an ideal solution, it did prevent more than a dozen apps from crashing until the official fix was released.

JSONModel: Several popular libraries make use of the Objective-C Runtime, and one we use frequently is JSONModel. For those who don’t know, JSONModel allows you to easily create data models from JSON. Under the hood it uses the Objective-C Runtime to read an object’s properties at runtime and fill in the value from a JSON.

To know how it achieves this, you just need to take a quick look at the __inspectProperties method in the JSONModel.m file. Here’s a short snippet:

// inspect inherited properties up to the JSONModel class
while (class != [JSONModel class]) {
    //JMLog(@"inspecting: %@", NSStringFromClass(class));

    unsigned int propertyCount;
    objc_property_t *properties = class_copyPropertyList(class, &propertyCount);

    //loop over the class properties
    for (unsigned int i = 0; i < propertyCount; i++) {

        JSONModelClassProperty* p = [[JSONModelClassProperty alloc] init];

        //get property name
        objc_property_t property = properties[i];
        const char *propertyName = property_getName(property);
        p.name = @(propertyName);

        //JMLog(@"property: %@", p.name);

        //get property attributes
        const char *attrs = property_getAttributes(property);
        NSString* propertyAttributes = @(attrs);
        NSArray* attributeItems = [propertyAttributes componentsSeparatedByString:@","];

Wrapping up

And with those examples, we can wrap up our overview of Objective-C runtime! So, what should you remember?

  • Avoid using the Objective-C Runtime if possible, and only use it (with care) when the problem at hand can’t be solved any other way.
  • Always know what you’re doing when working with the Objective-C Runtime.
  • Seriously, make sure you know what you’re doing when using the Objective-C Runtime! Remember, the stakes are high & a lot can go wrong.
  • Don’t use it to modify private methods in Apple frameworks, your app will be rejected from the store.
  • If you swizzle methods in Apple frameworks, always call the original implementation as well. Know that an OS update can have severe effects on the workings of your app.

Would you like to read more about the Objective-C Runtime? If you’re a Cocoa developer, the best place to start is Apple’s own API reference. Or click through to these excellent blog posts about associated objects and method swizzling, and this basic walkthrough for using Objective-C runtime.


profile picture

David

Team lead - iOS

4 Oct  ·  9 min read