美文网首页程序员
九:黑魔法Method-Swizzling 方法交换

九:黑魔法Method-Swizzling 方法交换

作者: Mr姜饼 | 来源:发表于2021-01-07 09:18 被阅读0次

    前言:

    Method Swizzling 是什么

    Method Swizzling是objective-c中的黑魔法,算是runtime中的一种实战使用模式,它允许我们动态的替换方法,实现Hook功能。

    但是它也是一把双刃剑,用得好的人可以用它来很轻松的实现一些复杂的功能,而如果用的不好,后果就真的是毁灭性的伤害,这样的黑魔法,我们一定要尽力去掌握并驾驭它。

    Method Swizzling 能做什么

    先从名字来看,Method方法Swizzling混合,那他的意思就是方法混合??? 好像也没有一个准确的翻译,我们就姑且翻译成方法交换吧。

    也就是说把原来 A方法实现的a,原来B方法实现的b交换一下,让A来实现b的功能,让B来实现a的功能。咋一看好像没什么厉害的地方,不就是交换个方法么,有什么用呢?您先别急,往下看。

    Method Swizzling 原理

    在Method方法中,有两个关键的成员变量:SEL和IMP。

    IMP是一个函数指针,指向的是方法的实现。

    原则上,方法名SEL和IMP是一一对应的,那Method Swizzling的本质就是改变他们的对应关系,达到交换方法实现的目的。

    交换方法的坑点和注意事项

    坑点:
    本类Cls中并没有实现要交换的方法,但是父类中存在该方法

    类A(run) -> 继承 类B(eat)
    当A去实现交换 run 和 eat 的时候,并不会出现问题 ; 一旦B去实现自己原本有的 eat的时候,这时候就大概率出现问题, !!!!!

    例子说明:

    //
    //  Test3ViewController.m
    //  TestDemo
    //
    //  Created by jiangbin on 2021/1/6.
    //  Copyright © 2021 ice. All rights reserved.
    //
    
    #import "Test3ViewController.h"
    #import <objc/runtime.h>
    
    @interface B : NSObject
    
    @end
    
    @implementation B
    
    - (void)eat{
        NSLog(@"%@ eat ----",self);
    }
    
    
    @end
    
    
    @interface A : B
    @property (nonatomic , copy)NSString* name;
    @end
    
    
    @implementation A
    
    + (void)load{
        static dispatch_once_t onceToken;
        dispatch_once(&onceToken, ^{
            Method oriMethod = class_getInstanceMethod(self, @selector(run));
            Method swiMethod = class_getInstanceMethod(self, @selector(eat));
            method_exchangeImplementations(oriMethod, swiMethod);
        });
    
    }
    
    - (void)run{
        NSLog(@ " %@ run ----",self);
        NSLog(@"%@",self.name);
    }
    
    
    @end
    
    @interface Test3ViewController ()
    
    
    @end
    
    @implementation Test3ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        
        A* a = [A new];
        [a eat];
        
        B* b = [B new];
        [b eat];
        // Do any additional setup after loading the view.
    }
    
    /*
    #pragma mark - Navigation
    
    // In a storyboard-based application, you will often want to do a little preparation before navigation
    - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender {
        // Get the new view controller using [segue destinationViewController].
        // Pass the selected object to the new view controller.
    }
    */
    
    @end
    
    

    运行结果

    libc++abi.dylib: terminating with uncaught exception of type NSException
    *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[B name]: unrecognized selector sent to instance 0x600002fe82c0'
    terminating with uncaught exception of type NSException
    

    原因分析:
    A类将自己的方法run 和 父类的方法eat 进行了调换,A调用eat的时候自然会走自己原有的run方法,此时是没有错误的,但是B调用自己子类的run方法,一旦该方法中出现父类不曾有的属性或者方法时,程序就会出现异常崩溃

    补救措施:

    {
        
        if (!cls) NSLog(@"传入的交换类不能为空");
        
        Method oriMethod = class_getInstanceMethod(cls, oriSEL);
        Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
       
        // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
        // 交换自己没有实现的方法:
        //   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
        //   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL
    
        BOOL success = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(oriMethod));
    
        if (success) {// 自己没有 - 交换 - 没有父类进行处理 (重写一个)
            class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
        }else{ // 自己有
            method_exchangeImplementations(oriMethod, swiMethod);
        }
    }
    

    method-swizzling的应用:

    method-swizzling最常用的应用是防止数组、字典等越界崩溃

    在iOS中NSNumber、NSArray、NSDictionary等这些类都是类簇,一个NSArray的实现可能由多个类组成。所以如果想对NSArray进行Swizzling,必须获取到其“真身”进行Swizzling,直接对NSArray进行操作是无效的。

    image.png

    防止数组越界:

    
    + (void)load{
        Method fromMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(objectAtIndex:));
        Method toMethod = class_getInstanceMethod(objc_getClass("__NSArrayI"), @selector(custom_objectAtIndex:));
        method_exchangeImplementations(fromMethod, toMethod);
    }
    ...做自己的处理(debug和release模式要区分开来)
    

    相关文章

      网友评论

        本文标题:九:黑魔法Method-Swizzling 方法交换

        本文链接:https://www.haomeiwen.com/subject/tmznoktx.html