底层原理:Runtime之Objc_msgSend用法

2022-05-02  本文已影响0人  飘摇的水草

objc_msgSend用法

Person *person = [[Person alloc] init];
[person personTest];
// objc_msgSend(person, @selector(personTest));
// 消息接收者(receiver):person
// 消息名称:personTest
        
[Person initialize];
// objc_msgSend([MJPerson class], @selector(initialize));
// 消息接收者(receiver):[MJPerson class]
// 消息名称:initialize

objc_msgSend流程解析

  1. 在消息发送阶段会去尝试找到这个方法来进行调用,如果在当前类的方法缓存中查找,如果找不到则去它的 rw_t 缓存中查找,如果当前类没找到,则去父类中找,如果找到则直接调用,就不会进行动态方法解析,否则会进行动态方法解析(这两个方法中的其中一个:resolveInstanceMethodresolveClassMethod),允许开发者动态去创建一个新的方法,如果动态方法解析没有处理,则进入消息转发阶段,有可能会转发给另外一个对象去调用,最终完成方法调用,如果最终这三个步骤都没有找到则会报一个:unrecognized selector sent to instance

objc_msgSend执行流程01-消息发送:

消息发送流程总结.jpeg

如果在自己的方法缓存和父类的方法缓存里没有找到方法,则就会进入动态方法解析

2. 动态方法解析

完整的代码实现如下:

@implementation Person

struct method_t
{
    SEL sel;
    char *types;
    IMP imp;
};

+ (BOOL)resolveInstanceMethod:(SEL)sel
{
    if (sel == @selector(test))
    {
        //第一种做法
        //获取其他方法
//        struct method_t *method = (struct method_t *)class_getInstanceMethod(self, @selector(other));
//        //动态添加test方法的实现
//        class_addMethod(self, sel, method -> imp, method->types);

        //第二种做法
        Method otherMethod = class_getInstanceMethod(self, @selector(other));
        //动态添加方法的实现,IMP表示方法的实现
        class_addMethod(self, sel, method_getImplementation(otherMethod), method_getTypeEncoding(otherMethod));
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

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

上面的代码表示如果没有找到 test 方法则就会调用 other 方法

3. 消息转发

代码如下:

@implementation Person
 - (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test))
    {
        return [[Cat alloc]init];
    }
    return [super forwardingTargetForSelector:aSelector];
}
@end

上面的代码Person类不处理test方法而将其转发给Cat类处理,Cat类里的代码如下:

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

此时当Person对象调用test方法时,最终会调用Cat类的test方法。

//如果是类方法则需要将下面的方法变为+号
//methodSignatureForSelector也需要改为+号
- (id)forwardingTargetForSelector:(SEL)aSelector
{
    if (aSelector == @selector(test))
    {
        return nil;
        //return [[Cat alloc]init];
    }
    return [super forwardingTargetForSelector:aSelector];
}

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

// NSInvocation封装了一个方法调用,包括:方法调用者、方法、方法参数,其中Invocation.target为方法的调用者,Invocation.selector为方法名,Invocation.getArgument为获取方法参数
- (void)forwardInvocation:(NSInvocation *)anInvocation
{
//    anInvocation.target = [[Cat alloc]init];
//    [anInvocation invoke];
    //或
    [anInvocation invokeWithTarget:[[Cat alloc]init] ];
}

完整的动态解析和消息转发流程如下图所示:

动态解析和消息转发.png
dynamic用法
@dynamic age;

上面的这句代码是提醒编译器不要自动生成 getset 方法,也不要自动生成成员变量


面试题
上一篇 下一篇

猜你喜欢

热点阅读