SEL, Method, IMP

First, let’s look at the objc_Method structure

runtime.h /// An opaque type that represents a method in a class definition. Typedeft struct objc_method *Method; typedef struct objc_method *Method struct objc_method { SEL method_name OBJC2_UNAVAILABLE; char *method_types OBJC2_UNAVAILABLE; IMP method_imp OBJC2_UNAVAILABLE; }Copy the code

Let’s look at the contents of the objc_method structure:

SEL method_name Method name char *method_types method type IMP Method_IMP method implementation

1, SEL

Objc.h /// An opaque type that represents a method selector. Typedef struct objc_selector *SEL;Copy the code

So I’m going to show you the relationship between selector and SEL, which is an instance of SEL, which is a code name for a method, so you can find a method quickly.

Because different classes can have methods with the same name, you can quickly locate a function using Class + selector to get the function pointer IMP.

In iOS, the Runtime will map all of the Method hashes into a set using the load function at runtime. So when you run it, you’re going to find the selector very quickly, and you’re not going to sacrifice too much performance because of the Runtime feature.

And one of the drawbacks of this is that when we’re writing C code, we often use function overloading, where the function has the same name and different arguments, but that doesn’t work in Objective-C, because the selector only remembers the name of the method, it doesn’t have any arguments, so you can’t tell one method from another.

2, IMP

/// A pointer to the function of a method implementation. Typedef id (*IMP)(id, SEL,...) ; #endifCopy the code

Simply put, it is a function pointer that is used to find specific implementations to use.

3, method_types

Const char *types — Encoding of function types (including return value types, parameter types). IOS provides an @encode directive that can represent specific types as string encoding, i.e. representing types by strings. The main purpose is to facilitate the runtime by describing and storing function return values and parameter types in strings.

       NSLog(@"%s",@encode(int));
       NSLog(@"%s",@encode(float));
       NSLog(@"%s",@encode(int *));
       NSLog(@"%s",@encode(id));
       NSLog(@"%s",@encode(void));
       NSLog(@"%s",@encode(SEL));
       NSLog(@"%s",@encode(float *));

Copy the code

The output

2022-01-04 17:59:45.269504+0800 Runtime[71731.1839447] I 2022-01-04 17:59:45.269606+0800 Runtime[71731.1839447] f 2022-01-04 17:59:45.269692+0800 Runtime[71731.1839447] ^ I 2022-01-04 17:59:45.269775+0800 Runtime[71731.1839447] @ 2022-01-04 17:59:45.269853+0800 Runtime[71731.1839447] V 2022-01-04 17:59:45.269934+0800 Runtime[71731.1839447] : The 2022-01-04 17:59:45. 270033 + 0800 Runtime ^ f [71731-1839447]Copy the code

– (int)test:(int)age height:(float)height – (int)test:(int)age height:(float)height i-@-:-i-f: i24@0: 8i16F20:8i16F20:8i16F20 They are used to describe the length and position of the parameters of a function, from left to right:

  • I — The function returns a value of type int
  • 24 — Total length of arguments (24 bytes)
  • @ — the first parameter id
  • 0 — the initial memory offset of the first argument (0 bytes, starting from the 0th byte)
  • The second argument SEL
  • 8 — the initial memory offset of the second parameter (8 bytes, starting from the 8th byte, so the id parameter above takes up the previous 8 bytes)
  • I — the third argument int
  • 16 — the start offset in memory of the third argument (16 bytes, from the 16th byte, so the SEL argument above takes up the previous 8 bytes)
  • F — the fourth argument float
  • 20 — the initial memory offset of the fourth argument (20 bytes, starting from the 20th byte, so the int argument above takes up the first 4 bytes, and the total length is 24, so the last 4 bytes are for float)

Second, code injection

1. Method exchange

** New methods are the same Class as methods that need to be injected by code

Example: Code injection into ViewController’s viewDidLoad method

New ViewController class: ViewController+hook

#import "ViewController+hook.h" #import <objc/runtime.h> @implementation ViewController (hook) + (void)load { OrigMethod = class_getInstanceMethod(self.class, @selector(viewDidLoad)); HookMethod = class_getInstanceMethod(self.class, @selector(hook_viewDidLoad)); //>>> methods swap method_exchangeImplementations(origMethod, hookMethod); } - (void)hook_viewDidLoad {NSLog(@" insert code before calling viewDidLoad "); [self hook_viewDidLoad]; NSLog(@" inject code after calling viewDidLoad "); } @endCopy the code

So, when I go to viewDidLoad I’m going to go to hook_viewDidLoad, and then when I call hook_viewDidLoad because of method swapping I’m going to call hook_viewDidLoad I’m going to call the original method, and I’m going to do code injection.

2. Add methods and replace implementations

** Application scenarios: ** New methods and methods that need to be injected by code are different classes.

Why can’t I use method interchange? If the internal implementation of the old method uses a Class variable, the variable will not be found because the caller is a new Class –> self is already a new Class.

< span style = “max-width: 100%; clear: both; min-height: 1em;

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface ClassA : NSObject

- (void)test1;

- (void)test2;

+ (void)testClass;

@end

NS_ASSUME_NONNULL_END

Copy the code
#import "ClassA.h"

@interface ClassA()

@end

@implementation ClassA

- (void)test1{
    NSLog(@"A Test1");
}

- (void)test2{
    NSLog(@"A Test2");
}

+ (void)testClass {
    NSLog(@"A TestClass");
}

@end

Copy the code

2.1 Example method code injection

Create ClassB, load method in ClassA add method, replace the implementation

+ (void)load {///>>> > Class = NSClassFromString(@"ClassA"); / / / > > > need to code injection instance methods of the NSArray * arr = @ [@ "test1" @ "test2"); For (NSString *st in arr) {///>>> get the original Method origMethod = class_getInstanceMethod(origClass, NSSelectorFromString(st)); //>>> Generate a new Selector NSString *newName = [NSString stringWithFormat:@"%@_add",st]; SEL newSelector = NSSelectorFromString(newName); Class_addMethod (origClass,newSelector, method_getImplementation(origMethod), method_getTypeEncoding(origMethod)); HookIMP = [[self new] methodForSelector:@selector(test)]; method_setImplementation(origMethod, hookIMP); } } - (void)test { NSString * selName = NSStringFromSelector(_cmd); NSLog(@"%@ before code injection ",selName); NSString *new = [NSString stringWithFormat:@"%@_add",selName]; [self performSelector:NSSelectorFromString(new)]; NSLog(@"%@ code injection after execution ",selName); }Copy the code

Execution code:

    ClassA *a = [ClassA new];
    [a test1];
    [a test2];

Copy the code

The log is as follows:

2.2 Class method code injection

OrigClassMethod = class_getClassMethod(origClass, NSSelectorFromString(@"testClass")); class_addMethod(object_getClass(origClass),NSSelectorFromString(@"testClass_add"), method_getImplementation(origClassMethod), method_getTypeEncoding(origClassMethod)); SEL mClassSel = @selector(testClass); IMP hookClassIMP = [self methodForSelector:mClassSel]; method_setImplementation(origClassMethod, hookClassIMP);Copy the code

The important thing to note here is that because it is a class method, the method exists in the list of methods in the metaclass, so you need to add the method to the metaclass

object_getClass(origClass)

Copy the code

At this point, the class method calling ClassA will be called to the testClass of ClassB to implement code injection of the class method

+ (void)testClass {NSLog(@" code injected before testClass execution "); [self performSelector:NSSelectorFromString(@"testClass_add")]; NSLog(@" code injection after testClass execution "); }Copy the code
  • 1.BAT and other major manufacturers iOS interview questions + answers

  • 2.Must-read books for Advanced Development in iOS (Classics must Read)

  • 3.IOS Development Advanced Interview “resume creation” guide

  • (4)IOS Interview Process to the basics