objective-c Hook Scheme (i): Method swizzling
In the absence of a class implementation of the source code, want to change the implementation of one of the methods, in addition to inheriting it rewrite, and the use of category name method of violence preemptive, there is more flexible method? In Objective-c programming, how can hooks be implemented? The title is a bit big, the plan is divided into several to summarize.
This article mainly introduces the hook for selector, the protagonist is the title plays through the ———— Method swizzling.
Method swizzling Principle
Calling a method in Objective-c is actually sending a message to an object, and the only way to find the message is by selector's name. By using the dynamic characteristics of objective-c, the corresponding method of selector can be realized at runtime to achieve the purpose of linking to the method.
Each class has a list of methods that hold the mapping of selector's name and method implementation. IMP is a bit like a function pointer, pointing to a concrete method implementation.
We can use Method_exchangeimplementations to exchange the IMP in 2 ways,
We can use Class_replacemethod to modify the class,
We can use Method_setimplementation to directly set the IMP for a method,
......
In the final analysis, the Imp of selector is changed, as shown:
Method swizzling Practice
For example, I'd like to hook up Nsarray's Lastobject method in just two steps.
First step: Give Nsarray a lastobject of my own
- #import "Nsarray+swizzle.h"
- @implementation Nsarray (Swizzle)
- -(ID) mylastobject
- {
- ID ret = [self mylastobject];
- NSLog (@"********** mylastobject ***********");
- return ret;
- }
- @end
At first glance, is this not recursive? Don't forget that this is the selector,[self mylastobject [self lastobject] that we are going to switch to IMP] will do.
Step Two: Exchange 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 are very pleasing, is not unbearable to UIWebView loadrequest: add TODO it?
Package of Method swizzling
Before the rnswizzle found on GitHub, recommend to everyone, you can search.
- //
- 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 Danger Not dangerous
In response to this problem, I saw a satisfactory answer on the StackOverflow, here translation, summary records in this article, to show sharing:
Using Method swizzling programming is like cutting vegetables with a sharp knife, some people fear cutting to their own so afraid of sharp knives, but in fact, the use of blunt knife is often more prone to accidents, and the knife is more secure.
Method swizzling can help us write better, more efficient, and maintainable code. But if you misuse it, it can also lead to bugs that are hard to troubleshoot.
Background
Like design patterns, if we know the doorway to a pattern, we know how to use it or not. The singleton pattern is a good example, it is controversial but many people still use it. Method swizzling is also the same, once you really understand its advantages and disadvantages, use it or not you should have your own point of view.
Discuss
Here are some of the pitfalls 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 each of these points to improve understanding of method swizzling and understand how to deal with it.
Method swizzling is not atomic
The methods I've seen using method swizzling are basically safe when used concurrently. This is not a problem in 95% of cases. Usually you replace the implementation of a method with the hope that it will work throughout the lifetime of the program. In other words, you would put the method swizzling the action implemented in a plus method + (void) load, and invoke execution at the beginning of the application. You will not encounter concurrency problems. If you are 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 for swizzling. Our goal is to change some code. The Swizzling method is an often important thing, when you are not just modifying an instance of a Nsbutton class, but all the Nsbutton instances in the program. So be careful when you swizzling, but don't always try to avoid it.
Imagine that if you rewrite a method of a class and do not call this method of the parent class, this can cause problems. In most cases, the parent method expects to be called (at least this is what the document says). If you do this in the swizzling implementation, this avoids most of the problems. or call the original implementation, if not, you have a great effort to consider the security of the code.
Possible Naming conflicts
The naming conflict runs through the whole cocoa problem. We often prefix the class name with the category method name. Unfortunately, the naming conflict is still a torment. But Swizzling does not have to think too much about this issue. We just need to make a small change to the name of the original method before naming it, like we usually name it:
- @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 works correctly, but what happens if my_setframe: defined elsewhere?
This problem is not just in swizzling, there is an alternative workaround:
- @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 doesn't look so objectice-c (with a function pointer), which avoids the selector naming conflict.
Finally, a more perfect definition of the Swizzle method is given:
- typedef IMP *imppointer;
- BOOL Class_swizzlemethodandstore (Class class, SEL original, IMP replacement, Imppointer store) {
- Imp imp = NULL;
- 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 would be a problem to call method swizzling normally.
- [Self my_setframe:frame];
Call My_setframe directly: What the runtime does is
- Objc_msgsend (Self, @selector (my_setframe:), frame);
Runtime to find My_setframe: The method of implementation, the _cmd parameter is my_setframe:, but in fact runtime found the method implementation is the original setframe:.
One simple workaround: Use the swizzling definition described above.
The Order of Swizzles matters
The order of execution of multiple swizzle methods also needs attention. Suppose Setframe: only defined in NSView, imagine doing 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 if the method on Nsbutton is swizzled? Well most swizzling would ensure that it's not replacing the implementation of setframe:for all views and so it'll pull up The instance method. This would use the existing implementation to Re-Define setframe:in the Nsbutton class so, exchanging implementations doesn ' t affect all views. The existing implementation is the one defined on NSView. The same thing would happen when swizzling on NSControl (again using the NSView implementation).
When you call Setframe:on a button, it'll therefore call your swizzled method, and then jump straight to the SE Tframe:method originally defined on NSView. The NSControl and NSView swizzled implementations won't 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 would be a able to pulling up the right method. Likewise, since the control swizzling was before the button swizzling, the button would pull up the control ' s swizzled impl Ementation of Setframe:. This was a bit confusing, but the correct order. How can we ensure this order of things?
Again, just use load to swizzle things. If you are swizzle in load and you are changes to the class being loaded, you'll be safe. The Load method guarantees that super class Load method is called before any subclasses. We ' ll get the exact right order!
This paragraph pasted the original text, hard translation is too clumsy ... As a summary , the object of multiple inheriting classes is swizzle, starting with the subclass object. If the parent class object is swizzle first, then the swizzle will not be able to get the real original method implementation.
(Thanks for qq373127202 's reminder in the comments, please correct it here, thank you very much)
When multiple objects of a class that have an inheritance relationship swizzle, start with the parent object first. This ensures that the subclass method gets the Swizzle implementation in the parent class. Swizzle does not go wrong in the + (void) load, because the load class method is called from the parent class by default.
Difficult to understand (looks recursive)
(The implementation of the new method) looks like recursion, but looking at the Swizzling encapsulation method already given above, it is easy to read.
This problem has been completely solved!
Difficult to debug
Debug when the BackTrace, which doped with the Swizzle method name, a mess Ah! The swizzle scheme described above makes the method names printed in BackTrace very clear. But it's still hard to debug, because it's hard to remember what swizzling has affected. Write a good document for your code (even if you are the only one who will see it). Getting into a good habit is no more difficult than debugging a multithreaded problem.
Conclusion
If used properly, method swizzling is still safe. A simple and safe way to do this is to only swizzle in load. Like many other things, it is dangerous, but it can be used correctly if you understand it.
iOS Security reprint