Using the Objective-C runtime and method swizzling

Objective-C runtime is often an unknown topic for new iPhone developers. In practice, using Objective-C Runtime is needed really seldom, but there are situations, when its usage saves a lot of work and makes things easier. One of the most well known examples is “method swizzling”, which is sometimes considered to be dangerous, but is often very useful.

Why is runtime needed? 

Objective-C is a runtime-oriented language, which means that it has two possible places of making decisions about what will actually be executed: it generally defers as many decisions as it can from compile and link time to runtime, which means that it does things dynamically whenever possible. Therefore, by using runtime directly, we could achieve more flexibility by swapping methods, adding properties, redirecting messages etc…

Important data structures

Most important structures are defined in following header files:

By exploring those header files, it’s possible to get a good overview of runtime implementation details.

One of the first things that we see in objc.h is an object declaration:

It turns out that an object is represented by an ordinary C structure during the runtime. Every Objective-C object has a class pointer known as “isa” , which is needed for Objective-C runtime for making multiple decisions about available methods, properties and gaining other information about the object’s class.

Class itself has a similar structure:

That’s an interesting point, because every Class in Objective-C is an object itself. The Class structure has similarly to the Object structure an isa pointer that must point to a Class structure. It is needed in order to invoke class methods (methods starting with +). Such a class has a special name and is called “a meta class”, which is a class for a Class object. The meta-class is also an object and uses base class’ meta-class as its class. For example, classes that inherit from NSObject use NSObject meta-class as their class.

Note the “OBJC_ISA_AVAILABILITY” near the isa pointer. Starting with iOS 7, using isa directly is deprecated and Xcode will show a warning about this.

Instead of using isa directly, object_setClass and object_getClass should be used.

Similarly, C structures are defined for other Objective-C language parts:

Using runtime functions

In addition to defining language structures, there are also many convenient functions available.

Creating new classes in runtime

To create a new class with methods and properties, several steps should be passed:

1. Use objc_allocateClassPair to create a new class.

objc_allocateClassPair takes 3 parameters: a superclass (can be nil to create a new root object), a name for the new class as a string and a parameter called extraBytes, which should be usually 0.

It is called “allocateClassPair”, not “allocateClass”, because during the allocation a class and its metaclass are allocated (therefore a pair).

2. Use class_addMethod to add methods.

class_addMethod takes following parameters:

–       a class to which to add a new method

–       a method’s selector – same selector can be afterwards used to call the method

–       a function that is the implementation of the new method. It should take at least two parameters id self and SEL _cmd. After that, other parameters can be also added.

–       Method types.

The implementation of the function is simple:

Method types describe the types used and returned by the method.

For example “@@:i”describes a function that returns id, and takes 3 parameters: id, SEL and int. The first character is a return type, the second and third characters must always be “@:” because those are required method parameters (id self, SEL _cmd). The last parameters represents an integer.

The full list of type identifiers is available HERE.

Additionally, @encode() can be used to get a type identifier.

For example @encode(NSInteger) returns “i”.

 3. Use class_addIvar to add properties.

class_addIvar takes following parameters:

–       a class to which to add a new variable

–       a name for the new variable

–       a size for the new variable

–       the alignment of the instance variable, which shows, how the variable should be aligned in memory. To align a type to its size, log2(size) should be used.

–       Variable’s type.

4. When a class is ready, objc_registerClassPair should be used to finally register the class.

The class must be registered before it can be used. After the registration, new instance variables cannot be added to the class.

It is also important to note that instance methods and instance variables should be added to the corresponding class. However, class methods should be added to the metaclass. It is not possible to add class variables to the metaclass.

Using the new class

Now, when the class is created, it can be used normally:

To retrieve and set instance variables, there are functions object_setInstanceVariable and object_getInstanceVariable. However, they are unavailable in ARC as they required manual memory handling.

Instead setValue:forKeyPath: and valueForKey: can be used.

In order to call a method, objc_msgSend can be used, which takes an object, a selector and its parameters:

Working with properties, instance variables and methods

Getting a list of all properties, instance variables and methods is simple, corresponding functions return a C array that can be cycled through to get all properties/instance variables/methods:

It is also possible to get a value for each property by using objc_msgSend and calling the selector for each property (each property has a selector with property’s name returning the value of the property).

To get variables use class_copyIvarList and to get methods use class_copyMethodList.

Adding properties in Categories

Categories are a really nice way to extend objects without subclassing or owning them. However, it is not possible to add properties to classes by using categories – at least without using runtime.

It turns out that an object can have multiple associated objects, so by using associated objects a category can define new properties.

To add an associated object to the object use:

The first parameter is an object for the association, the second parameter is a key and it should be used to later retrieve the value of the associated object. Therefore a key should be saved for the later use. The third parameter is the value of the associated object and the last parameter defines attributes for the association (similar to object’s properties).

To retrieve the object use:

Method Swizzling

Swizzling is generally a discouraged technique, because of many potential problems, including possible naming conflicts or unexpected changes in behaviour. However, it is sometimes used to avoid duplicate code or extend base functionalities.

The idea behind swizzling is very simple. We take a method and replace its implementation with another method. That way we can extend the method’s functionality in all instances of the class.

The swizzling should always be done in the “load” method. Load is always called when the class is initially loaded and it’s always called only once. Therefore it’s a good place for changing the behaviour of all instances of the class.

The example exchanges two NSString methods (called in a NSString category).

Objective-C Runtime Programming Guide

To explore more about runtime programming, there’s no better place than Apple’s documentation :).

Leave a Reply

Your email address will not be published. Required fields are marked *


*