b2cloud

31st August 2011

Method Swizzling to override in a category

Guides | Tutorial By 5 years ago

Objective C categories are great for extending classes, however if you want to override methods then you’re going to have some problems as you can no longer call the method on your original class owns, usually breaking a lot of functionality the higher up the class food chain you travel (try this on NSObject‘s init, return nil). If you called the same method on self you would end up with an infinite loop, and calling super will skip the original class completely.

As methods in Objective C are called at runtime rather than linked at compile time like most languages we can get around this by switching the methods at runtime. This is called ‘Method Swizzling’ and allows us to overcome the shortcomings of categories that override.

Before I go on I’ll just say that while I have experimented with Method Swizzling, I have never used it on a real project so I can’t guarantee that it will get accepted to the App Store.

First download Jonathan Rentzsch’s JRSwizzle class from GitHub. This provides a category extension of NSObject that will swap methods around at runtime.

Start Xcode and create a view based project. To demonstrate swizzling we will make a category to override UIView‘s initWithCoder: and then set the background colour to red (to confirm our code is being run).

Without making any changes run the project. The view seen has a gray background.

Create a new Objective C category for UIView and add in the .h add the static method

+ (void) swizzle;

And back in the .m (remember to #import “JRSwizzle.h”)

+ (void) swizzle
{
	[UIView jr_swizzleMethod:@selector(initWithCoder:) withMethod:@selector(initWithCoder_swizzle:) error:nil];
}

As you may have guessed we will have to invoke this code ourselves (only once) to swap the methods the first time. We are swapping initWithCoder: with initWithCoder_swizzle:, so it isn’t a ‘real’ override as our code will be written within initWithCoder_swizzle:, but it will give the same effect.

Create the method for initWithCoder_swizzle: inside our UIView category with the following code

- (id) initWithCoder_swizzle:(NSCoder*) aDecoder
{
	[UIView jr_swizzleMethod:@selector(initWithCoder:) withMethod:@selector(initWithCoder_swizzle:) error:nil];
	id result = [self initWithCoder:aDecoder];
	[UIView jr_swizzleMethod:@selector(initWithCoder:) withMethod:@selector(initWithCoder_swizzle:) error:nil];

	[result setBackgroundColor:[UIColor redColor]];

	return result;
}

Providing our static swizzle is called before the first time initWithCoder: is called, initWithCoder_swizzle: will be called instead, where we swizzle them back so we can call the original initWithCoder:, we call it and store the result and then the swizzle back so the next time we get called our custom method is called first. After (or before) this we can run whatever code we please, just be aware of infinite loops if using swizzled objects within a swizzled method. In our example we set the background colour to red and then return the newly created view.

The last step is to invoke our swizzle the first time. To ensure this is done before any objects are created of that type, do this first thing in the main.m (preferably the first line after the autorelease pool is created, remember to import your category header).

[UIView swizzle];

Run the project and hzaar, you are overriding a method through a category!

Download the UIView+Swizzle source

  • Pingback: Monitoring all iOS touches | b2cloud()

  • Michael Goff

    You do not need to swizzle and then swizzle back in your initWithCoder_swizzle: method. jr_swizzleMethod… actually swaps IMP’s so to call the original method just call initWithCoder_swizzle: (as that selector now points at the original method IMP).

    So you method becomes:

    – (id) initWithCoder_swizzle:(NSCoder*) aDecoder
    {
    id result = [self initWithCoder_swizzle:aDecoder];

    [result setBackgroundColor:[UIColor redColor]];

    return result;
    }

  • Michael Goff

    You do not need to swizzle and then swizzle back in your initWithCoder_swizzle: method. jr_swizzleMethod… actually swaps IMP’s so to call the original method just call initWithCoder_swizzle: (as that selector now points at the original method IMP).

    So you method becomes:

    – (id) initWithCoder_swizzle:(NSCoder*) aDecoder
    {
    id result = [self initWithCoder_swizzle:aDecoder];

    [result setBackgroundColor:[UIColor redColor]];

    return result;
    }

  • Leonard Pauli
  • Claes

    Thanks! Do you know if Apple allows swizzling of private classes (iOS) and/or private methods in the App Store?

Recommended Posts

iOS Frameworks

Post by 5 years ago

After developing iPhone applications for a while you tend to develop some 'must have' pieces of code that you use on all of your apps. Being able to share the code on multiple projects is...

Got an idea?

We help entrepreneurs, organizations and established brands from around
the country bring ideas to life. We would love to hear from you!