[Objective-C learning to apply] Method Swizzling

Method Swizzling does not yet have a widely accepted translation, and I personally think that the one that is easier to understand is method transformation. Simply put, it is a technique for modifying the implementation of methods in a class at runtime.
In this article, we will walk through the ins and outs of the method transformation.

When striving for the ins and outs of this situation, we need to grasp three principles: learn things to understand, explain the profound things in a simple way, and apply what we have learned.
Among them, learning from things is the method, simple explanation is the result, and the purpose is to apply what you have learned.

1. Ge Wu: understand its principle

The technical basis of method transformation is that Objective-C is an object-oriented language with runtime dynamic characteristics. If you want to thoroughly understand the implementation details of method transformation, you need to have an overall understanding of Objective-C, especially its runtime. The principle.
What we are here is runtime.

1.1. World structure

Objective-C is an object-oriented language, so object-oriented is the core idea. The following picture expresses this clearly enough.
Objective-C world view

It is stated in the "Tao De Jing": Dao produces one, one life two, two produces three, and three produces all things.
The Objective-C world view is very similar to the Taoist world view.

The "Tao" of Objective-C

Everything is an object. This is the highest standard of the entire world.

The "One" of Objective-C

Since everything is an object, and considering the concept of abstraction and inheritance in object-oriented thinking, there must be the first object in this world, which is also the root of all objects.
And this root is "one", NSObject.

The "two" of Objective-C

We know that NSObject is the base class of all classes, so it is a class. But "Tao" also said "Everything is an object", what about this NSObject? Yes, it is also an object. By extension, all classes are actually objects.
But we know that in Objective-C, objects are all types, and they are all defined by classes. So, what is the type of NSObject? Or what is the class of the class?
The type of NSObject is the MetaClass of NSObject. NSObject is an instance of the NSObject metaclass. All classes have a corresponding metaclass.
And the MetaClass of this NSObject is "two"!

Having said that, you may have a question. Since NSObject (1) is defined by the metaclass (2) of NSObject, then why is "Dao begets one, life is two"? Instead of "two lives one"?

However, don't forget that there is only one root class NSObject in the entire Objective-C world, and all other classes must inherit from NSObject, so inevitably, the NSObject metaclass is also a subclass of NSObject.
That's why I said: two in one life.

The "Three" of Objective-C

As we mentioned above, NSObject is an instance of the NSObject metaclass. So if your brain is big enough, you should think of a question: What is the metaclass of this NSObject?
According to the highest criterion-everything is an object, then it is also an object. So what is its type? Is it the metaclass of the NSObject metaclass? So whose instance is the metaclass of the NSObject metaclass? Isn't this an infinite loop?

In fact, your concern does not exist, because the type of the NSObject metaclass is itself. In fact, the type of the metaclass of all classes is the NSObject metaclass. Therefore, there is no infinite loop.

In this way, classes and metaclasses form a closed loop: everything is an object, and all objects are defined by types (classes or metaclasses).
You rely on me for the NSObject class and the NSObject metaclass, and I inherit from you, the two are one. Under the interaction of this pair of yin and yang twins, coupled with the basic inheritance system in object-oriented, the Objective-C world always derives countless classes.
This is the "three lives of all things"!

1.2. Operating rules

In 1.1, we only talked about the structure of the entire Objective-C world, but did not explain how it works or how all objects are connected.

Message mechanism

The operation of Objective-C relies on the message mechanism. In fact, this method is closer to the real world than direct calling.
For example, if you come to a small restaurant for dinner at noon, and after eating, you shout: "Waiter, pay the bill!"
Then at this time, "you" sent a message called "Pay the bill" to the "waiter". OC language code:

服务员类 *服务员 = [饭馆 可用的服务员];
[服务员 买单];

But there is a problem here, that is, you don't know whether the "waiter" object can handle the "pay the bill" message. Or in your experience, the "waiters" of other restaurants can "pay the bill", but in fact this restaurant cannot.
The difference here is actually the same as the operating rules of Objective-C. In Objective-C, you can also send a message to the specified object without knowing whether it can respond.

But by extension, this kind of problem will inevitably appear in the real world, because there is no IDE environment in the world to help you do strong type checking, and you can't ask the other party whether they can respond to the message every time you send a message to the object .
But in Objective-C software development, especially business logic development, it is generally not encountered, because the Xcode environment still does basic type verification. But this is a function of the IDE environment, not part of the operating rules of the Objective-C world.
This is the dynamic nature of Objective-C.

So once this happens, how should we deal with it? There are actually several different results:

  1. The waiter ignores you at all. Because the waiter couldn't handle the payment message, he didn't give any response, which is like a flashback of the APP. Of course, the real world will not crash and restart...
  2. The waiter just told you that he can't pay the bill. This is a simple fault-tolerant process. When the waiter encounters a [XX] message that cannot be processed, he will directly tell you "I can't XX". So you can choose other treatment methods.
  3. The waiter will lead you directly to the counter and transfer it to the cashier or boss. This is a pretty good approach that is close to seamless connection. When the waiter receives a "pay" message that she cannot handle, she should determine the type of the message is "pay" and forward it to the cashier or boss who can handle the "pay" message.

Code display

Having said so much, in fact, the above just talked about the basic rules of the operation of the Objective-C world-the message mechanism with dynamic characteristics.
So how does this message mechanism work in the entire world structure?

Before that, we need to understand the way the code is displayed in the Objective-C world.
Let’s take a look at "one" first: NSObject

@interface NSObject <NSObject> {
    
    
    Class isa  OBJC_ISA_AVAILABILITY;
}

+ (void)load;
+ (void)initialize;
.
.
.
@end

We can see that there is a Class isa OBJC_ISA_AVAILABILITY;variable, so what is this Class type?

typedef struct objc_class *Class;

It can be seen that Class is actually a structure pointer, and the structure is defined as follows:

struct objc_class {
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif

} OBJC2_UNAVAILABLE;

Among them, *methodLists stores the instance method list of the current class, which represents the list of all messages that the instance of the current class can respond to, and the function pointer address that is executed when responding to the message.
The data structure is similar to the following table:

Selector IMP
SelectorA IMPA
SelectorB IMPB
SelectorC IMPC
Selector… IMP…

Among them, Selector is the number variable of the method, and IMP is the corresponding implementation function pointer.

Now let's talk about the overall process through the example of paying the bill:
When sending a pay message to the waiter:
1. First look for the cache method list (cache methodLists) of the waiter class according to @Selector (paying);
2. Found it, Get the corresponding IMP value, and then call and execute the function;
3. If it is not found, it will be searched from the method Lists of the server class;
4. If it is found, the corresponding IMP will be executed; if it is not found, it will go to super class Search in the list of methods until you find the base class (NSObject);
5. Finally, if you don’t find it, you will enter the dynamic method analysis and message forwarding mechanism. (This part of the knowledge will be discussed in detail later) For
Write picture description here
example, the waiter class:

Selector IMP
A la carte IMP a la carte
Take cutlery IMP take cutlery
Take a napkin IMP take napkin
IMP…

If it is not found, the message is forwarded to the parent class; for
example, the parent class of the waiter class is human:

Selector IMP
eat IMP eat
go to bed IMP sleep
Beaten beans IMP Beans
wear clothes IMP wears clothes
Undress IMP undress
IMP…

Here is a question. If it is a class method, where is it stored?
Yes, class methods are stored in the metaclass method list of the class.
For example, the server class has such a class method:

服务员类 *服务员 = [服务员类 穿着女仆装的服务员];

So, the metaclass of the waiter class, the method list is

Selector IMP
Waiter in maid outfit IMP waiter in maid outfit
IMP…

2. To know: understand its essence

Through the above grid, we already know the world composition and operating rules of Objective-C, then combined with the above content, what is the essence of our protagonist Method Swizzling?
That's right, it is to transform the IMP corresponding to the Selector in the MethodList!

In the description of the message mechanism in the previous section, after finding the Selector, it is always necessary to get the corresponding IMP and then call to execute the IMP. So as long as we modify the IMP corresponding to a Selector in the MethodList, the modified IMP will be called when the program is running, and the effect we want will be run.

2.1. System functions

Based on this logic, some functions are provided in the objc/runtime.h unit of OC to modify the MethodList of the class.

Implementation of the exchange method:

OBJC_EXPORT void method_exchangeImplementations(Method m1, Method m2) 
     __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

The function of this function is to exchange the implementation function pointers of the two methods.

Examples are as follows:

Method Method吃饭 = class_getInstanceMethod([服务员类 class], @selector*(吃饭));
Method Method脱衣服 = class_getInstanceMethod([服务员类 class], @selector*(脱衣服));
method_exchangeImplementations(Method吃饭, Method脱衣服);

After execution, the MethodList of the parent class of the server class is as follows:

Selector IMP
eat IMP undress
go to bed IMP sleep
Beaten beans IMP Beans
wear clothes IMP wears clothes
Undress IMP eat
IMP…

Then when the following code is executed...

服务员类 *服务员 = [服务员类 穿着女仆装的服务员];
[服务员 吃饭];

New method:

/** 
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                                 const char *types) 
     __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

The function of this function is to add a new method to cls. If this method exists in the MethodList of cls (excluding the MethodList of the parent class), the addition fails and returns NO. If there is a SEL method in the parent class of cls, then after executing this function, a method with the same name will be added to the MethodList of cls to override the implementation of the parent class.

Function implementation of setting method:

OBJC_EXPORT IMP method_setImplementation(Method m, IMP imp) 
     __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

The function of this function is: the new realization function pointer of the setting method, and the return value is the old realization function pointer.

Replacement method:

OBJC_EXPORT IMP class_replaceMethod(Class cls, SEL name, IMP imp, 
                                    const char *types) 
     __OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

The function of this function is: if this method exists in the MethodList of cls (excluding the MethodList of the parent class), call method_setImplementation; otherwise, call class_addMethod;

3. Application: Combining theory with practice

Speaking of this, we have a basic and complete understanding of MethodSwizzling technology and its implementation principles, and we can be regarded as completing the learning process of learning things.
But no matter what technology, learning is ultimately for application, that is, to apply what you have learned, and Method Swizzling is no exception. So next we have to think about why we should use Method Swizzling, and how to use this technology in the end.

why

If you want to explain Method Swizzling in one sentence, it is: modify the method implementation in a class at runtime.
So the question is, if you want to modify the method implementation, inheritance, category and other technologies can also be implemented, why use Method Swizzling?
-Avoid multiple inheritance;
for example, in the project, the framework will provide a VC base class, and then the project references the framework after it will be known as a VC base class on this basis, a type of business module may be in this base class A base class is abstracted on the basis of.
In fact, a lot of code logic in these inheritance chains is not business-related. If you use MethodSwizzling technology, you can replace multiple inheritance systems.
-Fix system bugs;
when the class provided by the system has a bug, you cannot directly modify the code of the system class; if you define a subclass, it will cause a lot of code modification; if you use the category, you cannot call the original function implementation of the method.
At this time, the method transformation technology can be used to replace the method implementation of the system class without modifying the business code, and to ensure that the new function implementation will call the original method implementation.
-Avoid modifying a large amount of code; for
example, use Youmeng to count the number of entries and exits on the page. Under normal circumstances, you need to add statistical codes in the viewWillAppear: and viewWillDisappear: methods of each VC. In this way, even if you can directly modify the code of the class, the workload of modification is relatively large.
If you use method transformation technology, you can replace the method implementation of UIViewController and use the Class name to perform page statistics.
-Add debugging logs;
this scene is more violent. If you want to add start and end logs to the method, it must be very troublesome to change the method one by one.
-Other AOP scenarios (permissions, caching, performance statistics...);
In fact, scenarios such as Umeng statistics and debugging logs belong to the category of problems that AOP needs to solve. These problems are universal and have nothing to do with business logic. Therefore, all the problems to be solved in AOP programming basically need to be completed through method transformation in Objective-C.

what

Now we need to think about a question: If we need to use method transformation technology, what does this technology look like from the user's point of view?
-The fundamental purpose: to replace the method implementation in a class;
-Compatibility: whether the original implementation needs to be called after the replacement ; -Sequence: the calling
sequence of the new code and the original method implementation; call the original method implementation at the beginning, middle or last ;
-Diversity: Need to consider subclass instance method replacement, subclass instance method addition, and instance method replacement inherited from the parent class; subclass method replacement, subclass method addition, and class method replacement inherited from the parent class ;
-Scope of influence: When a subclass replaces a method inherited from a parent class, will it affect the functions of other classes inherited from this parent class; how to ensure the final replacement effect when multiple classes on the same inheritance chain are replaced at the same time when the method is implemented ;

how

Before we think about how to change the technology according to the method to meet the user's requirements, we must consider the shortcomings of this technology. The so-called "won't consider victory, consider defeat first", so that we can remain invincible.
See reference 1 for specific issues.
However, there is a problem that was not raised in the original text, namely:
the order of multiple method transformations for the same method of the same class.
This question is actually very important. As the original text says, we store new method implementations in the declaration category, and perform method transformations in the +load; method of this category.
Then considering the situation of the framework, there may be multiple method transformation categories (a category is declared in the framework, and another category is declared in the project); at this time, the execution order of the two categories is in the order of compilation We cannot control and ensure the order of execution.

In the case of AOP programming, each aspect is independent of each other, and there is no call order requirement. Therefore, the impact of this problem is not significant.
But for the framework, this problem is bigger. For example, in the category of the frame, the background color of the view is set to white, and in the category of the item, the background color of the view is set to gray. Then different loading order will lead to different execution order, and the final effect will be different.
To solve this problem, it is not possible +load;to perform implicit method transformation in the method of the category , but to make an explicit call when the APP is started.

Based on the user's requirements, there can be two different implementation ideas (encapsulation to achieve the purpose of direct use):
-Class hook method;
hook, hook, which means that custom code is hooked to the specified method and executed at the same time as the specified method;
advantages :Users are convenient to use;
Disadvantages: not flexible enough; A lot of coding is needed to realize the scheme;

  • Class inheritance method;
    inheritance, that is, the original implementation method is overwritten by default, but the original implementation of the method can be [super viewDidLoad];called through, for example , explicit code calls;
    advantages: flexible, can control whether to execute the original implementation in its own logic; less coding workload;
    Disadvantages: users need to write more code;

If you compare it like this, you may not be able to understand the difference. Let's experience the difference with interface statistics and permission control scenarios.
In the scene of interface statistics, our requirement is actually very simple: call the corresponding statistics code according to the Class before calling the viewWillAppear: method each time. This scenario must be implemented by calling the original method. Therefore, the user experience of using the hook scheme will be better.
In the permission control scenario, we require the user to verify whether the current user is logged in when clicking the function button, if not logged in, the login interface will pop up, and the original method will be called when logged in. Then the hook scheme is actually unable to meet the requirements of the scene (although the hook scheme is mentioned in reference 1, its implementation is actually similar to the inherited scheme).

Based on time considerations, this time I adopted the class inheritance scheme.
To adopt this scheme, we need to meet the following points:

  1. Support new methods and implementation of replacement methods;
  2. When replacing the method implementation, the original implementation pointer of the method needs to be saved, and this implementation pointer can be explicitly called in the new method implementation;
  3. Need to support instance methods and class methods;

The above three points are controllable by us as a technical solution provider. As for the following four points, users need to pay attention:

  1. If you use the method transformation function on a small scale, you need +load;to perform method transformation in the method of the category ; if you use it on a large scale, you must explicitly call the method transformation class method when the APP is started;
  2. When the method is changed, dispatch_once needs to be used for control and only executed once;
  3. When performing method transformations with multiple inheritance relationships at the same time, start with the parent class first;
  4. When changing the method, pay attention to the use of prefixes in the declaration of the new method to avoid naming conflicts;

Please see DEMO for specific implementation. The final interface provided by NSObject+Swizzle.h is as follows:

/**
 *  通过新的方法名调配当前类的实例方法,支持新增方法
 *
 *  @param originalSelector 需要调配的方法名
 *  @param replacement      新的方法名
 *  @param store            存储原实现IMP的指针
 *
 *  @return 是否成功
 */
+ (BOOL)swizzleInstanceMethod:(SEL)aOriginalSelector withReplacement:(SEL)aReplacementSelector andStore:(IMPPointer)aStorePointer;

/**
 *  通过新的方法名调配当前类的类方法,支持新增方法
 *
 *  @param originalSelector 需要调配的方法名
 *  @param replacement      新的方法名
 *  @param store            存储原实现IMP的指针
 *
 *  @return 是否成功
 */
+ (BOOL)swizzleClassMethod:(SEL)aOriginalSelector withReplacement:(SEL)aReplacementSelector andStore:(IMPPointer)aStorePointer;

Reference

1. Objective-C hook scheme (1): Method Swizzling
2. Objective-C method and related method analysis
3. Deep understanding of Objective-C: Category

Guess you like

Origin blog.csdn.net/jhq1990/article/details/50149255