美文网首页
笔记 - OC 转发消息机制

笔记 - OC 转发消息机制

作者: 强子ly | 来源:发表于2019-09-30 20:59 被阅读0次
消息转发流程

目录

  • 一、消息机制
  • 二、方法调用源码查看
  • 三、objc_msgSend的执行流程
  • 四、番外篇

一、简要说明OC消息机制(objc_msgSend执行流程)

OC中的方法调用,其实都是转换为objc_msgSend函数调用

objc_msgSend的执行流程可以分为3大阶段
- 消息发送
- 动态方法解析
- 消息转发

如果找不到合适的方法调用,会报错 unrecognized selector sent to instance 0x100555ad0

二、方法调用源码查看

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // 创建消息接收者(receiver)
        MJPerson *person = [[MJPerson alloc] init];
        // 对象方法调用
        [person personTest];
        
        // 类方法调用
        [MJPerson initialize];
    }
    return 0;
}

和以前一样,我们使用clang看一下源码

$ xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
  • 2.1、对象方法调用 [person personTest];
// C++源码
((void (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("personTest"));

// 删除强制转换后
objc_msgSend(person, sel_registerName("personTest"));

// sel_registerName 相当于 @selector,所以,最终我们可以写成
objc_msgSend(person, @selector(personTest));
  • 2.2、类方法调用[MJPerson initialize];
// C++源码
((void (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("MJPerson"), sel_registerName("initialize"));

// 删除强制转换后
objc_msgSend(objc_getClass("MJPerson"), sel_registerName("initialize"));

// 同理,最终我们可以写成
objc_msgSend([MJPerson class], @selector(initialize))

三、objc_msgSend的执行流程

  • 3.1、消息发送阶段
李明杰-底层原理-消息发送流程.png

objc-runtime-new.mm类中的lookUpImpOrForward方法我们可以看到其实现

    // Try this class's cache.
    // 从当前类缓存中查找
    imp = cache_getImp(cls, sel);
    if (imp) goto done;


    // Try this class's method lists.
    // 从当前类方法列表中查找
    {
        Method meth = getMethodNoSuper_nolock(cls, sel);
        if (meth) {
            // 如果找到,就将它加入类的缓存中
            log_and_fill_cache(cls, meth->imp, sel, inst, cls);
            imp = meth->imp;
            goto done;
        }
    }


    // Try superclass caches and method lists.
    // 从父类的缓存中查找
    {
        unsigned attempts = unreasonableClassCount();
        // 一层一层向上查找(superClass->superSuperClass->...)
        for (Class curClass = cls->superclass;
             curClass != nil;
             curClass = curClass->superclass)
        {
            // Halt if there is a cycle in the superclass chain.
            if (--attempts == 0) {
                _objc_fatal("Memory corruption in class list.");
            }
            
            // Superclass cache.
            // 缓存父类的方法(下面顺带将父类方法缓存到自己的缓存中)
            imp = cache_getImp(curClass, sel);
            if (imp) {
                if (imp != (IMP)_objc_msgForward_impcache) {
                    // Found the method in a superclass. Cache it in this class.
                    // 将父类的方法缓存到消息接收者里面
                    log_and_fill_cache(cls, imp, sel, inst, curClass);
                    goto done;
                }
                else {
                    // Found a forward:: entry in a superclass.
                    // Stop searching, but don't cache yet; call method 
                    // resolver for this class first.
                    break;
                }
            }
            
            // Superclass method list.
            // 在父类方法列表中查找,并缓存到父类的缓存中
            Method meth = getMethodNoSuper_nolock(curClass, sel);
            if (meth) {
                log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
                imp = meth->imp;
                goto done;
            }
        }
    }
  • 3.2、动态方法解析阶段
动态方法解析
    // No implementation found. Try method resolver once.
    // 判断是否进行过动态方法解析,如果没有解析过,则执行括号中的内容
    if (resolver  &&  !triedResolver) {
        runtimeLock.unlockRead();
        _class_resolveMethod(cls, sel, inst);
        runtimeLock.read();
        // Don't cache the result; we don't hold the lock so it may have 
        // changed already. Re-do the search from scratch instead.
        // 第一次解析后标记,第二次就不会进行解析了
        triedResolver = YES;
        // 回到消息发送阶段
        goto retry;
    }

    // No implementation found, and method resolver didn't help. 
    // Use forwarding.
    // 消息转发阶段
    imp = (IMP)_objc_msgForward_impcache;
    cache_fill(cls, sel, imp, inst);

 done:
    runtimeLock.unlockRead();
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
    if (! cls->isMetaClass()) {
        // try [cls resolveInstanceMethod:sel]
        _class_resolveInstanceMethod(cls, sel, inst);
    } 
    else {
        // try [nonMetaClass resolveClassMethod:sel]
        // and [cls resolveInstanceMethod:sel]
        _class_resolveClassMethod(cls, sel, inst);
        if (!lookUpImpOrNil(cls, sel, inst, 
                            NO/*initialize*/, YES/*cache*/, NO/*resolver*/)) 
        {
            _class_resolveInstanceMethod(cls, sel, inst);
        }
    }
}
  • 3.2.1、动态方法解析具体实现一:对象方法调用解析
@interface MJPerson : NSObject

- (void)test;

@end
#import "MJPerson.h"
#import <objc/runtime.h>

@implementation MJPerson

//- (void)test {
//    NSLog(@"%s", __func__);
//}

- (void)other {
    NSLog(@"%s", __func__);
}

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

@end
#import <Foundation/Foundation.h>
#import "MJPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        [person test];
    }
    return 0;
}

2019-09-30 19:45:46.065352+0800 Test12[18214:1032252] -[MJPerson other]
  • 3.2.2、类方法调用解析
@interface MJPerson : NSObject

+ (void)test;

@end
#import "MJPerson.h"
#import <objc/runtime.h>

@implementation MJPerson

//+ (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 resolveInstanceMethod:sel];
}

@end
#import <Foundation/Foundation.h>
#import "MJPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [MJPerson test];
    }
    return 0;
}

2019-09-30 19:48:41.825602+0800 Test12[18258:1034184] c_other -MJPerson -test
  • 3.2.3、C语言函数动态解析
#import "MJPerson.h"
#import <objc/runtime.h>

@implementation MJPerson

//- (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)) {
        // 获取其他方法
        class_addMethod(self, sel, (IMP)c_other, "v16@0:8");
        // 返回yes代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

@end

  • 3.3、消息转发
消息转发流程
  • 3.3.1、消息转发(一):forwardingTargetForSelector(MJPerson 消息由 MJStudent处理)
@interface MJPerson : NSObject

- (void)test;

@end


@implementation MJPerson

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

@end
#import "MJStudent.h"

@implementation MJStudent

- (void)test {
    NSLog(@"%s", __func__);
}

@end
#import <Foundation/Foundation.h>
#import "MJPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        [person test];
    }
    return 0;
}

2019-09-30 19:59:13.819509+0800 Test12[18418:1039881] -[MJStudent test]
  • 3.3.2、消息转发(二):forwardInvocation
    MJPerson 的消息 forwardingTargetForSelector方法未能处理,进入下一步转发流程
@interface MJPerson : NSObject

- (void)test;

@end
#import "MJPerson.h"
#import "MJStudent.h"

@implementation MJPerson

- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return nil;
    }
    return [super forwardingTargetForSelector:aSelector];
}

// 方法签名:返回值、参数类型
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test)) {
        return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
    }
    return [super methodSignatureForSelector:aSelector];
}

//
/**
 NSInvocation 封装了一个方法调用,包括方法调用者、方法名、方法参数
 
 anInvocation.target 方法调用者
 anInvocation.selector 方法名
 [anInvocation getArgument:NULL atIndex:0] 方法参数
 */
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    /**
     方法一
     
     anInvocation.target = [[MJStudent alloc] init];
     [anInvocation invoke];
     */
    
    // 方法二
    [anInvocation invokeWithTarget:[[MJStudent alloc] init]];
}

@end
#import "MJStudent.h"

@implementation MJStudent

- (void)test {
    NSLog(@"%s", __func__);
}

@end
#import <Foundation/Foundation.h>
#import "MJPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        [person test];
    }
    return 0;
}

2019-09-30 20:08:02.735260+0800 Test12[18511:1043993] -[MJStudent test]

四、番外篇

  • 4.1、类方法转发到对象方法
#import "MJStudent.h"

@implementation MJStudent

+ (void)test {
    NSLog(@"%s", __func__);
}

- (void)test {
    NSLog(@"%s", __func__);
}

@end
#import "MJPerson.h"
#import "MJStudent.h"

@implementation MJPerson

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

@end
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [MJPerson test];
    }
    return 0;
}

2019-09-05 21:05:50.180158+0800 test[40754:500893] -[MJStudent test]
  • 4.2、动态实现 set、get方法
@interface MJPerson : NSObject

@property (nonatomic, assign) int age;

@end
#import "MJPerson.h"
#import <objc/runtime.h>

@implementation MJPerson

@dynamic age;

void setAge(id self, SEL _cmd, int age) {
    NSLog(@"age is %d", age);
}

int age(id self, SEL _cmd) {
    return 120;
}

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(setAge:)) {
        class_addMethod(self, sel, (IMP)setAge, "v@:I");
        return YES;
    } else if (sel == @selector(age)) {
        class_addMethod(self, sel, (IMP)age, "i@:I");
    }
    return [super resolveInstanceMethod:sel];
}

@end
#import <Foundation/Foundation.h>
#import "MJPerson.h"

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        MJPerson *person = [[MJPerson alloc] init];
        person.age = 20;
        NSLog(@"person.age is %d", person.age);
    }
    return 0;
}

2019-09-07 18:40:20.740030+0800 test[1514:27406] age is 20
2019-09-07 18:40:20.740276+0800 test[1514:27406] person.age is 120

相关文章

  • 关于Runtime 消息发送机制的延伸

    说到OC 不得不说一下OC 的消息转发机制;何为OC 的消息转发机制;其实就是这样的; Objc 在向一个对象发送...

  • OC消息机制,消息转发机制

    Runtime简称运行时,其中最主要的是消息机制 概述 C 与 OC 的不同 1.C 语言,函数的调用在编译的时候...

  • 笔记 - OC 转发消息机制

    目录 一、消息机制 二、方法调用源码查看 三、objc_msgSend的执行流程 四、番外篇 一、简要说明OC消息...

  • Runtime知识点整理1

    OC消息机制?消息转发机制流程?什么是Runtime?什么场景下使用? ==============巴拉巴拉......

  • oc消息转发机制

    一、消息转发机制 在OC中,调用一个对象的方法,实际上是给对象发了一条消息,在编译Objective-C函数调用的...

  • OC消息转发机制

    当一个对象收到它没实现的消息的时候,通常会发生如下的情况。 调用+(BOOL)resolveInstanceMet...

  • OC 消息转发机制

    首先了解几个概念:class 的定义 method的定义 消息转发本质:在运行时将方法地址(imp)和一个名字(s...

  • OC 消息转发机制。

    当调用一个 NSObject 对象不存在的方法时,并不会马上抛出异常,而是会经过多层转发,层层调用对象的-reso...

  • OC 消息转发机制

    首先我们看一下objc_msgSend它具体是如何发送消息: 首先根据receiver对象的isa指针获取它对应的...

  • OC消息转发机制

    消息转发的两大阶段 先征询接收者,所属的类,看其是否能够动态添加方法,以处理这个『未知的选择子』,这叫做动态方法解...

网友评论

      本文标题:笔记 - OC 转发消息机制

      本文链接:https://www.haomeiwen.com/subject/wkbzectx.html