程序员

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

2021-01-07  本文已影响0人  Mr姜饼

前言:

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模式要区分开来)
上一篇 下一篇

猜你喜欢

热点阅读