Runtime系列:消息机制【04】

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: OC中的方法调用的本质,都是转换为objc_msgSend函数的调用。这里所说的消息机制就是objc_msgSend的执行流程。

OC中的方法调用的本质,都是转换为objc_msgSend函数的调用。

这里所说的消息机制就是objc_msgSend的执行流程。


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


1、消息发送


1、首先判断消息接收者是否为nil,如果为nil则直接退出。所以存在使用 nil 调用方法,编译的时候也不会报错。

2、如果消息接收者不为空,通过isa指针找到消息接收者类对象,然后去查找方法(如果类对象没有找到方法,通过superclass指针找到父类继续查询),具体流程如下图:


receiver通过isa指针找到receiverClass

5bab06cd1b66475aacc5c19dc7a02383.png


注意:如果是在class_rw_t中查找方法:


1、已经排序的,二分查找

2、没有排序的,遍历查找


如果消息接收者的类和所有父类中都没有找到方法实现。则进入动态方法解析阶段。


2、动态方法解析


底层实现代码:


_class_resolveMethod(cls,sel,inst);

a53eda5f9d714ed89b45db2ce7aea3a3.pngtriedResolver:是否动态解析的标记。



2.1、实例方法动态解析

方法一:


//-(void)test {
//    NSLog(@"%s",__func__);
//}
- (void)other {
    NSLog(@"%s", __func__);
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 获取其他方法
        Method method = class_getInstanceMethod(self, @selector(other));
        // 动态添加test方法的实现
        class_addMethod(self,
                        sel,
                        method_getImplementation(method),
                        method_getTypeEncoding(method)
                        );
        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


注意:


Method 可以理解为等价于struct method_t *

方法二:


//- (void)test {
//    NSLog(@"%s",__func__);
//}
void c_other(id self, SEL _cmd) {
    NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 动态添加test方法的实现
        class_addMethod(self,
                        sel,
                        (IMP)c_other,
                        "v16@0:8"
                        );
        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}


方法实现:


2.2、类方法动态解析

//+ (void)test {
//    NSLog(@"%s",__func__);
//}
void c_other(id self, SEL _cmd) {
    NSLog(@"c_other - %@ - %@", self, NSStringFromSelector(_cmd));
}
+ (BOOL)resolveClassMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 注意:第一个参数是类对象【object_getClass(self)】
        class_addMethod(object_getClass(self), sel, (IMP)c_other, "v16@0:8");
        return YES;
    }
    return [super resolveClassMethod:sel];
}


3、消息转发


底层实现代码:


imp = (IMP)_objc_msgforward_impcache;

98edd44f93d6479f838f95edfaddbfa2.png


开发者可以在forwardInvocation:方法中自定义任何逻辑

以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)


解决方案:


3.1、实例方法

1、将消息转发给另一个实例对象实现

本质:在另外一个实例对象,实现一个一样的方法


- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        // objc_msgSend([[Cat alloc] init], aSelector)
        return [[Cat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}


Cat.h:


#import <Foundation/Foundation.h>
@interface Cat : NSObject
- (void)test;
@end


Cat.m:


#import "Cat.h"
@implementation Cat
- (void)test
{
    NSLog(@"%s", __func__);
}
@end


2、方法签名

// 方法签名:返回值类型、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
        // return [[[Cat alloc] init] methodSignatureForSelector:aSelector];
    }
    return [super methodSignatureForSelector:aSelector];
}
/**
 * NSInvocation封装了一个方法调用,包括:方法调用者、方法名、方法参数
 * anInvocation.target 方法调用者
 * anInvocation.selector 方法名
 * [anInvocation getArgument:NULL atIndex:0]
 */
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    // 参数顺序:receiver、selector、other arguments
    // anInvocation.target == [[MJCat alloc] init]
    // anInvocation.selector == test:
    // anInvocation的参数:15
    // [[[Cat alloc] init] test:15]
    [anInvocation invokeWithTarget:[[Cat alloc] init]];
    // 返回值
    int ret;
    [anInvocation getReturnValue:&ret];
    NSLog(@"%d", ret);
}


3.2、类方法

1、将消息转发给另一个实例对象实现

+ (id)forwardingTargetForSelector:(SEL)aSelector {
    // objc_msgSend([[Cat alloc] init], @selector(test))
    // [[[Cat alloc] init] test]
    if (aSelector == @selector(test)) {
        return [[Cat alloc] init];
    }
    return [super forwardingTargetForSelector:aSelector];
}


2、方法签名

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}
+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"1123");
}


当以上三个阶段没有实现,那就是 objc_msgSend 找不到合适的方法进行调用,会报错unrecognized selector sent to instance。


以上是从消息发送后,消息处理的全部流程。


相关文章
|
3月前
|
图形学
【unity知识点】实现延迟调用——InvokeRepeating Invoke CancelInvoke Coroutine使用介绍
【unity知识点】实现延迟调用——InvokeRepeating Invoke CancelInvoke Coroutine使用介绍
79 0
|
4月前
|
消息中间件 缓存 安全
android开发,使用kotlin学习消息机制Handler
android开发,使用kotlin学习消息机制Handler
197 0
|
Java
Java常用API---Runtime(消息机制)含代码例子
私有化构造方法,不能被实例化
172 0
Java常用API---Runtime(消息机制)含代码例子
|
C语言 数据安全/隐私保护
runtime(消息机制)
runtime(消息机制)
136 0
runtime(消息机制)
|
iOS开发
iOS - Runtime Swizzling 源码剖析
Runtime源码下载 源码位于objc-class-old.m
|
Java Spring
Spring源码编译报错:reactor.core.publisher中的MonoProcessor已过时
Spring源码编译报错:reactor.core.publisher中的MonoProcessor已过时
574 0
Spring源码编译报错:reactor.core.publisher中的MonoProcessor已过时