9 Comments

Method Swizzling in Objective-C

I was recently introduced to method swizzling in Objective-C, and I thought I’d dig into it a bit and write about what I learned.

Objective-C handles method dispatch at runtime. This allows the implementation for a selector (i.e., method) to be changed as a program executes. Method swizzling is the act of swapping the implementations of two selectors as a program runs. This dynamic program modification is similar to monkey patching, a concept supported by other dynamic languages.

Most of the use cases I’ve found for method swizzling involve extending the functionality of an existing method. That is, there is some method that you can’t modify directly, but would like to add additional functionality to (e.g., logging, etc.). It might seem like subclassing would be the preferred way to do this, and for most scenarios it is probably is, though there are places where swizzling makes more sense. To get a feeling for what those scenarios look like, let’s look at an example.

Method Swizzling Example

Suppose we want to print out a log statement whenever viewDidAppear is called on a UIViewController. We could subclass UIViewController with a LoggingViewController and use that as our new base class for view controllers. While this approach is usually reasonable, it may be undesirable for several reasons:

  1. You would need to shim in subclasses for UIViewController, and all of the other controllers that extend it (e.g., UITableViewController, UINavigationViewController, etc.) to ensure that all viewDidAppear calls are logged.
  2. In order to make use of your logging functionality, other projects would have to shim in a LoggingViewController as well (e.g., this solution is not easily extensible to other projects).
  3. If the logging is for debug, this is can be a lot of work for something that you are going to throw away.

An alternative approach would be to swizzle the viewDidAppear method with another method that calls viewDidAppear and then print a log. To achieve this we can do the following:

1. Create a category.

It’s standard to create a category method when using method swizzing to extend existing functionality. For our logging example, let’s create a UIViewController+Logging category on UIViewController.

2. Write the additional functionality.

The next step is to write a category method inside of our UIViewController+Logging class that includes the functionality we wish to add (logging in our example). To do this we’ll write a method called logged_viewDidAppear.


- (void) logged_viewDidAppear:(BOOL)animated {
[self logged_viewDidAppear:animated];
NSLog(@"logged view did appear for %@", [self class]);
}

It may seem this this method makes a nonsensical recursive call to itself, but it won’t actually be doing that after we swizzle it.

3. Swizzle the methods.

The last step is to write the code that actually swaps the memory locations that the two selectors correspond to. This is typically done in the load class method and wrapped in a dispatch_once. In our UIViewController+Logging class we’ll add the following.


+ (void)load {
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
SEL viewWillAppearSelector = @selector(viewDidAppear:);
SEL viewWillAppearLoggerSelector = @selector(logged_viewDidAppear:);
Method originalMethod = class_getInstanceMethod(self, viewWillAppearSelector);
Method extendedMethod = class_getInstanceMethod(self, viewWillAppearLoggerSelector);
method_exchangeImplementations(originalMethod, extendedMethod);
});
}

This code swizzles the method implementation for viewDidAppear with the implementation for logged_viewDidAppear. To get this to compile, you need to import objc/runtime.h. Putting it all together, our UIViewController+Logging class looks like this:


#import
#import "UIViewController+Logging.h"
@implementation UIViewController (Logging)
+ (void)load {
static dispatch_once_t once_token;
dispatch_once(&once_token, ^{
SEL viewWillAppearSelector = @selector(viewDidAppear:);
SEL viewWillAppearLoggerSelector = @selector(logged_viewDidAppear:);
Method originalMethod = class_getInstanceMethod(self, viewWillAppearSelector);
Method extendedMethod = class_getInstanceMethod(self, viewWillAppearLoggerSelector);
method_exchangeImplementations(originalMethod, extendedMethod);
});
}
- (void) logged_viewDidAppear:(BOOL)animated {
[self logged_viewDidAppear:animated];
NSLog(@"logged view did appear for %@", [self class]);
}
@end

Summary

Let’s walk through what will happen when the above code runs.

  1. viewDidAppear is called on a view controller.
  2. Since it is swizzled with our category method, the viewDidAppear call actually invokes our logged_viewDidAppear category method.
  3. The first thing inside of our category method is a call to logged_ViewDidAppear, which since it has been swizzled, is actually a call to viewDidAppear and invokes the standard UIViewController viewDidAppear method.
  4. When that returns, our log statement is executed.

By doing this we have added functionality to all calls to viewDidAppear without subclassing anything or touching its implementation. When we run our program, it will log every call to viewDidAppear. What’s even more interesting is that we can add this new functionality to any other program just by adding our UIViewController+Logging class, without modifying any of its code or needing to understand how it’s implemented. That makes swizzling extremely powerful. If we wanted to we could could enhance our logging category to record statistics about these calls as well such as counts, timestamps, etc.

Swizzling Caution

While this concept is powerful, the internet is full of posts cautioning developers from overusing it. Most of these concerns are nicely captured in this Stack Overflow post.