iOS Runntime dynamically adds class methods and calls class_addMethod

  • 2020-10-23 20:18:22
  • OfStack

After getting started to develop iOS 1 for some time, I found that I could not only focus on completing the requirements, but also study other development skills in my spare time, so as to improve my level in limited time. Of course, the "other development techniques" proposition feels irrelevant in any one development area, and for me, experimenting with objc/runtime is the first step to getting started with iOS development.

As soon as I get to know runtime, I will start with the simple api. Today, I will list and organize the related points of class_addMethod:

Start with the document.


/** 
* Adds a new method to a class with a given name and implementation.
* 
* @param cls The class to which to add a method.
* @param name A selector that specifies the name of the method being added.
* @param imp A function which is the implementation of the new method. The function must take at least two arguments - self and _cmd.
* @param types An array of characters that describe the types of the arguments to the method. 
* 
* @return YES if the method was added successfully, otherwise NO 
* (for example, the class already contains a method implementation with that name).
*
* @note class_addMethod will add an override of a superclass's implementation, 
* but will not replace an existing implementation in this class. 
* To change an existing implementation, use method_setImplementation.
*/
OBJC_EXPORT BOOL class_addMethod(Class cls, SEL name, IMP imp, 
const char *types) 
__OSX_AVAILABLE_STARTING(__MAC_10_5, __IPHONE_2_0);

This method adds a new method to the class and its implementation. Analyze 1 parameters required by this method:


Class cls

The cls parameter represents the class in which the new method needs to be added.


SEL name

The name parameter represents the method name of selector, and you can name it as you like.


IMP imp

imp, or implementation, represents a pointer generated by the compiler to an implementation method. In other words, the method that this pointer points to is the method that we're going to add.


const char *types

The last parameter *types represents the return value and parameter of the method we are adding.

After a brief introduction to the parameters and functions required in class_addMethod, we are ready to take advantage of this method to add the methods we need! Before use, we must first clear Objective - C as one kind of dynamic language, it will be part of the code placed in the process of execution at run time, rather than at compile time, so when executing code, not only need is a compiler, also need a runtime environment (Runtime), in order to meet the demand of some 1, apple open source Runtime Source and provides open api for developers to use.

Second, we need to know when we need to call the method class_addMethod. In the project, I need to inherit from a certain class (subclass), but the parent class does not provide the call method I need, and I do not know the specific implementation of some methods in the parent class. Or, I need to write a class (category) for this class, in which I may need to replace/add a method (note: overriding a method in a class is not recommended, and so-called superclass methods cannot be obtained through super). In these two cases, we can achieve the desired effect with class_addMethod.

Okay, so with all that said how do we actually call it? If you don't know how to use it, the best way is to read the instructions. In the documentation provided by Apple, there are detailed usage methods (ES65en-ES66en Runtime ES69en-ES70en Method Resolution). The following is the detailed usage rules with the class myCar:

First of all, since we want to add our methods to a class, we should inherit or write a class classification. Here I create a new class named "myCar" as the class classification of "Car".


#import "Car+myCar.h"
@implementation Car (myCar)
@end

We know that in Objective - C, normal method call is through the message mechanism (message), so if the class is not found in the messages sent method, the system will enter to find the method of processing process, if in this process, we need to join our new method, can realize the dynamic added in the process of running. This process, or mechanism, is Message Forwarding of ES86en-ES87en

There are two main methods involved in this mechanism:


+ (BOOL)resolveInstanceMethod:(SEL)sel
+ (BOOL)resolveClassMethod:(SEL)sel

The only difference between the two methods is whether static or instance methods need to be added. Let's take the former for example. Since we're adding a method, we'll implement it in the "myCar" class as follows:


#import "Car+myCar.h"
void startEngine(id self, SEL _cmd) {
NSLog(@"my car starts the engine");
}
@implementation Car (myCar)
@end

At this point, we have implemented the method startEngine that we want to add. This is an C function that contains at least two arguments, self and _cmd (self represents the function itself, while _cmd is an SEL data body containing the specific method address). What if you want to add parameters to this method? See the following code:


#import "Car+myCar.h"
void startEngine(id self, SEL _cmd, NSString *brand) {
NSLog(@"my %@ car starts the engine", brand);
}
@implementation Car (myCar)
@end

Just add the required parameters and type after the two required parameters, and the same goes for the return value. Just change void before the method name to the desired return type. We don't need to return the value here.

Next, we reload resolveInstanceMethod: the function:


#import "Car+myCar.h"
#import <objc/runtime.h>
void startEngine(id self, SEL _cmd, NSString *brand) {
NSLog(@"my %@ car starts the engine", brand);
}
@implementation Car (myCar)
+ (BOOL)resolveInstanceMethod:(SEL)sel {
if (sel == @selector(drive)) {
class_addMethod([self class], sel, (IMP)startEngine, "v@:@");
return YES;
}
return [super resolveInstanceMethod:sel];
}
@end

In explanation 1, this function executes in the runtime environment if no implementation of this method is found. Line 1 determines if the SEL name passed in matches, and then calls the class_addMethod method, passing in the appropriate parameters. The third parameter is passed in the implementation of the C language function we added, that is, the name of the third parameter must be the same as the name of the added specific function 1. The fourth argument refers to the return value of the function and the contents of the arguments.

As for the return value of this class method, in my tests, whatever the value of BOOL was, it did not affect our execution target, and 1 would simply return YES.

If you feel uncomfortable writing a new function in the C language style, you can rewrite it as follows:


Class cls
0

class_getMethodImplementation means to get a pointer to the specific implementation of SEL.

Then create a new class "DynamicSelector" in which we implement the dynamic addition of methods to "Car".


Class cls
1

Note that [self method:] cannot be used here, because the added method is only executed at run time, and the compiler is only responsible for the method retrieval at compile time. Once an object is not retrieved by its drive method, an error will be reported, so we use performSelector:withObject: to call, save, and run.


Class cls
2

The result of printing meets the goal we want to achieve. If a value needs to be returned, the method is similar.


Related articles: