iOS进阶_Hook Delegate(webView示例+UIScrollView示例)

无意间在iOS开发交流群中看见一位小伙伴儿碰见的问题

这里写图片描述

小伙伴儿的想法是在基类中监听UIScrollView的滑动事件,隐藏和显示方法都写好了,而且他们的业务逻辑是所有界面有滑动的地方就有这种需求,如果一个页面一个页面的去写未免太过于麻烦。所以我的建议是可不可以尝试使用method Swizzling黑魔法,Hook,交换方法。那么问题来了,他需要交换的是UIScrollView的delegate方法,那么又该如何实现呢?

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

尝试hook setDelegate拿到实际调用其代理的类,然后对其进行方法交换。

参考zper的文章(缘分的是zper恰好在同一个iOS交流群中)。

下面是全部的示例代码:

ViewController.h

#import "ViewController.h"

@interface ViewController ()<UIScrollViewDelegate>

@property(nonatomic,strong)UIScrollView * scrollView;
@property(nonatomic,weak)UIImageView *imageView;
@property(nonatomic,strong)UIImage * image;
/**
 问题一.为什么scrollView用strong修饰?imageView用week?
 */
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    //搭建界面
    CGSize size = self.view.frame.size;
    self.scrollView = [[UIScrollView alloc] initWithFrame:CGRectMake(0, 170, size.width, size.height - 80)];
    [self.view addSubview:self.scrollView];
    //MARK:-设置缩放属性
    self.scrollView.delegate = self;
    self.scrollView.minimumZoomScale = 0.5;
    self.scrollView.maximumZoomScale = 2.0;

    //imageView
    UIImageView * iv =[[UIImageView alloc]init];
    //会调用view的getter方法。loadView方法在执行的过程中,如果self.view == nil,会自动调用loadView加载
    [self.view addSubview:iv];
    self.imageView = iv;

    //MARK: - 利用GCD来做下载图片
    dispatch_async(dispatch_get_global_queue(0, 0), ^{
        //异步执行
        NSURL * url =[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1525326853527&di=56bda83da629c944572273eb3793ede9&imgtype=0&src=http%3A%2F%2Fscimg.jb51.net%2Fallimg%2F170209%2F106-1F20916102V08.jpg"];
        NSData * data =[NSData dataWithContentsOfURL:url];
        UIImage * image =[UIImage imageWithData:data];
        //更新UI
        dispatch_async(dispatch_get_main_queue(), ^{
            self.imageView.image = image;
            [self.imageView sizeToFit];
            self.scrollView.contentSize = image.size;
        });
    });
}

//MARK: - 下载图片
-(void)downloadImage{
    NSLog(@"%@",[NSThread currentThread]);  
    //NSUTL -> 统一资源定位符,每一个URL,对应着一个网络资源
    NSURL * url =[NSURL URLWithString:@"https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1525326853527&di=56bda83da629c944572273eb3793ede9&imgtype=0&src=http%3A%2F%2Fscimg.jb51.net%2Fallimg%2F170209%2F106-1F20916102V08.jpg"];

    //下载图片(在网络上传输的所有数据都是二进制)
    //为什么是二进制:因为物理层是网线,网线里面是电流,电流有高低电频,高低电频表示二进制.数字信号转模拟信号转电信号
    NSData * data =[NSData dataWithContentsOfURL:url];
    //在UI线程去更新UI
    /**
     *1.SEL:在主线程执行的方法
     *2.传递给方法的参数
     *3.是否让当前线程等待(注意:如果当前线程是主线程,YES没有用)
     */
    //线程间通信
    [self performSelectorOnMainThread:@selector(setImage:) withObject:[UIImage imageWithData:data] waitUntilDone:NO];
    NSLog(@"come here");
}
-(void)setImage:(UIImage *)image{
    _image =image;
    NSLog(@"更新UI在%@",[NSThread currentThread]);
    //直接将图片设置到控件上
    self.imageView.image = image;
    //让imageView和image一样大
    [self.imageView sizeToFit];
    //指定scrollView的contentSize
    self.scrollView.contentSize=image.size;
}

#pragma mark - <UIScrolView代理>
-(UIView *)viewForZoomingInScrollView:(UIScrollView *)scrollView{
    return self.imageView;
}
-(void)scrollViewDidScroll:(UIScrollView *)scrollView{

    NSLog(@"scrollViewDidScroll");
//    NSLog(@"%@",NSStringFromCGAffineTransform(self.imageView.transform)); 
}

@end

UIScrollViewDelegateHook.h

#import <UIKit/UIKit.h>
@interface UIScrollViewDelegateHook : NSObject <UIScrollViewDelegate>

+ (void)exchangeUIScrollViewDelegateMethod:(Class)aClass;

@end

UIScrollViewDelegateHook.m

#import "UIScrollViewDelegateHook.h"
#import <objc/runtime.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(replacedMethod);
    IMP replacedMethodIMP = method_getImplementation(replacedMethod);

    BOOL didAddMethod =
    class_addMethod(originalClass,
                    replacedSel,
                    replacedMethodIMP,
                    method_getTypeEncoding(replacedMethod));

    if (didAddMethod) {
        NSLog(@"class_addMethod succeed --> (%@)", NSStringFromSelector(replacedSel));
        Method newMethod = class_getInstanceMethod(originalClass, replacedSel);
        method_exchangeImplementations(originalMethod, newMethod);
    }
    else {
            NSLog(@"class_addMethod fail --> (%@)", NSStringFromSelector(replacedSel));
        }
}
//static void hook_exchangeMethod(Class originalClass, SEL originalSel, Class replacedClass, SEL replacedSel)
//{
//    static NSMutableArray *classList = nil;
//    if (classList == nil) {
//        classList = [NSMutableArray array];
//    }
//    NSString *className = [NSString stringWithFormat:@"%@__%@", NSStringFromClass(originalClass), NSStringFromSelector(originalSel)];
//    NSLog(@"%@",className);
//    for (NSString *item in classList) {
//        // 防止 setDelegate 方法被调用多次,导致代理方法又被换掉
//        if ([className isEqualToString:item]) {
//            return;
//        }
//    }
//    [classList addObject:className];
//    Method originalMethod = class_getInstanceMethod(originalClass, originalSel);
//    assert(originalMethod);
//    Method replacedMethod = class_getInstanceMethod(replacedClass, replacedSel);
//    assert(replacedMethod);
//    IMP replacedMethodIMP = method_getImplementation(replacedMethod);
//    BOOL didAddMethod =
//    class_addMethod(originalClass,
//                    replacedSel,
//                    replacedMethodIMP,
//                    method_getTypeEncoding(replacedMethod));
//    if (didAddMethod) {
//        NSLog(@"class_addMethod failed --> (%@)", NSStringFromSelector(replacedSel));
//    } else {
//        NSLog(@"class_addMethod succeed --> (%@)", NSStringFromSelector(replacedSel));
//    }
//    Method newMethod = class_getInstanceMethod(originalClass, replacedSel);
//    method_exchangeImplementations(originalMethod, newMethod);
//}


@implementation UIScrollViewDelegateHook

+ (void)exchangeUIScrollViewDelegateMethod:(Class)aClass{
    // hook它的scrollViewDidScroll代理方法
    hook_exchangeMethod(aClass, @selector(scrollViewDidScroll:), [self class], @selector(replace_scrollViewDidScroll:));
}

- (void)replace_scrollViewDidScroll:(UIScrollView *)scrollView{
    NSLog(@"replace_scrollViewDidScroll");
    [self replace_scrollViewDidScroll:scrollView];
}

@end

UIScrollView+UIScrollView_Hook.h

#import <UIKit/UIKit.h>

@interface UIScrollView (UIScrollView_Hook)

@end

UIScrollView+UIScrollView_Hook.m

#import "UIScrollView+UIScrollView_Hook.h"
#import <objc/runtime.h>
#import "UIScrollViewDelegateHook.h"

@implementation UIScrollView (UIScrollView_Hook)
- (void)hook_setDelegate:(id<UIScrollViewDelegate>)delegate{
    [self hook_setDelegate:delegate];
    // 获得delegate的实际调用类
    Class aClass = [delegate class];
    // 传递给UIScrollViewDelegateHook来交互方法
    [UIScrollViewDelegateHook exchangeUIScrollViewDelegateMethod:aClass];
}

#pragma mark - Method Swizzling
// 在load中交换系统的setDelegate 和我们要hook的代理方法
+ (void)load{
    static dispatch_once_t onceToken;

    dispatch_once(&onceToken, ^{
        //        Class class = [super class];
        SEL originalSelector = @selector(setDelegate:);
        SEL swizzledSelector = @selector(hook_setDelegate:);
        Method originalMethod = class_getInstanceMethod([UIScrollView class], originalSelector);
        Method swizzledMethod = class_getInstanceMethod([UIScrollView 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);
        //        }
    });
}
@end

运行效果:

这里写图片描述

gitHub地址

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值