【iOS】—— 方法交换

方法交换

1. 方法交换的原理

每个继承于NSObject的类都可以获得runtime的支持,在这样一个类中,有一个isa指针,指针指向该类定义的结构体,这个结构体中包括,指向其父类定义的指针以及Dispatch tableDispatch table是一张SELIMP的对应表。

也就是说需要通过方法编号SEL最后还有通过Dispatch table寻找对应的IMPIMP是函数指针,然后执行这个方法。

方法编号SEL和实现方法IMP的对应关系:

方法交换后的对应关系:

oriSEL的方法实现变成了swiIMP
swiSEL的方法实现变成了oriIMP

也就是调用oriSEL方法,最终方法实现是swiIMP

方法交换的方式:

objective-c 复制代码
  // 类中获取oriSEL对应的方法实现
  Method oriMethod = class_getInstanceMethod(cls, oriSEL);
  // 获取swiSEL对应的方法实现
  Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
  // 将两个方法实现进行交换,
  method_exchangeImplementations(oriMethod, swiMethod);

再进行方法交换操作,建议在单例模式下进行,避免重复调用交换。

2. 方法交换案例

1)递归问题分析

创建一个LGStudent类,类中有两个方法lg_studentInstanceMethodstudentInstanceMethod,在load方法对这两个方法进行交换,同时lg_studentInstanceMethod的实现中再次调用lg_studentInstanceMethod方法。

objective-c 复制代码
#import "ViewController.h"
#import "LGStudent.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    LGStudent *s = [[LGStudent alloc] init];
    [s studentInstanceMethod];
    
}
@end
  
#import "LGStudent.h"
#import <objc/runtime.h>

@implementation LGStudent
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"方法交换---:%s", __func__);
        Method oriIMP = class_getInstanceMethod(self, @selector(studentInstanceMethod));
        Method swiIMP = class_getInstanceMethod(self, @selector(lg_studentInstanceMethod));
        method_exchangeImplementations(oriIMP, swiIMP);
    });
}

- (void)lg_studentInstanceMethod {
    [self lg_studentInstanceMethod];
    NSLog(@"LGStudent对象方法:%s", __func__);
}

- (void)studentInstanceMethod {
    NSLog(@"LGStudent对类方法:%s", __func__);
}

@end

lg_studentInstanceMethod中再次调用该方法,是否会引起递归调用?

运行结果:

**分析:**没有引起递归,因为进行了方法交换,所以调用对象方法studentInstanceMethod,会找到lg_studentInstanceMethod的方法实现,而lg_studentInstanceMethod中调用lg_studentInstanceMethod,而此时该方法的指向已经指向studentInstanceMethod。如下图所示:

2)交换父类方法

创建一个LGSstudent类,类中有一个实例方法,lg_studentInstanceMethod,其父类LGPerson中有一个实例方法personInstanceMethod,在LGStudent类的load方法中对进行方法交换,将lg_studentInstanceMethod方法交换成父类中的personInstanceMethod方法。

objective-c 复制代码
#import "ViewController.h"
#import "LGStudent.h"
#import "LGPerson.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    LGPerson *p = [[LGPerson alloc] init];
    [p personInstanceMethod];
}
@end
  
  
  
#import "LGPerson.h"

@implementation LGPerson
- (void)personInstanceMethod{
    NSLog(@"person对象方法:%s",__func__);
}

@end

#import "LGStudent.h"
#import <objc/runtime.h>

@implementation LGStudent
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"方法交换---:%s", __func__);
        Method oriIMP = class_getInstanceMethod(self, @selector(personInstanceMethod));
        Method swiIMP = class_getInstanceMethod(self, @selector(lg_studentInstanceMethod));
        method_exchangeImplementations(oriIMP, swiIMP);
    });
}

- (void)lg_studentInstanceMethod {
    [self lg_studentInstanceMethod];
    NSLog(@"LGStudent对象方法:%s", __func__);
}

运行结果:

**分析:**成功调用,子类调用父类的方法personInstanceMethod,通过消息发送的原理,会进行慢速查找,找到父类方法,此时父类方法的实现变成了子类的lg_studentInstanceMethod方法,在子类中调用lg_studentInstanceMethod方法,最终的实现的方法为父类的personInstanceMethod

如果调用父类对象的personInstanceMethod方法?

objective-c 复制代码
#import "ViewController.h"
#import "LGStudent.h"
#import "LGPerson.h"
@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    
    LGPerson *p = [[LGPerson alloc] init];
    [p personInstanceMethod];
}
@end
  
  
  
#import "LGPerson.h"

@implementation LGPerson
- (void)personInstanceMethod{
    NSLog(@"person对象方法:%s",__func__);
}

@end

#import "LGStudent.h"
#import <objc/runtime.h>

@implementation LGStudent
+ (void)load {
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        NSLog(@"方法交换---:%s", __func__);
        Method oriIMP = class_getInstanceMethod(self, @selector(personInstanceMethod));
        Method swiIMP = class_getInstanceMethod(self, @selector(lg_studentInstanceMethod));
        method_exchangeImplementations(oriIMP, swiIMP);
    });
}

- (void)lg_studentInstanceMethod {
    [self lg_studentInstanceMethod];
    NSLog(@"LGStudent对象方法:%s", __func__);
} 

运行结果:

**分析:**报错,父类调用personInstanceMethod方法,执行的是子类lg_studentInstanceMethod实现,并且同时调用lg_studentInstanceMethod方法,但是父类中并没有lg_studentInstanceMethod的实现,找不到这个方法,所以报错。

如果进行方法交换,一定要确保方法已经实现,否则会出现本例中啃爹的现象(方法交换,而父类没有方法的实现,导致报错)。所以在进行相关方法交换时,尽量避免涉及到其父类或者其子类的方法。

3. 方法交换思路

为避免上面案例的问题,总结以下实现思路:

为避免上面案例的问题,总结以下实现思路:

objective-c 复制代码
 + (void)lg_bestMethodSwizzlingWithClass:(Class)cls oriSEL:(SEL)oriSEL swizzledSEL:(SEL)swizzledSEL{

    if (!cls) NSLog(@"传入的交换类不能为空");
    
    // 获取类中的方法
    Method oriMethod = class_getInstanceMethod(cls, oriSEL);
    // 要被交换的方法
    Method swiMethod = class_getInstanceMethod(cls, swizzledSEL);
    
    // 判断类中是否存在该方法-避免动作没有意义
    if (!oriMethod) { 

        // 在oriMethod为nil时,添加oriSEL的方法,实现为swiMethod
        class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));

        // 替换后将swizzledSEL复制一个不做任何事的空实现,代码如下:
        method_setImplementation(swiMethod, imp_implementationWithBlock(^(id self, SEL _cmd){

            NSLog(@"来了一个空的 imp");
        }));
    }

    // 一般交换方法: 交换自己有的方法 -- 走下面 因为自己有意味添加方法失败
    // 交换自己没有实现的方法:
    //   首先第一步:会先尝试给自己添加要交换的方法 :personInstanceMethod (SEL) -> swiMethod(IMP)
    //   然后再将父类的IMP给swizzle  personInstanceMethod(imp) -> swizzledSEL
    //oriSEL:personInstanceMethod

    // 向类中添加oriSEL方法,方法实现为swiMethod
    BOOL didAddMethod = class_addMethod(cls, oriSEL, method_getImplementation(swiMethod), method_getTypeEncoding(swiMethod));
    
    // 自己有意味添加方法失败-所以这里会是false
    if (didAddMethod) {
        // 如果添加成功,表示原本没有oriMethod方法,此时将swizzledSEL的方法实现,替换成oriMethod实现
        class_replaceMethod(cls, swizzledSEL, method_getImplementation(oriMethod), method_getTypeEncoding(oriMethod));
    }else{
        // 方法交换
        method_exchangeImplementations(oriMethod, swiMethod);
    }
}
相关推荐
用户091 天前
SwiftUI Charts 函数绘图完全指南
ios·swiftui·swift
YungFan1 天前
iOS26适配指南之UIColor
ios·swift
权咚2 天前
阿权的开发经验小集
git·ios·xcode
用户092 天前
TipKit与CloudKit同步完全指南
ios·swift
小溪彼岸2 天前
macOS自带截图命令ScreenCapture
macos
法的空间2 天前
Flutter JsonToDart 支持 JsonSchema
android·flutter·ios
2501_915918412 天前
iOS 上架全流程指南 iOS 应用发布步骤、App Store 上架流程、uni-app 打包上传 ipa 与审核实战经验分享
android·ios·小程序·uni-app·cocoa·iphone·webview
TESmart碲视2 天前
Mac 真正多显示器支持:TESmart USB-C KVM(搭载 DisplayLink 技术)如何实现
macos·计算机外设·电脑
00后程序员张2 天前
iOS App 混淆与加固对比 源码混淆与ipa文件混淆的区别、iOS代码保护与应用安全场景最佳实践
android·安全·ios·小程序·uni-app·iphone·webview
Magnetic_h2 天前
【iOS】设计模式复习
笔记·学习·ios·设计模式·objective-c·cocoa