iOS - Runtime-消息机制-objc_msgSend()

十念  金牌会员 | 2024-6-21 13:12:53 | 显示全部楼层 | 阅读模式
打印 上一主题 下一主题

主题 798|帖子 798|积分 2394

iOS - Runtime-消息机制-objc_msgSend()

前言

本章重要先容消息机制-objc_msgSend的执行流程,分为消息发送、动态方法解析、消息转发三个阶段,每个阶段可以做什么。还先容了super的本质是什么,如何调用的
1. objc_msgSend执行流程

OC中的方法调用,其实都是转换为objc_msgSend函数的调用
objc_msgSend的执行流程可以分为3大阶段


  • 消息发送
  • 动态方法解析
  • 消息转发
1.1 消息发送




  • 假如是从class_rw_t中查找方法

    • 已经排序的,二分查找
    • 没有排序的,遍历查找

  • receiver通过isa指针找到receiverClass
  • receiverClass通过superclass指针找到superClass
1.2 动态方法解析


1.2.1 开发者可以实现以下方法,来动态添加方法实现



  • +resolveInstanceMethod: -----用于 实例方法
  • +resolveClassMethod: -----用于类方法
1.2.2 动态解析事后,会重新走“消息发送”的流程



  • “从receiverClass的cache中查找方法”这一步开始执行
1.2.3 示例

ZSXPerson类有test方法,但是方法实现注释掉了
  1. @interface ZSXPerson : NSObject
  2. - (void)test;
  3. @end
  4. @implementation ZSXPerson
  5. //- (void)test {
  6. //    NSLog(@"ZSXPerson - %s", __func__);
  7. //}
  8. @end
复制代码
此时我们调用 test 方法发就会报错unrecognized selector sent to instance

在动态方法解析阶段给类对象增长方法实现
  1. - (void)other {
  2.     NSLog(@"ZSXPerson - %s", __func__);
  3. }
  4. + (BOOL)resolveInstanceMethod:(SEL)sel {
  5.     if (sel == @selector(test)) {
  6.         // 获取 qitafangfa
  7.         Method method = class_getInstanceMethod(self, @selector(other));
  8.         // 动态添加 test 方法的实现
  9.         class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
  10.         // 返回YES代表有动态添加方法
  11.         return YES;
  12.     }
  13.     return [super resolveInstanceMethod:sel];
  14. }
复制代码
将方法实现other设置为test的方法实现,这时候可以看到不会报错了,而是执行了- (void)other方法

1.2.3.1 类方法

动态方法解析类方法也是雷同的,只不外用的是+resolveClassMethod:方法,并且class_addMethod应该给元类对象添加方法。使用object_getClass(self)获取元类对象
  1. @interface ZSXPerson : NSObject- (void)test;+ (void)test1;@end@implementation ZSXPerson//- (void)test {//    NSLog(@"ZSXPerson - %s", __func__);//}//+ (void)test1 {//    NSLog(@"ZSXPerson - %s", __func__);//}- (void)other {
  2.     NSLog(@"ZSXPerson - %s", __func__);
  3. }
  4. + (BOOL)resolveInstanceMethod:(SEL)sel {
  5.     if (sel == @selector(test)) {
  6.         // 获取 qitafangfa
  7.         Method method = class_getInstanceMethod(self, @selector(other));
  8.         // 动态添加 test 方法的实现
  9.         class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));
  10.         // 返回YES代表有动态添加方法
  11.         return YES;
  12.     }
  13.     return [super resolveInstanceMethod:sel];
  14. }+ (BOOL)resolveClassMethod:(SEL)sel {    if (sel == @selector(test1)) {        // 获取 qitafangfa        Method method = class_getInstanceMethod(self, @selector(other));        // 动态添加 test 方法的实现        class_addMethod(object_getClass(self), sel, method_getImplementation(method), method_getTypeEncoding(method));        // 返回YES代表有动态添加方法        return YES;    }    return [super resolveClassMethod:sel];}@end
复制代码
main.m
  1. int main(int argc, const char * argv[]) {
  2.     @autoreleasepool {
  3. //        ZSXPerson *person = [[ZSXPerson alloc] init];
  4. //        [person test];
  5.         [ZSXPerson test1];
  6.     }
  7.     return 0;
  8. }
复制代码
执行结果

1.3 消息转发


假如动态方法解析阶段没有处理,返来到消息转发阶段


  • 首先来到forwardingTargetForSelector:方法,该方法中可以重新返回一个消息接收者,步伐将会重新执行objc_msgSend()方法,此时消息时发送给新的担当者
  • 假如forwardingTargetForSelector:方法没有处理,会来到methodSignatureForSelector:方法,该方法可以返回一个方法署名,返回后,步伐会继续调用forwardInvocation:方法。假如methodSignatureForSelector:方法也没处理,步伐就抛出非常
  • 在forwardInvocation:方法中,开发者可以自定义任何逻辑
  • 以上方法都有对象方法、类方法2个版本(前面可以是加号+,也可以是减号-)
1.3.1 forwardingTargetForSelector:

新建一个ZSXCat类,该类实现了- (void)test方法
  1. @interface ZSXCat : NSObject
  2. @end
  3. @implementation ZSXCat
  4. - (void)test {
  5.     NSLog(@"ZSXCat - %s", __func__);
  6. }
  7. @end
复制代码
实现forwardingTargetForSelector:方法,将消息担当者转发给ZSXCat对象
  1. @interface ZSXPerson : NSObject
  2. - (void)test;
  3. @end
  4. @implementation ZSXPerson
  5. //- (void)test {
  6. //    NSLog(@"ZSXPerson - %s", __func__);
  7. //}
  8. //+ (void)test1 {
  9. //    NSLog(@"ZSXPerson - %s", __func__);
  10. //}
  11. - (id)forwardingTargetForSelector:(SEL)aSelector {
  12.     if (aSelector == @selector(test)) {
  13.         return [[ZSXCat alloc] init];
  14.     }
  15.     return [super forwardingTargetForSelector:aSelector];
  16. }
  17. @end
复制代码
运行结果:
调用了ZSXCat的- (void)test方法
1.3.2 methodSignatureForSelector:

  1. @implementation ZSXPerson
  2. //- (void)test {
  3. //    NSLog(@"ZSXPerson - %s", __func__);
  4. //}
  5. //- (id)forwardingTargetForSelector:(SEL)aSelector {
  6. //    if (aSelector == @selector(test)) {
  7. //        return [[ZSXCat alloc] init];
  8. //    }
  9. //    return [super forwardingTargetForSelector:aSelector];
  10. //}
  11. // 方法签名:返回值类型、参数类型
  12. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  13.     if (aSelector == @selector(test)) {
  14.         return [NSMethodSignature signatureWithObjCTypes:"v16@0:8"];
  15.     }
  16.     return [super methodSignatureForSelector:aSelector];
  17. }
  18. // NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
  19. // anInvocation.target  方法调用者
  20. // anInvocation.selector  方法名
  21. // [anInvocation getArgument:NULL atIndex:0]  参数
  22. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  23.     NSLog(@"---- forwardInvocation");
  24. }
  25. @end
复制代码
返回方法署名,来到forwardInvocation:继续执行

2. 拓展

2.1 forwardInvocation:自定义逻辑

在动态方法解析阶段,+resolveClassMethod:方法是可以给消息担当者动态增长一个`方法实现
在消息转发阶段,forwardingTargetForSelector:方法是可以重新返回一个消息担当者,相当于是让另一个人来处理这个方法。
但是,来到methodSignatureForSelector:方法后,可以使用方法署名自定义更复杂的业务
2.1.1 方法署名阶段的其他用法

把方法调用 转发给ZSXCat对象

  1. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  2.     NSLog(@"---- forwardInvocation");
  3.     // 把方法调用 转发给ZSXCat对象
  4.     [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
  5. }
复制代码
使用参数

方法增长age参数- (void)testint)age
调用时传入参数:
  1. int main(int argc, const char * argv[]) {
  2.     @autoreleasepool {
  3.         ZSXPerson *person = [[ZSXPerson alloc] init];
  4.         [person test:10];
  5.     }
  6.     return 0;
  7. }
复制代码
方法署名使用参数
  1. // 方法签名:返回值类型、参数类型
  2. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  3.     if (aSelector == @selector(test:)) {
  4.         return [NSMethodSignature signatureWithObjCTypes:"i24@0:8i16"];
  5.     }
  6.     return [super methodSignatureForSelector:aSelector];
  7. }
  8. // NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
  9. // anInvocation.target  方法调用者
  10. // anInvocation.selector  方法名
  11. // [anInvocation getArgument:NULL atIndex:0]  参数
  12. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  13.     NSLog(@"---- forwardInvocation");
  14.     // 把方法调用 转发给ZSXCat对象
  15. //    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
  16.     // 取出参数。参数顺序:receiver、selector、other arguments
  17.     int age;
  18.     [anInvocation getArgument:&age atIndex:2];
  19.     NSLog(@"age is %d", age + 10);
  20. }
复制代码
打印结果:

2.2 消息转发 - 类方法

在处理消息转发的时候,会发现假如forwardingTargetForSelector和methodSignatureForSelector方法,使用+开头写法期间码提示没有这俩方法,所以有的人以为消息转发不支持类方案
其实是支持的,把方法的-号改成+即可:
  1. @interface ZSXPerson : NSObject
  2. - (void)test:(int)age;
  3. + (void)test1;
  4. @end
复制代码
  1. @implementation ZSXCat
  2. - (void)test {
  3.     NSLog(@"ZSXCat - %s", __func__);
  4. }
  5. + (void)test1 {
  6.     NSLog(@"ZSXCat - %s", __func__);
  7. }
  8. @end
复制代码
main.m
  1. int main(int argc, const char * argv[]) {
  2.     @autoreleasepool {
  3. //        ZSXPerson *person = [[ZSXPerson alloc] init];
  4. //        [person test:10];
  5.         [ZSXPerson test1];
  6.     }
  7.     return 0;
  8. }
复制代码
ZSXPerson.m
  1. @implementation ZSXPerson
  2. //- (void)test:(int)age {
  3. //    NSLog(@"ZSXPerson - %s", __func__);
  4. //}
  5. - (id)forwardingTargetForSelector:(SEL)aSelector {
  6.     if (aSelector == @selector(test)) {
  7.         return [[ZSXCat alloc] init];
  8.     }
  9.     return [super forwardingTargetForSelector:aSelector];
  10. }
  11. //+ (id)forwardingTargetForSelector:(SEL)aSelector {
  12. //    if (aSelector == @selector(test1)) {
  13. //        return [ZSXCat class];
  14. //    }
  15. //    return [super forwardingTargetForSelector:aSelector];
  16. //}
  17. // 方法签名:返回值类型、参数类型
  18. - (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  19.     if (aSelector == @selector(test:)) {
  20.         return [NSMethodSignature signatureWithObjCTypes:"i24@0:8i16"];
  21.     }
  22.     return [super methodSignatureForSelector:aSelector];
  23. }
  24. + (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
  25.     if (aSelector == @selector(test1)) {
  26.         return [NSMethodSignature signatureWithObjCTypes:"v@:"]; //v16@0:8 可简写为 v@:
  27.     }
  28.     return [super methodSignatureForSelector:aSelector];
  29. }
  30. // NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
  31. // anInvocation.target  方法调用者
  32. // anInvocation.selector  方法名
  33. // [anInvocation getArgument:NULL atIndex:0]  参数
  34. - (void)forwardInvocation:(NSInvocation *)anInvocation {
  35.     NSLog(@"---- forwardInvocation");
  36. //    // 把方法调用 转发给ZSXCat对象
  37. //    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
  38.     // 取出参数。参数顺序:receiver、selector、other arguments
  39. //    int age;
  40. //    [anInvocation getArgument:&age atIndex:2];
  41. //    NSLog(@"age is %d", age + 10);
  42. }
  43. + (void)forwardInvocation:(NSInvocation *)anInvocation {
  44.     NSLog(@"---- forwardInvocation");
  45.     // 把方法调用 转发给ZSXCat对象
  46.     [anInvocation invokeWithTarget:[ZSXCat class]];
  47. }
  48. @end
复制代码
2.3 super

2.3.1 示例代码

ZSXStudent继续于ZSXPerson,ZSXPerson继续于NSObject。如下代码打印结果是什么
ZSXPerson.h
  1. @interface ZSXPerson : NSObject
  2. @end
复制代码
ZSXStudent.h
  1. @interface ZSXStudent : ZSXPerson
  2. @end
复制代码
ZSXStudent.m
  1. @implementation ZSXStudent
  2. - (instancetype)init {
  3.     if (self = [super init]) {
  4.         NSLog(@"[self class] - %@", [self class]);
  5.         NSLog(@"[self superclass] - %@", [self superclass]);
  6.         NSLog(@"--------------------------------");
  7.         NSLog(@"[super class] - %@", [super class]);
  8.         NSLog(@"[super superclass] - %@", [super superclass]);
  9.     }
  10.     return self;
  11. }
  12. @end
复制代码
打印结果:
示例中,比力轻易混淆的是[super class]、[super superclass],他们的打印结果和[self class]、[self superclass]是一样的。
2.3.2 super本质

super调用时,底层会转换为objc_msgSendSuper2函数的调用,接收2个参数


  • struct objc_super
  • SEL
objc_super结构体:



  • receiver是消息接收者
  • super_class是第一个要搜索的类
super与self相比,它们消息担当者都是self,super会多传一个super_class,表示从super_class开始查找。
2.3.3 分析

ZSXStudent中,执行[super class],相当于执行这句:
  1. objc_msgSendSuper({self, [ZSXPerson class]}, @selector(class));
复制代码
表示:


  • 消息接收者:self
  • 从ZSXPerson类对象开始
  • 查找调用class方法
class方法存在于NSObject中的,此时不管从ZSXStudent开始查找,还是从ZSXPerson开始查找,最终都到NSObject才找到方法
class方法实现如下:

[super class]和[self class]他们的消息担当者都是self,也就是ZSXStudent,因此他们打印结果都是ZSXStudent。superclass则都是ZSXPerson
   @oubijiexi

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

十念

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表