Objective-C消息发送机制原理objc_msgSend( )

0、方法调用原理
0.1、以[reciver method]为例,实际上是objc_msgSend(reciver, @selector(method), …argments);
0.2、OC方法调用转换为objc_msgSend函数调用,方法调用者、方法名为该函数的前2个参数;
0.3、OC对象本质为结构体,参考:剖析Objective-C对象本质结构MJClassInfo

1、消息发送(查找方法)
1.1、如果方法调用者为nil,直接返回(不报错);
2.2、方法调用者通过isa指针查找方法接收者,方法接收者查找对象中的cache,如果没有则查找对象中方法列表class_rw_t;
2.3、如果找到则调用并缓存在cache中,否则通过superclass在父类中的cache,class_rw_t中查找;
2.4、如果找到则调用并缓存在cache中,否则再通过superclass的superclass查找,直到superclass为空;
2.5、如果在整个继承体系中都没有找到目标方法,调用方法接收者的动态解析方法(如果解析过则进入方法转发);

2、动态解析
2.1、两个相关方法:resoloveInstanceMethod:、resoloveClassMethod:前者为实例对象动态解析,后者为类\元类对象动态解析;

2.2、进入动态解析方法,需要完成的工作是给方法接收者添加方法实现,如通过class_addMetho()函数实现;

1
class_addMethod(Class cls, SEL name, IMP imp, const char *types)

2.2.1、第一参数为方法接收者:实例方法调用则cls为类对象,类方法调用则为元类对象;
2.2.2、第二个参数为方法名,可直接使用传进来的形参;
2.2.3、第三个参数与第四个参数与方法对象Method相关,可通过method_getImplementation()、method_getTypeEncoding()获取,而Method对象可通过class_getClassMethod(Class cls, SEL sel)获取;

2.3、添加方法实现后,原来调用sel方法动作变成调用method方法,解析后的方法不关心方法性质,即对象方法或类方法都可以;
2.4、解析完成后,再次执行方法调用各步骤,如果查找到了动态方法则执行,如果还是没有找到,不再进行动态解析,进入方法转发阶段;

3、方法转发
3.1、相关方法

1
2
3
4
5
6
7
- (id)forwardingTargetForSelector:(SEL)aSelector;
+ (id)forwardingTargetForSelector:(SEL)aSelector;
 
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
+ (void)forwardInvocation:(NSInvocation *)anInvocation;

3.1、消息转发首先调用forwardingTargetForSelector:方法,该方法要求返回一个实现了原方法的对象;
3.1.1、如果返回对象不为空,则调用对象的aSelector方法,这也是一个方法调用,同样符合当前所有的机制,即从消息发送开始—>动态解析—->方法转发这一系列动作,可以理解为是一次递归;
3.1.2、如果该方法没有实现,或返回为空,则调用方法签名

3.2、方法签名methodSignatureForSelector:该方法只需要返回一个有效签名即可,接下来会调用forwardInvocation:
3.2.1、如果返回为空或不是有效的NSMethodSignature对象,则调用doesNotRecognizeSelector方法抛出异常;
3.2.2、如果返回一个有效的NSMethodSignature,forwardInvocation:没有实现同样调用doesNotRecognizeSelector方法抛出异常;

3.3、forwardInvocation:会根据方法签名给出NSInvocation对象,只要实现了该方法即会执行该方法内的代码为方法最终归宿;

4、如果以上所有操作什么都没做,则直接调用doesNotRecognizeSelector方法抛出异常;

5、总结过程中的几个方法

1
2
3
4
5
6
7
8
9
10
11
// 动态解析
+ (BOOL)resolveInstanceMethod:(SEL)sel;
+ (BOOL)resolveClassMethod:(SEL)sel;
// 方法转发
- (id)forwardingSignatureMethodForSelector:(SEL)aSelector;
+ (id)forwardingSignatureMethodForSelector:(SEL)aSelector;
// 方法签名
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector;
- (void)forwardInvocation:(NSInvocation *)anInvocation;
+ (void)forwardInvocation:(NSInvocation *)anInvocation;

4 thoughts on “Objective-C消息发送机制原理objc_msgSend( )

  1. Pingback: 定时器NSTimer、CADisplayLink及代理NSProxy的基本使用 | 一天到晚游泳的余

  2. Sian Post author

    – (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    – (void)forwardInvocation:(NSInvocation *)anInvocation

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    
    #import "ViewController.h"
    #import <objc/runtime.h>
     
    @interface ViewController ()
    - (void)instanceMethod;
    + (void)classMethod;
    @end
     
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self instanceMethod];
    }
     
    - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(instanceMethod)){
            // 返回一个方法签名,该方法签名会联合aSelecotr被封装到一个NSInvocation对象中
            // 调用forwardInvocation:方法;
            return [NSMethodSignature signatureWithObjCTypes:"v@:"];
        }
        return [super methodSignatureForSelector:aSelector];
    }
    - (void)forwardInvocation:(NSInvocation *)anInvocation
    {
        NSLog(@"%s", __func__);
    }
    + (void)classMethod
    {
        NSLog(@"%s", __func__);
    }
     
    @end

    输出结果:
    2018-11-21 10:49:37.669124+0800 runtime[2166:353273] -[ViewController forwardInvocation:]

  3. Sian Post author

    – (id)forwardingTargetForSelector:(SEL)aSelector 实现:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    
    #import "ViewController.h"
    #import <objc/runtime.h>
     
    @interface ViewController ()
    - (void)instanceMethod;
    + (void)classMethod;
    @end
     
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self instanceMethod];
    }
     
    - (id)forwardingTargetForSelector:(SEL)aSelector
    {
        if (aSelector == @selector(instanceMethod)){
            // 原消息接收者为当前实例对象
            // 返回类对象,即将消息转发给类对象处理
            // 最终会调用类对象的相关方法
            return self.class;
        }
        return  [super forwardingTargetForSelector:aSelector];
    }
     
    + (void)classMethod
    {
        NSLog(@"%s", __func__);
    }
     
    @end
  4. Sian Post author

    + (BOOL)resolveInstanceMethod:(SEL)sel 示例:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    
    #import "ViewController.h"
    #import <objc/runtime.h>
     
    @interface ViewController ()
    - (void)instanceMethod;
    + (void)classMethod;
    @end
     
    @implementation ViewController
    - (void)viewDidLoad {
        [super viewDidLoad];
        [self instanceMethod];
    }
     
    + (BOOL)resolveInstanceMethod:(SEL)sel
    {
        if (sel == @selector(instanceMethod)){
            Class metaClass = objc_getMetaClass(class_getName(self));
            // 获取元类上的classMethod方法
            Method method = class_getClassMethod(metaClass, @selector(classMethod));
            IMP imp = method_getImplementation(method);
            const char *types = method_getTypeEncoding(method);
            // 给类对象的instanceMethod方法添加元类对象上的classMethod方法实现
            // 这样的结果是:调用- (void)instanceMethod方法会执行+ (void)classMethod
            class_addMethod(self, sel, imp, types);
            return YES;
        }
        return [super resolveInstanceMethod:sel];
    }
     
    + (void)classMethod
    {
        NSLog(@"%s", __func__);
    }
     
    @end

Leave a Reply