Skip to content
Contact

Objective-C vs Swift messages dispatch

P7020037

Objective-C and Swift runtimes differ in this matter, so let’s take a closer look at both languages internals.

Objective-C:

In Objective-C all methods are resolved dynamically at runtime. To decide which code to execute Objective-C runtime requires both a selector (method name) and the object that the message will be passed to. But how is it done internally? Well let’s start from the beginning.

In Objective-C everything is an object, even a class is an object (a little bit different, but sill an object). So what’s the definition of an object in Objective-C? You may ask. The basic definition is:

typedef struct objc_object {  
Class isa;
} *id;

So basically an object is a struct that starts with an isa member of type class. This makes sense as runtime can have access to every object’s information relating to what kind of a class is it.

Now let’s take a closer look at what a Class type is:

typedef struct objc_class *Class;
struct objc_class {  
Class isa OBJC_ISA_AVAILABILITY;
#if !__OBJC2__
Class super_class OBJC2_UNAVAILABLE;
const char *name OBJC2_UNAVAILABLE;
long version OBJC2_UNAVAILABLE;
long info OBJC2_UNAVAILABLE;
long instance_size OBJC2_UNAVAILABLE;
struct objc_ivar_list *ivars OBJC2_UNAVAILABLE;
struct objc_method_list **methodLists OBJC2_UNAVAILABLE;
struct objc_cache *cache OBJC2_UNAVAILABLE;
struct objc_protocol_list *protocols OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
This struct my vary for different runtimes as described here by Matt Gallagher

So it’s also a pointer to struct which starts with a pointer to Class, which according to our previous definition means it’s an object too. But the ‘so called’ ISA pointer is followed by more struct members providing information about its internal details - like name, size, methods list, properties and more.

 

For our purposes the most important is the **methodLists member, which holds all necessary information required by the Objective-C dynamic binding mechanism.

struct objc_method_list {  
int method_count
objc_method method_list [1]
objc_method_list * obsolete
}

Where objc_method is defined as

typedef id (*IMP)(id,SEL, …)
struct objc_method {  
IMP method_imp
SEL method_name
char * method_types
}

So there we have it: objc_method struct holds the information about every method we define.

One think to point out here is that instance methods are being looked up inside objects Class struct and class methods are in objects Class Class — Metaclass (objc_object -> isa -> isa)

We can read in Apple Documentation that:

The Objective-C language 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.

Objective-C besides running you code as you expect gives you a lot of possibilities to manipulate it’s behaviour at runtime
Basically you can create a class from scratch and add some methods/properties to it when your app is running, or change the implementation of an existing selectors — also known as method swizzling. To check all possible options that runtime gives us look inside <objc/runtime.h> header.

Every time a property or a method is accessed, the objc_msgSend function is invoked under the hood

id obc_msgSend(id self, SEL op, …)
The compiler generates calls to the messaging function. You should never call it directly in the code you write.

It takes two parameters:

  • receiver object to which the message is sent to
  • a selector of the method designated to handle the message.

It’s responsibility is to provide implementation based on class info, then call the procedure with provided arguments and return procedure value.

Here’s an example how it looks in real life code:

Person *person = [[Person alloc] init];  
[person setName:@“Johnny”];

Afer compilation:

objc_msgSend(  
objc_msgSend(
objc_msgSend(
objc_getClass(@“Person”), NSSelectorFromString(@“alloc”)),
NSSelectorFromString(@“init”)),
NSSelectorFromString(@“setName:”),@“Johnny”);

Objective-C runtime is very clever under the hood and whats more important veeeery fast. For each method pass runtime has to look up in a dispatch table dynamically method implementation for given selector. It doesn’t care about object type (Objective-C is dynamically-typed language with optional static typing) as long as it will find an entry in that table. Otherwise’ll you get runtime exception. (You probably have seen it lots of times in you debugger window as “unrecognized selector sent to instance”, when you tried to call a method on something that turned out to be at runtime something else. (i.e. when you were parsing some network response and you thought the parser will return nil but really it returned an NSNull instance, therefore message was send to it and didn’t find desired selector in NSNull’s dispatch table)).

So that’s Objective-C. It isn’t going away any time soon and it’s always good to have an understanding of what we are working with. 
Plus as Swift and Objective-C are interchangeable, you can always pick the right tool from your toolbox to solve different programming problems. Swift is still young and a subject to change.

Swift runtime

There is currently no access to Swift runtime (Objective-C runtime is open source), and most of the knowledge we currently have is based on some wise guy’s reverse engineering of Swift binaries and runtime.

Evan Swick in his post briefly describes what Swift classes internals look like with a conclusion:

Believe it or not, Swift objects are actually Objective-C objects.
(…)
Swift is Objective-C without messages.

The struct for Swift class representation lacks for method_list, but instead, as in C++, Swift classes have a vtable member which lists available methods in the class.

Vtables are created at compile time and contain function pointers accessed by index, then compiler uses that lookup table to translate method calls to appropriate function pointers and with enough information, eliminates the dynamic dispatch making the direct function call to the implementation. In some edge cases this might be much faster than the Objective-C dynamic dispatch.

If you would like to learn more about function name mangling in Swift, check this great article by Mike Ash

In theory, this approach is faster than Objective-C dynamic binding, but strongly reduces the flexibility, with performance gain not being very noticeable in most method calls. Also, as I mentioned before, Objective-C method dispatch is remarkably quick in what it does.

implementation = object->class.vtable[<index>]  
implementation()

But Swift is not constrained to only vtable method dispatch. 
Aside from default vtable approach, in which Swift class inherits from Objective-C class, it will use Objective-C dynamic binding. So as Cocoa probably won’t be rewritten to Swift, as we’ve been told at WWDC, this approach will be used quite often.

Another way to force Swift to use dynamic dispatch is to mark a class member declaration with the dynamic keyword. Those declarations will be dispatched using Objective-C runtime. Those will be also implicitly marked with the @objc keyword.

Swift Documentation: While the @objc attribute exposes your Swift API to the Objective-C runtime, it does not guarantee dynamic dispatch of a property, method, subscript, or initialiser. The Swift compiler may still devirtualise or inline member access to optimise the performance of your code, bypassing the Objective-C runtime.

Method swizzling is available in Swift for dynamically dispatched members (Objective-C subclasses or marked as dynamic).

I.e. for UIViewController subclass/extension it might look something like this:

func swizzleViewDidAppear() {
    Method original, swizzled
    original = class_getInstanceMethod(object_getClass(self), Selector.convertFromStringLiteral("viewDidAppear:"));
swizzled = class_getInstanceMethod(object_getClass(self), Selector.convertFromStringLiteral("bht_viewDidAppear:"));
    method_exchangeImplementations(original, swizzled);
}

At first I thought such a method would probably never be possible with strict vtable dispatched methods (with pure Swift class), but there is already something you can use in Swift: SWRoute 
(It allows you to route (hook) almost any function/method with another function/method or even a closure.)

The third way of dealing with method dispatch was introduced to optimise code even more. If compiler has enough information, it can simply inline the function calls directly or even eliminate them entirely!

Summary

All in all, it’s just one of the possible performance boosters built into Swift, although most tests show that it’s not as fast as Apple claims it to be (in comparison to Objective-C). Let’s just give Apple a chance to optimise the language performance-wise.



By Bartek Hugo Trzciński

Head of Technology by day and software engineer by night. Recently solving more problems in business and mangement than in code - hard to tell which one is more fun. Local dev groups activist. Automotive enthusiast. Boosted Board shredding champ. The best companion to dance and to give you a ride.