Swizzling Methods for the win!
Method swizzling is something we can accomplish with the Objective-C runtime which has many powerful C functions in the runtime header.
#include <objc/runtime.h>
Now before we start coding it's important to have at least a high level understanding of how Objective-C methods work in the runtime. When the objective-c runtime loads from a binary being executed, all the objects that are used are templatized - that is to say they have a their structure defined in memory with things such as what instance variables an instance has and the names of those instances (for KVC) and the instance methods and class methods a class has and the associated selector (
SEL
) used to call a particular method, which is effectively just a function that has 2 leading arguments: self
, the pointer to the instance being executed on; and _cmd
the selector that is executing. This association of selectors and functions is maintained in a map for the object template. This means that if we can change the function that a selector is associated with in the map to a new function we've got the amazing ability to change the behavior of objects at runtime. Insane right!Things that we will want to be able to do easily:
- If a method exists, we want to be able to exchange it with a new implementation that we can define in a category
- If a method doesn't exist, we want to be able to set our implementation as the method
- We want to do all of this for both instance methods AND for class methods
The biggest reason we want our functions to both be able to add the method if missing and replace the method if it exists is that when it comes to inheritance, it is very possible that you might want to swizzle out a method on a subclass that is implemented in a super class, in which case that class instance would NOT have the method for swizzling, but it's parent would. It takes great care from the programmer to ensure that the method implementations are savvy enough to handle either scenario though.
Let's write out the function declarations for what we'd like to see and then tackle them one at a time.
BOOL SwizzleInstanceMethods(Class class, SEL dstSel, SEL srcSel); BOOL SwizzleClassMethods(Class class, SEL dstSel, SEL srcSel);
So you can see the three necessary arguments for method swizzling, the Class to modify, the destination selector and the source selector. Knowing the Objective-C runtime functions that can accomplish this yields a straightforward solution. I'll address each, starting with instance method swizzling.
BOOL SwizzleInstanceMethods(Class class, SEL dstSel, SEL srcSel) { Method dstMethod = class_getInstanceMethod(class, dstSel); Method srcMethod = class_getInstanceMethod(class, srcSel); if (!srcMethod) { @throw [NSException exceptionWithName:@"InvalidParameter" reason:[NSString stringWithFormat:@"Missing source method implementation for swizzling! Class %@, Source: %@, Destination: %@", NSStringFromClass(class), NSStringFromSelector(srcSel), NSStringFromSelector(dstSel)] userInfo:nil]; } IMP srcIMP = method_getImplementation(srcMethod); if (class_addMethod(class, dstSel, srcIMP, method_getTypeEncoding(srcMethod))) { class_replaceMethod(class, dstSel, method_getImplementation(dstMethod), method_getTypeEncoding(dstMethod)); } else { method_exchangeImplementations(dstMethod, srcMethod); } return (method_getImplementation(srcMethod) == method_getImplementation(class_getInstanceMethod(class, dstSel))); }
First, we will get the destination and source
Method
s. We use the objc function class_getInstanceMethod
to accomplish this. Method
is simply a pointer to a struct with all of the necessary method information disconnected from the selector used to call it.Next, we validate that there is a source method to swizzle. If the user hasn't implemented the source method as a category method, then there's no point in swizzling, so we'll throw an exception.
Then we get into the actual act of Swizzling. The logic here is like this:
- if the destination method doesn't exist, make the destination method be the source method
- AND replace the old source method with the original destination method (which will be NULL and effectively removes the old source method)
- if the destination method already existed, exchange the source and destination methods so they are both still in the runtime, but effectively have traded what selector they respond to
Finally, we want to validate success by ensuring the source method we'd set out to swizzle to respond to the destination selector does in fact respond to it with a simple pointer comparison.
Onto class method swizzling:
BOOL SwizzleClassMethods(Class class, SEL dstSel, SEL srcSel) { Class metaClass = object_getClass(class); if (!metaClass || metaClass == class) // the metaClass being the same as class shows that class was already a MetaClass { @throw [NSException exceptionWithName:@"InvalidParameter" reason:[NSString stringWithFormat:@"%@ does not have a meta class to swizzle methods on!", NSStringFromClass(class)] userInfo:nil]; } return SwizzleInstanceMethods(metaClass, dstSel, srcSel); }
Wait, that seems too easy! Well, the nice thing about class methods, is that they are merely instance methods on the Class instance of a Class, or the MetaClass. So we just ensure that the class passed in has a metaClass that we can swizzle, and we're good to go!
Now one thing we could do is have a 3rd function that is effectively the same implementation as
SwizzleInstanceMethods
but with 1 additional parameter: BOOL isMetaClass
. With that function, call it SwizzleInstanceMethodsInternal
, we could validate that the provided class was either the MetaClass (has a MetaClass equal to itself) or a normal class that has an actual MetaClass. From there we could have our two functions call it with isMetaClass either YES or NO, but I'll leave that as an exercise for anyone wanted to go to that level.There we go! We can now easily swizzle one method for another in order to port functionality of a method forward or backward - or to overwrite the functionality of existing methods. A word of warning, with this powerful tool in place, one must take great responsibility as modifying the object behavior can be dangerous if care is not taken.
I will follow this post up with a post to swizzle the description method on the NSData class so that it can behave in a configurable manner.
Find this code on github
I have heard of method swizzling before, but never knew how it was done, thank you for this explanation. It will take some time to digest though.
ReplyDeleteI do have a question about it though. As you said, "it is very dangerous" and "9 times out of 10 you can solve the problem without modifying the runtime". So my question is... when IS a time you should use method swizzling?
I guess if you want to modify how many built in frameworks interact with the swizzled method, you may want it, but I'm not sure how adding something to have a built in framework use a modified NSString method would be conducive to not crashing. I'm sure it is useful, but why would I swizzle a method, instead of just making a completely new method in a category and use it instead for my classes?
Method swizzling can come in useful when trying to modify the behavior of an existing method. For example, say you wanted to log the class name of every view controller (both belonging to your app and system provided VCs) whose view did appear. You could swizzle viewDidAppear:, have the swizzled method call the original implementation and add an NSLog(@"[%@ %@%@]", NSStringFromClass([self class]), NSStringFromSelector(_cmd), (animated) ? @"YES" : @"NO") to get what you need done. This would be a case of modifying the runtime to improve your ability to debug/observe the behavior of your app.
DeleteThere are lots of reasons out there, but I can't simply enumerate them here - I hope this example can get you thinking about it. Otherwise, you should try a decision tree:
1) Am I trying modifying the existing behavior of a system method? (Yes, continue)
2) Can I add a category method to achieve what I want? (No, continue)
3) Can I subclass in order to achieve what I want? (No, continue)
4) Method swizzling is an option
Last recommendation:
Only swizzle to extend the behavior of a method. Don't ever change the underlying behavior of an existing method, since that is a dependency that the system will be expecting.
Thanks You for writing such a wonderful self explanatory blog. I have one question here:
ReplyDelete- When i am trying to access "self" in my swizzled method, it is not the object of my class rather it is object of the class whose method i swizzled. Is there a way to access actual "self" object..?
You should not swizzle across objects, that can have unintended side effects including not having access to the desired object instance with "self". Per the examples provided, you should swizzle a category method to replace an existing method for the same class.
ReplyDeletebig help because there are websites
ReplyDeletethat post top 25 tech companies for the year, top 10, etc.
wifikill pro apk
Dababy
HOTSCHEDULES LOGIN
apa pool login
Hello nice blog
ReplyDelete