Document directory
- Method swizzling is not Atomic
- Changes behavior of un-owned code
- Possible naming conflicts
- Swizzling changes the method's arguments
- The order of swizzles matters
- Difficult to understand (looks recursive)
- Difficult to debug
Objective-C hook solution (1): Method swizzling
Without the implementation source code of a class, is there a more flexible way to change the implementation of one of the methods besides inheriting it to rewrite and using the method of class name duplication? In objective-C Programming, how does one implement hook? The title is a little big. I plan to summarize it in several articles.
This article mainly introduces the hook for selector. The main character is spoiler by the title ---- method swizzling.
Method swizzling Principle
Calling a method in objective-C is actually sending a message to an object. The only basis for finding a message is the selector name. The dynamic characteristics of objective-C can be used to steal the method corresponding to the selector during running, so as to hook the method.
Each class has a list of methods that store the selecing between selector names and method implementations. IMP is similar to a function pointer and points to the specific method implementation.
We can use method_exchangeimplementations to exchange IMP in two methods,
We can use class_replacemethod to modify the class,
We can use method_setimplementation to directly set the imp of a method,
......
In the final analysis, the imp of selector is stolen, as shown in:
Method swizzling practices
For example, I want to hook up nsarray's lastobject method. It takes only two steps.
Step 1: add my own lastobject to nsarray
#import "NSArray+Swizzle.h"@implementation NSArray (Swizzle)- (id)myLastObject{ id ret = [self myLastObject]; NSLog(@"********** myLastObject *********** "); return ret;}@end
At first glance, isn't it recursive? Don't forget that this is the selector for which we are going to change imp. [self mylastobject] will execute the real [self lastobject].
Step 2: Change imp
#import <objc/runtime.h>#import "NSArray+Swizzle.h"int main(int argc, char *argv[]){ @autoreleasepool { Method ori_Method = class_getInstanceMethod([NSArray class], @selector(lastObject)); Method my_Method = class_getInstanceMethod([NSArray class], @selector(myLastObject)); method_exchangeImplementations(ori_Method, my_Method); NSArray *array = @[@"0",@"1",@"2",@"3"]; NSString *string = [array lastObject]; NSLog(@"TEST RESULT : %@",string); return 0; }}
Console output log:
2013-07-18 16:26:12.585 Hook[1740:c07] ********** myLastObject *********** 2013-07-18 16:26:12.589 Hook[1740:c07] TEST RESULT : 3
The results were very gratifying. Could you not help but want to add todo to the loadrequest of uiwebview?
Encapsulation of method swizzling
The rnswizzle found on GitHub is recommended to you. You can search for it.
//// RNSwizzle.m// MethodSwizzle#import "RNSwizzle.h"#import <objc/runtime.h>@implementation NSObject (RNSwizzle)+ (IMP)swizzleSelector:(SEL)origSelector withIMP:(IMP)newIMP { Class class = [self class]; Method origMethod = class_getInstanceMethod(class, origSelector); IMP origIMP = method_getImplementation(origMethod); if(!class_addMethod(self, origSelector, newIMP, method_getTypeEncoding(origMethod))) { method_setImplementation(origMethod, newIMP); } return origIMP;}@end
Method swizzling dangerous and not dangerous
To address this problem, I have seen a satisfactory answer on stackoverflow. Here I will translate the answer and record it in this article to share it:
Using Method swizzling for programming is like using a sharp knife when you cut a dish. Some people are afraid of sharp knives because they are worried about cutting themselves. But in fact, using a blunt knife is often more prone to accidents, and the knife is more secure.
Method swizzling can help us write better, more efficient, and easy to maintain code. However, if you abuse it, it will also lead to bugs that are difficult to troubleshoot.
Background
Like the design pattern, if we figure out the portal of a pattern, we know whether to use it or not. The Singleton mode is a good example. It is controversial, but many people still use it. The same is true for method swizzling. Once you really understand its advantages and disadvantages, you should have your own opinions on whether to use it or not.
Discussion
Here are some traps of method swizzling:
- Method swizzling is not Atomic
- Changes behavior of un-owned code
- Possible naming conflicts
- Swizzling changes the method's arguments
- The order of swizzles matters
- Difficult to understand (looks recursive)
- Difficult to debug
I will analyze these points one by one to improve understanding of method swizzling and understand how to deal with them.
Method swizzling is not Atomic
The methods implemented by method swizzling I have seen are basically safe in concurrent use. In 95% of the cases, this is not a problem. Generally, you want to replace the implementation of a method to be effective throughout the life cycle of the program. That is to say, you will put the method swizzling modification method implementation operations in a plus sign method + (void) load, and call and execute at the beginning of the application. You will not encounter concurrency problems. If you perform swizzle in the + (void) initialize initialization method, then ...... Rumtime may die in a strange state.
Changes behavior of un-owned code
This is a problem with swizzling. Our goal is to change some code. The swizzling method is very important. When you modify not only an nsbutton instance, but also all nsbutton instances in the program. Therefore, you should be careful when using swizzling, but do not always avoid it.
Imagine that if you override a class method without calling the parent class method, this may cause problems. In most cases, the parent class method is expected to be called (at least in this document ). If you do this in the swizzling implementation, this will avoid most problems. Let's call the original implementation. Otherwise, you have a great deal of effort to consider the code security issue.
Possible naming conflicts
Naming conflicts run through the entire cocoa problem. We often add a prefix before the class name and category method name. Unfortunately, naming conflicts are still a torment. However, swizzling does not have to worry too much about this issue. We only need to make a small change before naming the original method. For example, we usually name it like this:
@interface NSView : NSObject- (void)setFrame:(NSRect)frame;@end@implementation NSView (MyViewAdditions)- (void)my_setFrame:(NSRect)frame { // do custom work [self my_setFrame:frame];}+ (void)load { [self swizzle:@selector(setFrame:) with:@selector(my_setFrame:)];}@end
This code runs correctly, but what happens if my_setframe is defined elsewhere?
This problem exists not only in swizzling, but here is an alternative solution:
@implementation NSView (MyViewAdditions)static void MySetFrame(id self, SEL _cmd, NSRect frame);static void (*SetFrameIMP)(id self, SEL _cmd, NSRect frame);static void MySetFrame(id self, SEL _cmd, NSRect frame) { // do custom work SetFrameIMP(self, _cmd, frame);}+ (void)load { [self swizzle:@selector(setFrame:) with:(IMP)MySetFrame store:(IMP *)&SetFrameIMP];}@end
It seems that objectice-C is not so good (the function pointer is used), which avoids the Name Conflict of selector.
Finally, a perfect swizzle method is defined:
typedef IMP *IMPPointer;BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) { IMP imp = NULL; Method method = class_getInstanceMethod(class, original); if (method) { const char *type = method_getTypeEncoding(method); imp = class_replaceMethod(class, original, replacement, type); if (!imp) { imp = method_getImplementation(method); } } if (imp && store) { *store = imp; } return (imp != NULL);}@implementation NSObject (FRRuntimeAdditions)+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store { return class_swizzleMethodAndStore(self, original, replacement, store);}@end
Swizzling changes the method's arguments
I think this is the biggest problem. It will be a problem to call method swizzling normally.
[self my_setFrame:frame];
Directly call my_setframe:. What runtime does?
objc_msgSend(self, @selector(my_setFrame:), frame);
Runtime is used to find the method implementation of my_setframe:. The _ cmd parameter is my_setframe:, but in fact, the method implementation found by Runtime is the original setframe.
A simple solution: Use the swizzling definition described above.
The order of swizzles matters
Note the execution sequence of multiple swizzle methods. Suppose setframe: It is defined only in nsview. Imagine executing it in the following order:
[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];
What happens when the method on nsbutton is swizzled? Well most swizzling will ensure that it's not replacing the implementation of setframe: for all views, so it will pull up the instance method. this will use the existing implementation to re-define setframe: In the nsbutton class so that exchanging implementations doesn't affect all views. the existing implementation is the one defined on nsview. the same thing will happen when swizzling on nscontrol (again using the nsview implementation ).
When you call setframe: on a button, it will therefore call your swizzled method, and then jump straight to the setframe: method originally defined on nsview. the nscontrol and nsview swizzled implementations will not be called.
But what if the order were:
[NSView swizzle:@selector(setFrame:) with:@selector(my_viewSetFrame:)];[NSControl swizzle:@selector(setFrame:) with:@selector(my_controlSetFrame:)];[NSButton swizzle:@selector(setFrame:) with:@selector(my_buttonSetFrame:)];
Since the view swizzling takes place first, the control swizzling will be able to pull up the right method. likewise, since the control swizzling was before the button swizzling, the button will pull up the control's swizzled Implementation of setframe :. this is a bit confusing, but this is the correct order. how can we ensure this order of things?
Again, just use load to swizzle things. if you swizzle in load and you only make changes to the class being loaded, you'll be safe. the load method guarantees that the super class load method will be called before any subclasses. we'll get the exact right order!
The original Article is posted in this section. It's too easy to translate ...... Summary:When swizzle is an object of multiple inherited classes, it starts from the subclass object. If swizzle is the parent class object, the original method implementation cannot be obtained when swizzle is the next subclass object.
(I would like to thank qq3732017202 for its prompt in the comments. I would like to make a correction here. Thank you very much)
When swizzle is an object of multiple inherited classes, it starts with the parent object. This ensures that the subclass method is implemented by swizzle in the parent class. In + (void) load, swizzle will not make an error because the load class method will be called from the parent class by default.
Difficult to understand (looks recursive)
(Implementation of the new method) Looks like recursion, but look at the swizzling encapsulation method provided above, it is easy to understand.
This problem has been completely solved!
Difficult to debug
The backtrace played during debugging is mixed with the swizzle method name, which is a mess! The swizzle solution described above makes the printed method name in backtrace clear. But it is still difficult to debug, because it is difficult to remember the impact of swizzling. Write a document for your code (even if only you can see it ). Developing a good habit is no more difficult than debugging multithreading.
Conclusion
If appropriate, method swizzling is still safe. A simple and secure method is to only swizzle in load. Like many other things, it is dangerous, but you can use it correctly and appropriately by understanding it.
(All original articles and translations in this blog are signed by knowledge sharing-non-commercial use-share 2.5 in the same way)