24、OC语言的动态性学习(Runtime)

文章目录

  • 编译时:即编译器对语言的编译阶段。编译时只是对语言进行最基本的检查报错,包括词法分析、语法分析等等,将程序代码翻译成计算机能够识别的语言(例如汇编等),编译通过并不意味着程序就可以成功运行。
  • 运行时:即程序通过了编译这一关之后,编译好的代码会被装载到内存中运行起来的阶段,这个时候会具体对类型进行检查,而不仅仅是对代码的简单扫描分析,此时若出错程序会崩溃。

OC语言的动态性主要体现在三个方面:动态类型(Dynamic typing)、动态绑定(Dynamic binding)和动态加载(Dynamic loading)

一、动态类型

动态类型就是指运行时再去决定对象的类型,简单来说就是id类型。id类型是通用的对象类型,任何对象都可以被id指针所指。使用id类型将对象的类型推迟到运行时才确定,由赋给它的对象类型决定该对象类型。

也就是说id修饰的对象是动态类型对象,其他在编译期指明类型的为静态类型对象。

所以开发中如果不是涉及到多态,尽量还是使用静态的类型,这样编写错误,编译器会提前查出问题,可读性更高一点。

// 编译时认为是NSString,这是赋值了一个NSData对象编译器会给出警告信息:Incompatible pointer types initializing 'NSString *' with an expression of type 'NSData *'
NSString *testObject = [[NSData alloc]init];
// 编译其认为是NSString,所以允许使用NSString的方法,不会有警告和错误,
[testObject stringByAppendingString:@"string"];
// 编译期不允许使用NSData的方法,错误提示;No visible @interface for 'NSString' declares the selector 'base64EncodedDataWithOptions:'
[testObject  base64EncodedDataWithOptions:NSDataBase64Encoding64CharacterLineLength];

如以上代码,testObject在编译时,指针的类型是NSString,也就是说编译时期是被当做一个NSString类型来处理,编译器在类型检查时发现类型不匹配会给出警告信息,testObject在运行时,指针指向的是一个NSData对象,因此如果指针调用了NSString的方法,那么虽然编译通过了,但运行时会出现崩溃,

在实际开发中,可以通过-isKindOfClass:方法来确定对象的具体类型,在确定对象为某类成员后,可以安全地进行强制转换,继续之后的工作。

-isKindOfClass:-isMemberOfClass:都是NSObject的方法,-isKindOfClass:可以用来确定某个对象是否是某个类或者其子类的实例对象,而isKindOfClass:用以确定某个对象是否某个类的实例对象。

id obj = someInstance;
if ([obj isKindOfClass:someClass])
{
   
    someClass *classSpecifiedInstance = (someClass *)obj;
    // Do Something to classSpecifiedInstance which now is an instance of someClass
    //...
}

二、动态绑定

基于动态类型,在某个实例对象被确定后,其类型便被确定了。该对象对应的属性和响应的消息也被完全确定,这就是动态绑定。

由于OC的动态特性,在OC中其实很少提及“函数”的概念,传统的函数一般在编译时就已经把参数信息和函数实现打包到编译后的源码中了,而在OC中最常使用的是消息机制。调用一个实例的方法,所做的是向该实例的指针发送消息,实例在收到消息后,从自身的实现中寻找响应这条消息的方法。

动态绑定所做的就是在实例所属类确定后,将某些属性和相应的方法绑定到实例上。 这里所指的属性和方法当然包括了原来没有在类中实现的,而是在运行时才需要的新加入的实现。在Cocoa层,我们一般向一个NSObject对象发送-respondsToSelector:或者-instancesRespondToSelector:等来确定对象是否可以对某个SEL做出响应,而在OC消息转发机制被触发之前,对应的类的+resolveClassMethod:+resolveInstanceMethod:将会被调用,在此时有机会动态地向类或者实例添加新的方法,也即类的实现是可以动态绑定的。

void dynamicMethodIMP(id self, SEL _cmd)
{
   
    // implementation ....
}

//该方法在OC消息转发生效前被调用
+ (BOOL) resolveInstanceMethod:(SEL)aSEL
{
    
    if (aSEL == @selector(resolveThisMethodDynamically)) {
   
        //向[self class]中新加入返回为void的实现,SEL名字为aSEL,实现的具体内容为dynamicMethodIMP 
        class_addMethod([self class], aSEL, (IMP) dynamicMethodIMP, “v@:);
        return YES;
    }
    return [super resolveInstanceMethod:aSel];
}  

当然也可以在任意需要的地方调用class_addMethod或者method_setImplementation(前者添加实现,后者替换实现),来完成动态绑定的需求。

基本的动态特性在常规的Cocoa开发中非常常用,特别是动态类型和动态绑定。由于Cocoa程序大量地使用Protocol-Delegate的设计模式,因此绝大部分的delegate指针类型必须是id,以满足运行时delegate的动态替换。

三、动态加载

动态加载分为两部分:动态资源的加载(如:图片资源),代码模块的加载;这些都是在运行时根据需要有选择性的添加到程序中的,是一种代码和资源的“懒加载”模式,这样降低编译时期对内存的开销,提供程序的性能。

如:资源在动态加载图片进行屏幕适配时,因为同一个图片对象可能会准备几种不同分辨率的图片资源,程序就会根据当前机型动态的选择对应分辨率的图片,如:@1x,@2x,@3x的。

四、消息机制的基本原理

Objective-C 语言中,对象方法调用都是类似 [receiver selector]; 的形式,其本质就是让对象在运行时发送消息的过程。OC中的方法的调用时通过objc_msgSend(或者objc_msgSendSuper,或者objc_msgSend_stret,或者objc_msgSendSuper_stret)函数,向调用者发送名为SEL的消息,找到具体的函数地址IMP,进而执行该函数。

  • 编译阶段:[receiver selector];方法会被编译器转换为:
    objc_msgSend(receiver,selector) (不带参数)
    objc_msgSend(recevier,selector,org1,org2,…)(带参数)

  • 运行时阶段:消息接受者receiver寻找对应的selector。

objc_msgSend的执行流程可以分为3大阶段:

  • 1、消息查找
  • 2、动态方法解析
  • 3、消息转发

消息查找阶段

在这里插入图片描述

1、通过recevier的isa指针找到recevier的Class(类);根据给定的SEL,到缓存的方法列表cache_t中通过哈希查找得到对应的bucket_t在数组中的位置,就可以提取到它所对应的IMP函数指针,返回给调用方调用即可。

如果在当前类的Class(类)中没有找到这个selector,就继续在它的 superClass(父类)中寻找;
通过当前类的superClass来访问它的父类,需要判断父类是否为空,当前类如果是NSObject,那么它的父类就是nil,就可以结束父类逐级查找过程了。如果不是nil,就到父类的缓存中去查找,如果在缓存中查到了,就结束父类逐级查找的流程。如果没有找到就需要遍历当前类的父类的方法列表是否有实现,如果没有继续逐级向上查找,直到NSObject,还没有找到就结束父类逐级查找。
这个时候就会触发动态方法解析。

动态方法解析

在这里插入图片描述

Objective-C 运行时会调用 +resolveInstanceMethod: 或者 +resolveClassMethod:,让你有机会提供一个函数实现。我们可以通过重写这两个方法,通过class_addMethod添加其他函数实现,并返回 YES, 那运行时系统就会重新启动一次消息发送的过程。

主要用的的方法如下:

// 类方法未找到时调起,可以在此添加类方法实现
+ (BOOL)resolveClassMethod:(SEL)sel;
// 对象方法未找到时调起,可以在此对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel;

/** 
 * class_addMethod     给类添加一个新方法和该方法的实现
 * @param cls         将要添加方法的类
 * @param name        将要添加的方法名
 * @param imp         实现这个方法的指针
 * @param types imp   要添加的方法的返回值和参数
 * @return            如果添加方法成功返回 YES,否则返回 NO
 */
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                const char * _Nullable types);

举个例子:

#import "ViewController.h"
#include "objc/runtime.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
   
    [super viewDidLoad];
    
    // 执行 fun 函数
    [self performSelector:@selector(fun)];
}

// 重写 resolveInstanceMethod: 添加对象方法实现
+ (BOOL)resolveInstanceMethod:(SEL)sel {
   
    if (sel == @selector(fun)) {
    // 如果是执行 fun 函数,就动态解析,指定新的 IMP
        class_addMethod([self class], sel, (IMP)funMethod, "v@:");
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

void funMethod(id obj, SEL _cmd) {
   
    NSLog(@"funMethod"); //新的 fun 函数
}

@end
打印结果:
2019-06-12 10:25:39.848260+0800 runtime[14884:7977579] funMethod

v@:表示返回值和参数,可以在苹果官网查看Type Encoding相关文档 https://developer.apple.com/library/mac/DOCUMENTATION/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html

面试的过程中,经常会问到你是否使用过performSelector。
performSelector的应用场景之一就是一个类在编译时没有某个方法,在有运行时才会添加某个方法的实现。就需要使用performSelector去调用这个类的方法。

主要是为了考察动态添加方法。

消息转发

在这里插入图片描述

消息快转发

如果上一步中 +resolveInstanceMethod: 或者 +resolveClassMethod: 没有添加其他函数实现,运行时就会进行下一步:消息转发。消息转发可以分为消息快转发和消息慢转发,消息快转发其实就是重定向,把消息转交给其他类处理。

-forwardingTargetForSelector:对应的就是消息的快速转发流程,它主要是返回一个新的receiver,去处理sel这个当前类无法处理的消息,如果处理不了,就会转到效率低下的forwardInvocation:
在效率方面,forwardingTargetForSelector:领先forwardInvocation:一个数量级,因此,最好不要用后者的方式处理消息的转发逻辑。

关于forwardingTargetForSelector:返回新的receiver,需要注意以下几点:

  • 绝对不能返回self,否则会陷入无限循环。
  • 不处理的话,可以返回nil,或者[super forwardingTargetForSelector:sel](非根类的情况),此时会走methodSignatureForSelector:慢转发流程;
  • 如果有这个receiver,此时相当于执行objc_msgSend(newReceiver,sel,...),那么它必须拥有和被调用的方法相同方法前面的方法(方法名、参数列表、返回值类型都必须一致)。

用到的方法:

// 重定向方法的消息接收者,返回一个类或实例对象
- (id)forwardingTargetForSelector:(SEL)aSelector;

注意:这里+resolveInstanceMethod: 或者 +resolveClassMethod:无论是返回 YES,还是返回 NO,只要其中没有添加其他函数实现,运行时都会进行下一步。
举个例子:

#import "ViewController.h"
#include "objc/runtime.h"

@interface Person : NSObject

- (void)fun;

@end

@implementation Person

- (void)fun {
   
    NSLog(@"fun");
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
   
    [super viewDidLoad];
    
    // 执行 fun 方法
    [self performSelector:@selector(fun)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
   
    return YES; // 为了进行下一步 消息接受者重定向
}

// 消息接受者重定向
- (id)forwardingTargetForSelector:(SEL)aSelector {
   
    if (aSelector == @selector(fun)) {
   
        return [[Person alloc] init];
        // 返回 Person 对象,让 Person 对象接收这个消息
    }
    
    return [super forwardingTargetForSelector:aSelector];
}
打印结果:
2019-06-12 17:34:05.027800+0800 runtime[19495:8232512] fun

我们通过 forwardingTargetForSelector 可以修改消息的接收者,该方法返回参数是一个对象,如果这个对象是不是 nil,也不是 self,系统会将运行的消息转发给这个对象执行。否则,继续进行下一步:消息慢转发。

消息慢转发

如果经过消息动态解析、消息快转发,Runtime 系统还是找不到相应的方法实现而无法响应消息,Runtime 系统会利用 -methodSignatureForSelector: 方法获取函数的参数和返回值类型。

  • 如果 -methodSignatureForSelector: 返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 -forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
  • 如果 -methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector: 消息,程序也就崩溃了。
    所以我们可以在 -forwardInvocation: 方法中对消息进行转发。

用到的方法:

// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;

// 消息重定向
- (void)forwardInvocation:(NSInvocation *)anInvocation;

举个例子:

#import "ViewController.h"
#include "objc/runtime.h"

@interface Person : NSObject

- (void)fun;

@end

@implementation Person

- (void)fun {
   
    NSLog(@"fun");
}

@end

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
   
    [super viewDidLoad];
    
    // 执行 fun 函数
    [self performSelector:@selector(fun)];
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
   
    return YES; // 为了进行下一步 消息接受者重定向
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
   
    return nil; // 为了进行下一步 消息重定向
}

// 获取函数的参数和返回值类型,返回签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
   
    if ([NSStringFromSelector(aSelector) isEqualToString:@"fun"]) {
   
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    
    return [super methodSignatureForSelector:aSelector];
}

// 
- (void)forwardInvocation:(NSInvocation *)anInvocation {
   
    SEL sel = anInvocation.selector;   // 从 anInvocation 中获取消息
    
    Person *p = [[Person alloc] init];

    if([p respondsToSelector:sel]) {
      // 判断 Person 对象方法是否可以响应 sel
        [anInvocation invokeWithTarget:p];  // 若可以响应,则将消息转发给其他对象处理
    } else {
   
        [self doesNotRecognizeSelector:sel];  // 若仍然无法响应,则报错:找不到响应方法
    }
}
@end
打印结果:
2019-06-13 13:23:06.935624+0800 runtime[30032:8724248] fun

可以看到,我们在 -forwardInvocation: 方法里面让 Person 对象去执行了 fun 函数。

既然 -forwardingTargetForSelector:-forwardInvocation: 都可以将消息转发给其他对象处理,那么两者的区别在哪?

区别就在于 -forwardingTargetForSelector: 只能将消息转发给一个对象。而 -forwardInvocation: 可以将消息转发给多个对象。

以上就是 Runtime 消息转发的整个流程。

消息发送以及转发机制的总结

调用 [receiver selector]; 后,进行的流程:

  • 1、编译阶段:[receiver selector]; 方法被编译器转换为:
    objc_msgSend(receiver,selector) (不带参数)
    objc_msgSend(recevier,selector,org1,org2,…)(带参数)

  • 2、运行时阶段:消息接受者recever寻找对应的selector。
    (1)通过recevier的isa指针找到recevier的class(类);
    (2)在class(类)的method list(方法列表)中找对应的 selector;
    (3)如果在class(类)中没有找到这个selector,就继续在它的 superclass(父类)中寻找;
    (4)一旦找到对应的selector,直接执行recever对应selector方法实现的 IMP(方法实现)。
    (5)若找不到对应的selector,Runtime系统进入消息转发机制。

  • 3、运行时消息转发阶段:
    (1)动态方法解析:通过重写 +resolveInstanceMethod: 或者 +resolveClassMethod:方法,利用 class_addMethod方法添加其他函数实现;
    (2)消息快速转发:如果上一步添加其他函数实现,可在当前对象中利用 -forwardingTargetForSelector: 方法将消息的接受者转发给其他对象;
    (3)消息慢速转发:如果上一步没有返回值为 nil,则利用 -methodSignatureForSelector:方法获取函数的参数和返回值类型。

    • 如果-methodSignatureForSelector:返回了一个 NSMethodSignature 对象(函数签名),Runtime 系统就会创建一个 NSInvocation 对象,并通过 -forwardInvocation: 消息通知当前对象,给予此次消息发送最后一次寻找 IMP 的机会。
    • 如果 -methodSignatureForSelector: 返回 nil。则 Runtime 系统会发出 -doesNotRecognizeSelector: 消息,程序也就崩溃了。

重写respondsToSelector方法

OC中respondsToSelector方法可以用检查类对象是否能够处理对应的selector,当我们通过消息转发机制来处理selector时,respondsToSelector并不能按原意正常工作了,这个时候需要重写respondsToSelector方法,用来告诉方法调用者对应的selector是能够被处理的。如果是在动态解析阶段使用class_addMethod来为类动态添加方法,则不需要重写respondsToSelector.

代码实现

动态解析

void printTest() {
   
    NSLog(@"printTest");
}

@interface Test : NSObject
- (void) print;
@end

@implementation Test

+ (BOOL)resolveInstanceMethod:(SEL)sel {
   
    if(sel_isEqual(sel, @selector(print))) {
   
        //如果是print方法 就把printTest方法的实现地址链接到print上面
        class_addMethod([self class], @selector(print), (IMP)printTest, "v@:");
        return YES;
    }
    return  [super resolveInstanceMethod:sel];
}

@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
   
    [super viewDidLoad];
    Test* tt = [[Test alloc] init];
    if([tt respondsToSelector:@selector(print)]){
   
        [tt print];
    }
}
@end

快速转发

@interface Test2:NSObject
    - (void) print;
@end

@implementation Test2
- (void) print {
   
    NSLog(@"Test2");
}
@end

@interface Test : NSObject {
   
    Test2* _helper;
}
- (void) print;
- (void) moya;
@end

@implementation Test

- (instancetype)init
{
   
    self = [super init];
    if (self) {
   
        _helper = [[Test2 alloc] init];
    }
    return self;
}

- (BOOL)respondsToSelector:(SEL)aSelector {
   
    if(sel_isEqual(@selector(print), aSelector)){
   
        return [_helper respondsToSelector:aSelector];
    }
    return [super respondsToSelector:aSelector];
}

- (id)forwardingTargetForSelector:(SEL)aSelector {
   
    if (sel_isEqual(@selector(print), aSelector)){
   
        return _helper;
    }
    return [super forwardingTargetForSelector:aSelector];
}

- (void) moya {
   
    NSLog(@"moya");
}

@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
   
    [super viewDidLoad];
    Test* tt = [[Test alloc] init];
    if([tt respondsToSelector:@selector(print)]){
   
        [tt print];
    }
    
    if([tt respondsToSelector:@selector(moya)]){
   
        [tt moya];
    }
}
@end

慢速转发

@interface Test2:NSObject
    - (void) print;
@end

@implementation Test2
- (void) print {
   
    NSLog(@"Test2");
}
@end

@interface Test : NSObject {
   
    Test2* _helper;
}
- (void) print;
- (void) moya;
@end

@implementation Test

- (instancetype)init
{
   
    self = [super init];
    if (self) {
   
        _helper = [[Test2 alloc] init];
    }
    return self;
}

- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
   
    if (sel_isEqual(@selector(print), aSelector)){
   
        NSMethodSignature* sig = [NSMethodSignature signatureWithObjCTypes:"v@:"];
        return sig;
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
   
    SEL sel = [anInvocation selector];
    if([_helper respondsToSelector:sel]){
   
        [anInvocation invokeWithTarget:_helper];
    }else{
   
        [self doesNotRecognizeSelector:sel];
    }
}

- (BOOL)respondsToSelector:(SEL)aSelector {
   
    if (sel_isEqual(@selector(print), aSelector)){
   
        return [_helper respondsToSelector:aSelector];
    }
    return [super respondsToSelector:aSelector];
}

- (void) moya {
   
    NSLog(@"moya");
}

@end

@interface ViewController ()
@end

@implementation ViewController

- (void)viewDidLoad {
   
    [super viewDidLoad];
    Test* tt = [[Test alloc] init];
    if([tt respondsToSelector:@selector(print)]){
   
        [tt print];
    }
    
    if([tt respondsToSelector:@selector(moya)]){
   
        [tt moya];
    }
}
@end

五、Method Swizzling(动态方法交换)

简介

Method Swizzling实现将某个类的方法替换成自己定义的类的方法,从而达到Hook的作用,其实质就是交换两个方法的 IMP(方法实现)。

Method Swizzling原理:在OC中,每个类都维护着一个方法(Method)列表,Method则包含SEL和其对应的IMP信息,方法交换做的事情就是把SEL和IMP的对应关系断开,并和新的IMP生成对应关系。

Method(方法)对应的是 objc_method 结构体;而 objc_method 结构体 中包含了 SEL method_name(方法名)IMP method_imp(方法实现)

// objc_method 结构体
typedef struct objc_method *Method;

struct objc_method {
   
    SEL _Nonnull method_name;              // 方法名
    char * _Nullable method_types;         // 方法类型
    IMP _Nonnull method_imp;              // 方法实现
};

Method(方法)SEL(方法名)、IMP(方法实现)三者的关系可以这样来表示:
在运行时,Class(类) 维护了一个 method list(方法列表) 来确定消息的正确发送。method list(方法列表) 存放的元素就是 Method(方法)。而 Method(方法) 中映射了一对键值对:SEL(方法名):IMP(方法实现)

Method swizzling 修改了 method list(方法列表),使得不同 Method(方法)中的键值对发生了交换。比如交换前两个键值对分别为 SEL A : IMP ASEL B : IMP B,交换之后就变为了 SEL A : IMP BSEL B : IMP A

使用Method Swizzling需要的几个方法

使用Method Swizzling需要用到几个方法:

// 获取一个对象的实例方法
// cls: 方法所在的类
// name: 方法名称的selector
Method class_getInstanceMethod(Class cls, SEL name)

// 获取一个类的实例方法
// cls: 方法所在的类
// name: 方法名称的selector
Method class_getClassMethod(Class cls, SEL name)

// 交换两个方法
void method_exchangeImplementations(Method _Nonnull m1, Method _Nonnull m2);

/** 
 * class_addMethod     给类添加一个新方法和该方法的实现
 * @param cls         将要添加方法的类
 * @param name        将要添加的方法名
 * @param imp         实现这个方法的指针
 * @param types imp   要添加的方法的返回值和参数
 * @return            如果添加方法成功返回 YES,否则返回 NO
 */
BOOL class_addMethod(Class cls, SEL name, IMP imp, 
                const char * _Nullable types);
                
// 作用: 指定替换方法的实现
// cls : 将要替换方法的类
// name: 将要替换的方法名
// imp: 新方法的指针
// types: 新方法的返回值和参数描述
IMP _Nullable class_replaceMethod(Class _Nullable cls, SEL _Nonnull name, IMP _Nonnull imp, 
                    const char * _Nullable types) ;

// 获取方法的参数和返回值类型描述
const char * _Nullable method_getTypeEncoding(Method _Nonnull m);

Method Swizzling 使用方法

假如当前类中有两个方法:- (void)originalFunction;- (void)swizzledFunction;。如果我们想要交换两个方法的实现,从而实现调用 - (void)originalFunction; 方法实际上调用的是 - (void)swizzledFunction; 方法,而调用 - (void)swizzledFunction; 方法实际上调用的是 - (void)originalFunction; 方法的效果。那么我们需要像下边代码一样来实现。

Method Swizzling 简单使用

在当前类的 + (void)load; 方法中增加 Method Swizzling 操作,交换 - (void)originalFunction;- (void)swizzledFunction; 的方法实现。

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

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
   
    [super viewDidLoad];
    
    [self SwizzlingMethod];
    [self originalFunction];
    [self swizzledFunction];
}


// 交换 原方法 和 替换方法 的方法实现
- (void)SwizzlingMethod {
   
    // 当前类
    Class class = [self class];
    
    // 原方法名 和 替换方法名
    SEL originalSelector = @selector(originalFunction);
    SEL swizzledSelector = @selector(swizzledFunction);
    
    // 原方法结构体 和 替换方法结构体
    Method originalMethod = class_getInstanceMethod(class, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
    
    // 调用交换两个方法的实现
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

// 原始方法
- (void)originalFunction {
   
    NSLog(@"originalFunction");
}

// 替换方法
- (void)swizzledFunction {
   
    NSLog(@"swizzledFunction");
}

@end
打印结果:
2019-07-12 09:59:19.672349+0800 Runtime-MethodSwizzling[91009:30112833] swizzledFunction
2019-07-12 09:59:20.414930+0800 Runtime-MethodSwizzling[91009:30112833] originalFunction

一般日常开发中,并不是直接在原有类中进行 Method Swizzling 操作。更多的是为当前类添加一个分类,然后在分类中进行 Method Swizzling 操作。另外真正使用会比上边写的考虑东西要多一点,要复杂一些。

Method Swizzling 方案一

在该类的分类中添加 Method Swizzling 交换方法,用普通方式。
这种方式在开发中应用最多的。但是还是要注意一些事项,我会在接下来的 3. Method Swizzling 使用注意 进行详细说明。

@implementation UIViewController (Swizzling)

// 交换 原方法 和 替换方法 的方法实现
+ (void)load {
   
    
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
   
        // 当前类
        Class class = [self class];
        
        // 原方法名 和 替换方法名
        SEL originalSelector = @selector(originalFunction);
        SEL swizzledSelector = @selector(swizzledFunction);
        
        // 原方法结构体 和 替换方法结构体
        Method originalMethod = class_getInstanceMethod(class, originalSelector);
        Method swizzledMethod = class_getInstanceMethod(class, swizzledSelector);
        
        /* 如果当前类没有 原方法的 IMP,说明在从父类继承过来的方法实现,
         * 需要在当前类中添加一个 originalSelector 方法,
         * 但是用 替换方法 swizzledMethod 去实现它 
         */
        BOOL didAddMethod = class_addMethod(class,originalSelector,method_getImplementation(swizzledMethod),method_getTypeEncoding(swizzledMethod));
        if (didAddMethod) {
   
            // 原方法的 IMP 添加成功后,修改 替换方法的 IMP 为 原始方法的 IMP
            class_replaceMethod(class,swizzledSelector,method_getImplementation(originalMethod),method_getTypeEncoding(originalMethod));
        } else {
   
            // 添加失败(说明已包含原方法的 IMP),调用交换两个方法的实现
       method_exchangeImplementations(originalMethod, swizzledMethod);
        }
    });
}

// 原始方法
- (void)originalFunction {
   
    NSLog(@"originalFunction");
}

// 替换方法
- (void)swizzledFunction {
   
    NSLog(@"swizzledFunction");
}

@end

Method Swizzling 方案 B

在该类的分类中添加 Method Swizzling 交换方法,但是使用函数指针的方式。
方案 B 和方案 A 的最大不同之处在于使用了函数指针的方式,使用函数指针最大的好处是可以有效避免命名错误。

#import "UIViewController+PointerSwizzling.h"
#import <objc/runtime.h>

typedef IMP *IMPPointer;

// 交换方法函数
static void MethodSwizzle(id self, SEL _cmd, id arg1);
// 原始方法函数指针
static void (*MethodOriginal)(id self, SEL _cmd, id arg1);

// 交换方法函数
static void MethodSwizzle(id self, SEL _cmd, id arg1) {
   
    
    // 在这里添加 交换方法的相关代码
    NSLog(@"swizzledFunc");
    
    MethodOriginal(self, _cmd, arg1);
}

BOOL class_swizzleMethodAndStore(Class class, SEL original, IMP replacement, IMPPointer store) {
   
    IMP imp = NULL;
    Method method = class_getInstanceMethod(class, original);
    if (method) {
   
        const char *type = method_getTypeEncoding(method);
        imp = class_replaceMethod(class, original, replacement, type);
        if (!imp) {
   
            imp = method_getImplementation(method);
        }
    }
    if (imp && store) {
    *store = imp; }
    return (imp != NULL);
}

@implementation UIViewController (PointerSwizzling)

+ (void)load {
   
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
   
        [self swizzle:@selector(originalFunc) with:(IMP)MethodSwizzle store:(IMP *)&MethodOriginal];
    });
}

+ (BOOL)swizzle:(SEL)original with:(IMP)replacement store:(IMPPointer)store {
   
    return class_swizzleMethodAndStore(self, original, replacement, store);
}

// 原始方法
- (void)originalFunc {
   
    NSLog(@"originalFunc");
}

@end

Method Swizzling 方案 C

在其他类中添加 Method Swizzling 交换方法

这种情况一般用的不多,最出名的就是 AFNetworking 中的_AFURLSessionTaskSwizzling 私有类。_AFURLSessionTaskSwizzling 主要解决了 iOS7 和 iOS8 系统上 NSURLSession 差别的处理。让不同系统版本 NSURLSession 版本基本一致。

static inline void af_swizzleSelector(Class theClass, SEL originalSelector, SEL swizzledSelector) {
   
    Method originalMethod = class_getInstanceMethod(theClass, originalSelector);
    Method swizzledMethod = class_getInstanceMethod(theClass, swizzledSelector);
    method_exchangeImplementations(originalMethod, swizzledMethod);
}

static inline BOOL af_addMethod(Class theClass, SEL selector, Method method) {
   
    return class_addMethod(theClass, selector,  method_getImplementation(method),  method_getTypeEncoding(method));
}

@interface _AFURLSessionTaskSwizzling : NSObject

@end

@implementation _AFURLSessionTaskSwizzling

+ (void)load {
   
    if (NSClassFromString(@"NSURLSessionTask")) {
   
        
        NSURLSe
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值