Extending Methods in a Category by Method Swizzling
Probably you have been in the situation to being writing a category and at some point exclaim “F**K! It’s a category!”. Yes, and probably was because you accidentally tried to override a method without remembering that effectively, you were writing in a category.
In the largest percentage of situations, when you need to override a method, you want to write a subclass; when instead you want to write a category for a class, it is because you probably need to have some new methods or properties that you’d like to be used from every subclass.
There is only another situation which could confuse you, and this is when you are in both the previous cases, together.
Let’s suppose you want to add some generic code in
-layoutSubviews. The first thing which comes in your mind is:
1 2 3 4 5 6
Wait..let’s suppose also you would like every subclass of
UIView to implement an extra code, like for instance logging something or checking a property before do something else.
Make this by subclassing would be time-consuming and would implies code repetition as you should create a subclass and override the
-layoutSubviews method for
UILabel and so on, as well as set these subclasses for any new and old class…
Exactly, it is unthinkable.
When we need to add methods or properties to a class, we normally write a single Category for the higher superclass we need to extend. In this case that class would be
UIView. But, we still have a problem: we don’t want to add a new method, we want to extend an existing method.
We know that this is not possible, as a category is a class extension, and implementing an existing method like
-layoutSubviews, would results in a method repetition. As figurative and easy example think just to something like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
So, what can we do?
Playing around the problem with Method Swizzling
Method Swizzling is a technique with which, roughly speaking, you can swipe two methods making the selector of the first method calls the second method and vice-versa.
The idea is to create a new method and swap his implementation with the
-layoutSubviews one. Therefore, calling
-layoutSubviews, will be effectively executed the new method with our new code, and in the new method, we will also call his selector, which instead will executes the
-layoutSubviews original method.
Calm down, I know, it’s not easy to explain and to understand with these few lines above. So I created a couple of diagrams to have in mind a visual image of the situation:
The normal condition we have is the following:
The condition we want to pull out, is instead this:
Being not possible insert extra code in the
-layoutSubviews method, in a category, we have to redirect the flow through our custom method, in order to manipulate it.
Now, it’s the time to have a look at the explained code step by step.
Let’s start by saying that Apple offers a set of C functions, useful to manipulate properties, ivars, classes, methods and so on, at runtime. We are gonna see some of these in order to sort our situation.
Add the new method
The firs thing to do, is to add a new (empty for now) method to our category. This method, will be the one containing our code extension or our part of code we want to be executed when
-layoutSubviews is called:
1 2 3 4 5 6 7 8
Write the swizzling method - First part
The method containing the swizzling code, needs to be a class method. We will see the reason later.
We must to remeber that we are doing this to have an extended
-layoutSubviews default implementation:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
class_getInstanceMethod return the method which corresponds to the implementation of the selector and the class specified, or
NULL if the specified class or its superclasses do not contain an instance method with the specified selector.
method_getImplementation return a function pointer of type
It is important to underline that each class has a dispatch table, and as Apple says:
This table has entries that associate method selectors with the class-specific addresses of the methods they identify.
Method is the glue between
IMP, and at this point we have completely access to these information.
Write the swizzling method - Second part
Now, we need to exchange the two implementations:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
What’s happening in this code it is not really hard as could to seem.
The first thing to do is to try to add the method
-layoutSubviews. In fact, also if this method is in the default implementation of
UIView, it could not be implemented in the subclass of
UIView which the developer is gonna write. It means, that could not exists an associated implementation for that subclass, but just for the superclass.
Using the function
BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types ), we can add in a
Class a method associated to an implementation and called by a selector.
Therefore, if the method will results to be not implemented, the returned var will be
YES; if instead there will be an error due for example the fact just explained above, the resulted var will be equals to
In this code, we are adding a method called by the
-layoutSubviews selector, but with the implementation of our new
-mg_layoutSubviews method. The last parameter is an array of characters that describe the types of the arguments to the method.
Please note that
thisClass is equals to
self, but remember that we are in a class method (
+), and it means that
self is exactly the class
UIView and not an her
Now, coming back to the variable result:
YES, with the same approach, we have just to replace the other method associating the
-mg_layoutSubviews selector to the implementation of the
NO, means that the method and his implementation already exist, and we have just to exchange the two implementations between them.
-layoutSubviews (by the swizzled selector)
Even if we exchanged the two implementations, we are still missing a part of our final diagram above: the arrow which calls the
In fact, at the moment,
UIView will calls
-layoutSubviews, which due the swizzling, it is associated with our new method. But we still have that method empty.
What we need, is close the circle executing also the original
-layoutSubviews implementation. Just remember, that now that implementation can be executed calling the new selector
-mg_layoutSubviews. So let’s write it by code:
1 2 3 4 5 6 7 8 9 10
Now the circle is closed and we have our method, effectively, “extended”.
As saw above, we wanted to use the class method
+mg_extendsLayoutSubviews to write our swizzling code. So now, we have to call it in order to launch all this process.
The best method in which do it, is
+load. This because that method is executed when the class is initially loaded.
Basically we are saying to the class to exchange his
Method (and therefore
IMP) association in the dispatch table, and we are saying this directly when the class is loaded:
1 2 3 4 5 6 7
Because we are changing this global state of the class, we need to do it extremely careful. For this reason, we use the
dispatch_once function, which offers us the atomicity, and the guarantee that the code inside will be executed only once.
Being called by the class method
+load, this explains why
+mg_createNewLayoutSubviews is a class method too.
In the 90% of situations, you will not need to use this approach, but sometimes could be really useful. Just be sure you have not other solutions before to proceed, because since this is an extremely powerful way, it could be also extremely dangerous if you don’t pay the maximum attention.
Follow me on Twitter!