Class loading, initialization and method swizzling

Rarely iOS and OS X developers might need to do initialization before their class is used. This might be called as “static constructors” in other development paradigms. Objective-C runtime also offers similar mechanisms which we will discuss in this topic.

NSObject class defines two class methods responsible for class initialization:

+(void)load;
+(void)initialize;


Class loading and initialization order (according to Apple’s documentation):

  1. All classes from frameworks you link to are loaded and initialized.
  2. All classes from your code (bundle) are loaded.
  3. All C++ static initializers and C/C++ __attribute__(constructor) functions in your bundle are called.
  4. All initializers in frameworks that link to your code.

So, +load method is called when class is loaded from bundle (you could read this as “when application starts” for most cases).

If you try to reference self (which points to a Class object in this case) from +load method, this will cause +initialize to be called, therefore you might mess up the whole thing. So, you should be careful, during +load your class is not yet initialized.

One more thing about +load. It is called really early in application life-cycle. So early, that even autorelease pools are not available, so you should not count on those in your code.

Then when +initialize is called? The answer is – just before first use of the class (like when you call other class methods, or you create and instance of the class). This means, +initialize might not be called at all (in contrast with +load which will be called always).

Objective-C runtime handles +load and +initialize methods differently.

  • +load will be called only once on each class and each class category only if class (or category) explicitly implements this method.
  • +initialize is called in more traditional manner – if category implements +initialize, it will override implementation of the class. Also, if subclass does not implement +initialize runtime will call parent’s implementation, if any. This might cause +initialize to be called several times, and developer should protect his code from this.

These differences mean that you should not make any “protections” in +load method, but you MUST check class in +initialize:

@interface MyClass : NSObject

// ...

@end

@implementation MyClass

+(void)initialize
{
    if (self == [MyClass class]) 
    {
        // do initialization
    }
}

// ...
@end

You should also avoid any time consuming operations in +load (and in +initialize too). Since this +load is called in application start sequence – you need to save as much time as possible, or your app could be killed.

One example of +load usage is to swizzle methods. So, let’s talk a little about this.

Objective-C runtime offers tons of APIs for developers to manage classes. One of those is – to replace one method implementation with another. I won’t go into details, let’s just list some of data types:

  1. Class – defines class object, used to access class properties ([object class] will return Class object).
  2. SEL – selector, method signature like setTitle:forState:, different classes could have methods with identical selectors.
  3. IMP – method implementation, points to the actual code which is being executed.
  4. Method – method object, includes all information about method, including selector and implementation and class it belongs to.

And just two functions:

  1. class_getInstanceMethod – returns Method for specific Class and SEL.
  2. method_exchangeImpelementations – exchange IMP‘s of two Method‘s passed to this function.

Let’s look at example. We’re defining a category on UIButton to log all title changes in your application.

@implementation UIButton (Logger)

-(void)logAndSetTitle:(NSString*)title forState:(UIControlState)state
{
    NSLog(@"changing title [%@] to [%@] (state %d)", self.titleLabel.text, title, state);
    [self logAndSetTitle:title forState:state];
}

+(void)load
{
    Method setTitleOrig = class_getInstanceMethod(self, @selector(setTitle:forState:));
    Method setTitleLog = class_getInstanceMethod(self, @selector(logAndSetTitle:forState:));
    method_exchangeImplementations(setTitleOrig, setTitleLog);
}

We’re defining new method -logAndSetTitle:forState: which has the same parameters as UIButton‘s -setTitle:forState:.

In category’s +load swizzling these methods. This means that when any code is calling -setTitle:forState: on any UIButton, implementation of -logAndSetTitle:forState: will be called.

Now, look and the implementation of -logAndSetTitle:forState:. It seems that it has infinite recursion (it calls itself without any conditions). However, after method_exchangeImplementations, implementation of -logAndSetTitle:forState: is now standard UIButton‘s -setTitle:forState:. So, any call to change title of UIButton will now go first into our code and then to default implementation. And all that – without any subclassing.

However, with all this fun, there is a catch. You can’t use method swizzling in App Store apps, you can’t mess up with frameworks. So, know this, but don’t use in production (it could be useful for debugging though).

Apple documentation: Objective-C runtime reference, NSObject reference.

Example source code is available on GitHub.

Advertisements

2 thoughts on “Class loading, initialization and method swizzling

  1. Edgar

    Thanks for the insights. I just wanted to mention that you can actually do method swizzling in production code in App Store releases, Apple won’t reject your app just because of that. Although I don’t personally recommend it because in spite of it being really useful in certain use cases, it is hard to debug swizzled code and you should really know how it works and expect to find it in order to avoid spending much time debugging a possible bug/error.

    But some times method swizzling is almost the only solution, for instance when one of Apple’s frameworks has a bug and you want to perform a workaround instead of waiting for the next iteration to be released, but since we do not have access to Apple’s source code, method swizzling comes to rescue.

    Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s