inBuilder今日分享丨Object-C消息转发与发送机制

文章深入探讨了Objective-C的运行时机制,包括方法调用的过程,如objc_msgSend函数的使用,以及在类对象和实例对象中查找方法的流程。同时,详细解释了消息转发的三个阶段,如何通过resolveInstanceMethod、forwardingTargetForSelector和forwardInvocation来处理未找到的方法,展示了Objective-C作为动态语言的灵活性和错误处理能力。
摘要由CSDN通过智能技术生成

Objective-C 是一门动态语言,它将很多静态语言在编译和链接时期做的事情,推迟到了运行时来处理。当我们调用一个方法时会通过运行时(Runtime)来绑定、分发。它的运行时(Runtime)实现了对类、方法、成员变量、属性等信息的动态管理,给我们在编码时极大的灵活性。结合出现的问题,分享下运行时中的方法的调用、拦截过程。

本篇中用的到Runtime函数发送消息 objc_msgSend(receiver, selector, arg1, arg2, ...)获取类对象 object_getClass(id _Nullable obj)获取父类 class_getSuperclass(Class _Nullable cls)获取方法IMPclass_getMethodImplementation(Class _Nullable cls, SEL _Nonnull name)

类的定义

struct objc_class : objc_object {
    Class superclass; 
    cache_t cache;             
    class_data_bits_t bits;    
    class_rw_t *data() const {return bits.data();}
 }
 //class_rw_t
 struct class_rw_t {
     const method_array_t methods() const {
         auto v = get_ro_or_rwe();
         if (v.is<class_rw_ext_t *>()) {
             return v.get<class_rw_ext_t *>()->methods;
         } else {
             return method_array_t{v.get<const class_ro_t *>()->baseMethods()};
         }
      }
      //本篇只讨论method省略其他.....
 }

消息发送(方法调用)

OC中方法的调用实际会转化为objc_msgsend消息进行发送如:

@interface TestClass: NSObject 
//实例方法
- (void)printInstanceInfo; 
//类方法
+ (void)printInfo;
@end

@implementation TestClass
 - (void)printInstanceInfo { 
     NSLog(@"%s",__func__); 
 } 
 + (void)printInfo {
      NSLog(@"%s",__func__); 
 }
 @end

//调用
TestClass *test = [[TestClass alloc] init];
[test printInstanceInfo];
[TestClass printInfo];

上面的方法调用实际会转换为:

```objectivec
TestClass *test = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("TestClass"), sel_registerName("alloc"));
 ((void (*)(id, SEL))(void *)objc_msgSend)((id)test, sel_registerName("printInstanceInfo"));

当转换为此结构后才会进入消息的查找发送流程。

方法查询流程当调用到objc_msgSend(receiver, selector, arg1, arg2, ...)函数时,系统会首先判断接受者receiver是否为nil,如果nil则直接结束消息发送的流程。实际中当接受者或者对象为nil时,调用方法时应用程序不会出现崩溃。

receiver不为nil时,则会根据实例对象的isa指针找到类对象,在类对象的方法缓存cache中查找。如果查询到则直接调用,如果未查找到则会在类方法列表methods中查找。如果在该类对象的方法中查找到则会存储到cache中,然后再进行调用。

如果在当前类中未查询到,则会通过当前类的superclass指针找到类对象的父类,在类对象父类中查找。当在父类中查找到后,后首先在父类的cache中查找,如果找到则缓存在当前类的cache中,然后再调用。

如果未查询到继续在父类的methods中查找,如果找到了,则会把该方法缓存到当前类的cache中,然后再调用此方法。

如果在父类中没有找到则继续根据superclass向上查找,直到nil

如果到了nil依旧没有找到方法,则会触发消息转发。

注意:实例方法与类方法类似,只不过实例方法在类对象中查找,类方法在元类对象中查找。实际的查找过程经过了苹果的优化,比如方法存放是散列表中,查询通过二分法进行,缓存则根据调用的频率进行排序等等。

专有词说明

通过上面的流程我们需要了解几个名词:实例对象(instance):开发者创建,其中的isa指针指向类对象类对象(class):存储实例对象的方法、属性、协议等内容,superclass指向父类并最终指向nil,isa指向元类并最终指向NSObject, NSObjectisa指针指向自身。元类对象(meta):存储类对象内容,其superclass指向父元类,最终指向nil。

通过张关系图可以更明确三者之间的关系:
在这里插入图片描述

消息转发(方法拦截)

如果在各类中未找到方法(SEL), 提供会给开发者提供三次挽救的机会,如果最终没有补救程序会崩溃。

第一次回调

+ (BOOL)resolveInstanceMethod:(SEL)sel;//(实例方法)
+ (BOOL)resolveClassMethod:(SEL)sel;//(类方法)

此时系统会根据方法类型回调到实例方法或类方法中。我们可以此时进行重新板顶 并返回YES。如果未绑定成功或返回NO则会进入到第二次补救。比如我们调用了TestClass中不存在的方法printInstanceAbsent, 此时我们通过此机会可以转发到已实现的printInstanceInfo

示例代码:

//TestClass2.m中
@interface TestClass2 : NSObject
@end
@implementation TestClass2
- (void)printInstanceAbsent {
     NSLog(@"%s",__func__); 
}
@end

@implementation TestClass
- (void)printInstanceInfo {
    NSLog(@"%s",__func__);
}
//使用实例方法进行演示
+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(printInstanceInfo)) {
        class_addMethod([self class],sel,class_getMethodImplementation([TestClass2 class], @selector(printInstanceAbsent)),"v");
        return NO;
    }
    return [super resolveInstanceMethod:sel];
}
//打印
//-[TestClass2 printInstanceAbsent]
@end

第二次回调

如果上述最终返回NO,则回调到此方法。此方法要求返回一个id,允许把当前类的方法转发到其他类中,由其他类中同名同参接收

- (id)forwardingTargetForSelector:(SEL)aSelector;
//TestClass2.m中
@interface TestClass2 : NSObject
@end
@implementation TestClass2
- (void)printInstanceAbsent {
     NSLog(@"%s",__func__); 
}
@end
@implementation TestClass
- (void)printInstanceInfo {
    NSLog(@"%s",__func__);
}
// printInstanceAbsent,TestClass中未实现,而TestClass2已实现
- (id)forwardingTargetForSelector:(SEL)aSelector {
    if (aSelector == @selector(printInstanceAbsent:)) {
        return [TestClass2 new];
    }
    return [super forwardingTargetForSelector:aSelector];
}
//打印
//-[TestClass2 printInstanceAbsent]
@end

第三次回调

此步骤有两个回调方法,methodSignatureForSelector返回方法签名,forwardInvocation返回方法具体实现。只有当返回方法签名后才会执行方法实现。此步骤更加灵活,且可以返回到不同类中。如果此步骤未实现或未返回最终时间,则程序crash

//TestClass2.m中
@interface TestClass2 : NSObject
@end
@implementation TestClass2
- (void)printInstanceAbsent {
     NSLog(@"%s",__func__); 
}
@end

//TestClass3.m中
@interface TestClass3 : NSObject
@end
@implementation TestClass3
- (void)printInstanceAbsent {
     NSLog(@"%s",__func__); 
}
@end

@implementation TestClass
- (void)printInstanceInfo {
    NSLog(@"%s",__func__);
}
- (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(printInstanceAbsent)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"];
    }
    return [super methodSignatureForSelector:aSelector];
}

- (void)forwardInvocation:(NSInvocation *)anInvocation {
    TestClass2 *test2 = [TestClass2 new];
    TestClass3 *test3 = [TestClass3 new];
    if ([test2 respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:test2];
    }
    if ([test3 respondsToSelector:anInvocation.selector]) {
        [anInvocation invokeWithTarget:test3];
    }
}

//打印
// -[TestClass2 printInstanceAbsent]
// -[TestClass3 printInstanceAbsent]
@end

以上就是整个消息转发的过程,充分展现了Runtime动态语言的特性。可能此处有疑问的是方法签名[NSMethodSignature signatureWithObjCTypes:“v@:”];中为什么是v@:,在这里做个简要说明,以本篇示例中的printInstanceAbsent

v:当前方法返回值为void

@:一个id类型的对象

::对应的方法SEL根据调用的方法变化,具体描述的格式详见Apple编码规则

最终通过方法拦截解决了通过js调用原生方法时App崩溃问题。

最后插个安利:inBuilder 低代码平台开源社区版,免费下载,免费使用,欢迎体验:inBuilder社区

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值