zoukankan      html  css  js  c++  java
  • iOS hook delegate (一)

    利用method Swizzling黑魔法可以轻松的 hook 系统的已知类的方法, 但是对于系统的delegate方法, 其实际调用类存在多种, 如何针对未知调用类的 hook 呢?

    我们结合webView进行探讨, 并且假定调用webView的类已经实现了其所有的代理方法, 在不改变原类调用的请求下, 如何 hook 它呢?

    首先,如果我们设置webView的代理方法, 一定会调用:

    webView.delegate = xxx;

    这样一定会调用webView的setDelegate方法, 因此我们想到可不可以hook webView的setDelegate拿到实际调用其代理的类, 然后对其进行方法交换.

    - (void)hook_setDelegate:(id<UIWebViewDelegate>)delegate{
        [self hook_setDelegate:delegate];
        // 获得delegate的实际调用类
        Class aClass = [delegate class];
        // 传递给HookWebViewDelegateMonitor来交互方法
        [HookWebViewDelegateMonitor exchangeUIWebViewDelegateMethod:aClass];
    }
    // 在load中交换系统的setDelegate 和我们要hook的代理方法
    + (void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method originalMethod = class_getInstanceMethod([UIWebView class], @selector(setDelegate:));
            Method swizzledMethod = class_getInstanceMethod([UIWebView class], @selector(hook_setDelegate:));
            method_exchangeImplementations(originalMethod, swizzledMethod);
        });
    }

    首先我们新建一个类(HookWebViewDelegateMonitor), 用于交互方法并实现:

    #import "HookWebViewDelegateMonitor.h"
    #import <objc/runtime.h>
    #import <objc/message.h>
    
    static void hook_exchangeMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel){
        // 原方法
        Method originalMethod = class_getInstanceMethod(originalClass, originalSel);
    //    assert(originalMethod);
        // 替换方法
        Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);
    //    assert(originalMethod);
        IMP replacedMethodIMP = method_getImplementation(replacedMethod);
        // 向实现delegate的类中添加新的方法
        BOOL didAddMethod = class_addMethod(originalClass, replacedSel, replacedMethodIMP, "v@:@@");
        if (didAddMethod) { // 添加成功
            NSLog(@"class_addMethod_success --> (%@)", NSStringFromSelector(replacedSel));
        }
        // 重新拿到添加被添加的method,这部是关键(注意这里originalClass, 不replacedClass), 因为替换的方法已经添加到原类中了, 应该交换原类中的两个方法
        Method newMethod = class_getInstanceMethod(originalClass, replacedSel);
        // 实现交换
        method_exchangeImplementations(originalMethod, newMethod);
        }
    
    @implementation HookWebViewDelegateMonitor
    
    + (void)exchangeUIWebViewDelegateMethod:(Class)aClass{
        // 分别hook它的所有代理方法 
        hook_exchangeMethod(aClass, @selector(webViewDidStartLoad:), [self class], @selector(replace_webViewDidStartLoad:));
        hook_exchangeMethod(aClass, @selector(webViewDidFinishLoad:), [self class], @selector(replace_webViewDidFinishLoad:));
        hook_exchangeMethod(aClass, @selector(webView:didFailLoadWithError:), [self class], @selector(replace_webView:didFailLoadWithError:));
        hook_exchangeMethod(aClass, @selector(webView:shouldStartLoadWithRequest:navigationType:), [self class], @selector(replace_webView:shouldStartLoadWithRequest:navigationType:));
    }
    // 交换后的具体方法实现
    - (BOOL)replace_webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
    {
        NSLog(@"replaced_webView-shouldStartLoadWithRequest");
        return [self replace_webView:webView shouldStartLoadWithRequest:request navigationType:navigationType];
    }
    
    - (void)replace_webViewDidStartLoad:(UIWebView *)webView
    {
        NSLog(@"replaced_webViewDidStartLoad");
        [self replace_webViewDidStartLoad:webView];
    }
    
    - (void)replace_webViewDidFinishLoad:(UIWebView *)webView
    {
        NSLog(@"replaced_webViewDidFinishLoad");
        [self replace_webViewDidFinishLoad:webView];
    }
    
    - (void)replace_webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error{
        NSLog(@"replaced_webView-didFailLoadWithError");
        [self replace_webView:webView didFailLoadWithError:error];
    }

    当我们在ViewController中实现了WebView的调用打印日志如下:
    这里写图片描述
    这个方法还有一个弊端就是只能hook 原类中已经调用了的方法 , 如果原类中没有实现shouldStartLoadWithRequest这个方法的话, replace_shouldStartLoadWithRequest这个方法也不会调用.
    这里写图片描述

    本人还在进一步的探索中, 如有问题, 欢迎指正, 非常感谢. 如果大佬们有更好的hook方案, 也希望可以告知小弟.

    参考资料:
    通过Method Swizzling实现Hook中的深坑
    小白笔记(“动态”hook某class的delegate方法”)
    Objective-C的hook方案(一): Method Swizzling
    利用Objective-C运行时hook函数的三种方法

  • 相关阅读:
    NSIndexPath 延伸
    iOS进阶推荐的书目
    配置App真机测试证书的流程 一览
    NSAttributedString 的21种属性 详解
    KVO & 通知 小记
    贝塞尔曲线 & CAShapeLayer & Stroke 动画 浅谈
    提升开发人员修为的探讨
    热门IOS 第三方库
    drawRect & 内存 -> 深究
    CALayer & UIView 关系浅析
  • 原文地址:https://www.cnblogs.com/xiaocai-ios/p/7779747.html
Copyright © 2011-2022 走看看