IOS App code free intrusion method hook detailed introduction

  • 2021-08-28 21:20:57
  • OfStack

iOS App Code-Free Approach hook

Continue the study of Objective-C runtime

Recently, the company project is doing user behavior analysis

Therefore, App needs to send a message to the statistical system when some pages are switched and interoperated

In a few 10 Controller projects, it is completely impossible to add code one by one, and it is also difficult to maintain

But what needs to be dealt with here is Controller, and the above requirements can be realized in the following ways

1. Leveraging object inheritance in Objective-C

Inheritance is very common in object-oriented development, such as the project we are doing now will have an BaseViewController,

All new ViewController inherits BaseViewController and can be called by their subclasses by adding 1 public method\ properties to BaseViewController

This is one of the main ways to unify all the view controller styles in our project

2. Implement method hook using Category and Runtime

The hook scheme has one advantage, that is, it can avoid code intrusion and achieve wider universality. Through swizzling, we can combine the original method with the method we added.

That is, it does not need to add code to the original project, and it can achieve global coverage

Comparison of two schemes:

Compared with hook, it is more accurate to inherit the parent class, because all the pages to be counted are inherited from the controller of this parent class, while other pages such as UINavigationController and UIAlertController of the system will not be strayed into the statistical data

The above mentioned hook scheme is through hook, UIViewController, viewdidload/viewdidappear and other methods, and these methods will actually be called every Controller, so Controller that should not appear will also appear here (such as UINavigationController and UIAlertController mentioned above). However, one better feature of hook scheme is that it has no code intrusion and completes the work without modifying the project code.

Considering that the behavior analysis statistical system may be used in other projects of the company, hook scheme is adopted here. Then there will inevitably be statistics that should not be counted, and then we will make an analysis later.

Since the hook scheme is used, the swizzling of runtime should be used again

First, create a new category of UIViewController

Implement swizzling code


+ (void)load{
  [super load];
  
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{
    //  If you want to open it controller Statistics of  , Open the following line of code 
    __gbh_tracer_swizzleMethod([self class], @selector(viewDidAppear:), @selector(__gbh_tracer_viewDidAppear:));
  });
}

Well, when you see this, you will find that you are calling an C method, but how is this C method implemented? Look below


void __gbh_tracer_swizzleMethod(Class class, SEL originalSelector, SEL swizzledSelector){
  Method originalMethod = class_getInstanceMethod(class, originalSelector);
  Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
  
  BOOL didAddMethod =
  class_addMethod(class,
          originalSelector,
          method_getImplementation(swizzledMethod),
          method_getTypeEncoding(swizzledMethod));
  
  if (didAddMethod) {
    class_replaceMethod(class,
              swizzledSelector,
              method_getImplementation(originalMethod),
              method_getTypeEncoding(originalMethod));
  } else {
    method_exchangeImplementations(originalMethod, swizzledMethod);
  }
}

This is a standard swizzling writing, of course, github above also on the swizzling open source library, it is easy to use here is not said

Looking back at block 1, the red viewDidAppear is the method that I am going to hook, and __gbh_tracer_viewDidAppear is the method that I need to implement


- (void)__gbh_tracer_viewDidAppear:(BOOL)animated{
  [self __gbh_tracer_viewDidAppear:animated]; // Because the method has been swapped , What is called here is actually viewDidAppear: Method 
  
     // Object that does not allow data to be sent Controller
  NSArray *filter = @[@"UINavigationController",@"UITabBarController"];
  NSString *className = NSStringFromClass(self.class);
  if ([filter containsObject:className]) return ; // If the Controller When sending is not allowed log In the list of , You can't continue to go down 
  
  if ([self.title isKindOfClass:[NSString class]] && self.title.length > 0){ // Only those with titles meet my requirements 
    //  Send here log
  }

}

Well, I just said that I don't send data to some Controller. There are two judgments here, one is to add to the blacklist, and the other is to judge whether the title attribute of Controller is empty

The above judgment can basically meet the needs of my behavior analysis and statistics system. If you need any judgment, you can continue to add it

So I just need to add this Category to the project, this viewDidAppear will be hook out, can do whatever you want.

In addition, it is mentioned in the requirements that the init message should be sent once when the application is started

hook? Yes, but I prefer to use category + NSNotification because UIApplicationDidFinishLaunchingNotification is already in the system

This kind of notice can be used directly


@implementation UIApplication (GBHTracer)
+ (void)load{
  [super load];
  
  static dispatch_once_t onceToken;
  dispatch_once(&onceToken, ^{ // Execute only 1 Just once. 
    [[NSNotificationCenter defaultCenter]addObserver:self selector:@selector(__gbh_tracer_applicationDidFinishLaunching:) name:UIApplicationDidFinishLaunchingNotification object:nil];
  });
}

+ (void)__gbh_tracer_applicationDidFinishLaunching:(NSNotification *)noti{
  // Do whatever you want when the application starts !
}

@end

Hmm.. Our behavior analysis statistical system in the original project does not Import1 header file does not call any 1 method can achieve statistical results.

But what kind of operation response time statistics, or you need the reader in the response to call the corresponding method

Thank you for reading, hope to help everyone, thank you for your support to this site!


Related articles: