Key words: runtime runtime objc

HOOK: Commonly known as a HOOK, commonly referred to as a run time method to find a method (HOOK method), and then insert code into the method to implement our own business logic, the most common business scenario is the non-intrusive buried point. In iOS, runtime technology is usually used to implement. Let’s start with three questions.

I. How to implement HOOK?

It’s a very simple problem, and I’m sure you all do, so let’s take the viewDidAppear method of HOOK UIViewController. In order to facilitate the explanation, two concepts are agreed as follows:

HOOK method: for example, viewDidAppear (i.e., sourthod)

HOOK method: such as hook_viewDidAppear (that is, targetMethod)

1. Obtaining methods

Class_getInstanceMethod was used to obtain the sourthod and targetMethod respectively. Class_getInstanceMethod has a feature that if it doesn’t have a method implementation of its own, it will look it up from its parent.

Method sourceMethod  = class_getInstanceMethod(classObject, sourceSelector);
Method targetMethod = class_getInstanceMethod(classObject, targetSelector);
Copy the code

2. Add methods

First try to add method implementation to the method to be hooked. If the addition succeeds, then the method implementation to be hooked needs to replace the method implementation of HOOK.

BOOL isAdd = class_addMethod(classObject, sourceSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
if (isAdd) {
    class_replaceMethod(classObject, targetSelector, method_getImplementation(sourceMethod), method_getTypeEncoding(sourceMethod));
}
Copy the code

3. Exchange methods

If the addition in step 2 fails, it means that the method to be hooked already has a corresponding method implementation. In this case, the implementation of the two methods can be directly exchanged.

method_exchangeImplementations(sourceMethod, targetMethod);
Copy the code

The complete code is as follows:

#import "UIViewController+hook.h"
#import <objc/runtime.h>

@implementation UIViewController (hook)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL sourceSelector = @selector(viewDidAppear:);
        SEL targetSelector = @selector(hook_viewDidAppear:);
        [self hookClass:self sourceSelector:sourceSelector targetSelector:targetSelector];
    });
}

+ (void)hookClass:(Class)classObject sourceSelector:(SEL)sourceSelector targetSelector:(SEL)targetSelector {
    Method sourceMethod  = class_getInstanceMethod(classObject, sourceSelector);
    Method targetMethod = class_getInstanceMethod(classObject, targetSelector);
    BOOL isAdd = class_addMethod(classObject, sourceSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
    if (isAdd) {
        class_replaceMethod(classObject, targetSelector, method_getImplementation(sourceMethod), method_getTypeEncoding(sourceMethod));
    } else{ method_exchangeImplementations(sourceMethod, targetMethod); }} - (void)hook_viewDidAppear:(BOOL)animated {
    NSLog(@"----------hook----------");
    [self hook_viewDidAppear:animated];
}
@end
Copy the code

What happens when two extensions HOOK the same method at the same time?

In particular, is to write two categories hook1, hook2, hooK1, hook2 at the same time HOOK a method, the following HOOK viewDidAppear method as an example.

UIViewController+hook1.m

---------------.m----------------------
#import "UIViewController+hook1.h"
#import <objc/runtime.h>

@implementation UIViewController (hook1)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL sourceSelector = @selector(viewDidAppear:);
        SEL targetSelector = @selector(hook1_viewDidAppear:);
        [self hookClass:self sourceSelector:sourceSelector targetSelector:targetSelector];
    });
}

+ (void)hookClass:(Class)classObject sourceSelector:(SEL)sourceSelector targetSelector:(SEL)targetSelector {
    Method sourceMethod  = class_getInstanceMethod(classObject, sourceSelector);
    Method targetMethod = class_getInstanceMethod(classObject, targetSelector);
    BOOL isAdd = class_addMethod(classObject, sourceSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
    if (isAdd) {
        class_replaceMethod(classObject, targetSelector, method_getImplementation(sourceMethod), method_getTypeEncoding(sourceMethod));
    } else{ method_exchangeImplementations(sourceMethod, targetMethod); }} - (void)hook1_viewDidAppear:(BOOL)animated {
    NSLog(@"----------hook1----------");
    [self hook1_viewDidAppear:animated];
}
@end
Copy the code

UIViewController+hook2.m

---------------.m----------------------
#import "UIViewController+hook2.h"
#import <objc/runtime.h>

@implementation UIViewController (hook2)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL sourceSelector = @selector(viewDidAppear:);
        SEL targetSelector = @selector(hook2_viewDidAppear:);
        [self hookClass:self sourceSelector:sourceSelector targetSelector:targetSelector];
    });
}

+ (void)hookClass:(Class)classObject sourceSelector:(SEL)sourceSelector targetSelector:(SEL)targetSelector {
    Method sourceMethod  = class_getInstanceMethod(classObject, sourceSelector);
    Method targetMethod = class_getInstanceMethod(classObject, targetSelector);
    BOOL isAdd = class_addMethod(classObject, sourceSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
    if (isAdd) {
        class_replaceMethod(classObject, targetSelector, method_getImplementation(sourceMethod), method_getTypeEncoding(sourceMethod));
    } else{ method_exchangeImplementations(sourceMethod, targetMethod); }} - (void)hook2_viewDidAppear:(BOOL)animated {
    NSLog(@"----------hook2----------");
    [self hook2_viewDidAppear:animated];
}
@end
Copy the code

Conclusion:

  • Print the result
// UIViewController+hook1.m first compile 2021-08-07 22:26:17.798096+0800 hook[6663:235978] ----------A 2021-08-07 22:26:17. 804583 + 0800 hook (6663-235978) -- -- -- -- -- -- -- -- -- -- hook2 -- -- -- -- -- -- -- -- -- - the 2021-08-07 22:26:17. 804987 + 0800 hook (6663-235978) ----------hook1----------Copy the code
// UIViewController+hook2.m = 2021-08-07 22:33:46.783382+0800 hook[6733:240232] ----------A 2021-08-07 22:33:46. 788859 + 0800 hook (6733-240232) -- -- -- -- -- -- -- -- -- -- hook1 -- -- -- -- -- -- -- -- -- - the 2021-08-07 22:33:46. 789052 + 0800 hook (6733-240232) ----------hook2----------Copy the code
  • 2 cases, different results, in fact, is the load method call order problem, load method call order is compiled first to call load
  • No matter several categories HOOK the same method at the same time, it will be successful in the end, but the result order of HOOK depends on the order of classification compilation

3. Subclass A inherits from subclass B, B has an implementation of print, HOOK A’s print method?

ViewController

---------------.m----------------------
#import "ViewController.h"
#import "BViewController.h"

@implementation ViewController
- (void)viewDidLoad {
    [super viewDidLoad];
    BViewController *vc = [BViewController new];
    [vc performSelector:@selector(test)];
}
@end
Copy the code

AViewController

---------------.h----------------------
#import <UIKit/UIKit.h>
NS_ASSUME_NONNULL_BEGIN

@interface AViewController : UIViewController
@end

NS_ASSUME_NONNULL_END

---------------.m----------------------
#import "AViewController.h"

@implementation AViewController
- (void)viewDidLoad {
    [super viewDidLoad];
}

- (void)test {
    NSLog(@"----------A");
}
@end
Copy the code

BViewController

---------------.h----------------------
#import "AViewController.h"
NS_ASSUME_NONNULL_BEGIN

@interface BViewController : AViewController
@end

NS_ASSUME_NONNULL_END

---------------.m----------------------
#import "BViewController.h"

@implementation BViewController
- (void)viewDidLoad {
    [super viewDidLoad];
}
@end
Copy the code

BViewController+hook

---------------.m----------------------
#import "BViewController+hook.h"
#import <objc/runtime.h>

@implementation BViewController (hook)
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        SEL sourceSelector = @selector(test);
        SEL targetSelector = @selector(hook_test);
        [self hookClass:self sourceSelector:sourceSelector targetSelector:targetSelector];
    });
}

+ (void)hookClass:(Class)classObject sourceSelector:(SEL)sourceSelector targetSelector:(SEL)targetSelector {
    Method sourceMethod  = class_getInstanceMethod(classObject, sourceSelector);
    Method targetMethod = class_getInstanceMethod(classObject, targetSelector);
    BOOL isAdd = class_addMethod(classObject, sourceSelector, method_getImplementation(targetMethod), method_getTypeEncoding(targetMethod));
    if (isAdd) {
        NSLog(@"----------isAdd----------");
        class_replaceMethod(classObject, targetSelector, method_getImplementation(sourceMethod), method_getTypeEncoding(sourceMethod));
    } else{ method_exchangeImplementations(sourceMethod, targetMethod); }} - (void)hook_test {
    NSLog(@"----------hook_test----------");
    [self hook_test];
}
@end
Copy the code

Conclusion:

  • Print the result
The 2021-08-07 20:56:18. 344068 + 0800 hook (5914-201030) -- -- -- -- -- -- -- -- -- -- isAdd -- -- -- -- -- -- -- -- -- - the 2021-08-07 20:56:18. 521663 + 0800 Hook (5914-201030) -- -- -- -- -- -- -- -- -- -- hook_test -- -- -- -- -- -- -- -- -- - the 2021-08-07 20:56:18. 521819 + 0800 hook (5914-201030) -- -- -- -- -- -- -- -- -- -- ACopy the code
  • BViewController does not implement the test method, but HOOK successfully
  • If a subclass does not implement a method, then class_getInstanceMethod looks for it in its parent class, so it hooks the test method in its parent class