首页 > 其他分享 >iOS - Runtime-消息机制-objc_msgSend()

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

时间:2024-03-27 23:33:37浏览次数:27  
标签:super void iOS ZSXPerson objc msgSend test 方法 class

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

前言

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

1. objc_msgSend执行流程

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

objc_msgSend的执行流程可以分为3大阶段

  • 消息发送

  • 动态方法解析

  • 消息转发

1.1 消息发送

  • 如果是从class_rw_t中查找方法

    1. 已经排序的,二分查找
    2. 没有排序的,遍历查找
  • receiver通过isa指针找到receiverClass

  • receiverClass通过superclass指针找到superClass

1.2 动态方法解析

1.2.1 开发者可以实现以下方法,来动态添加方法实现
  • +resolveInstanceMethod: -----用于 实例方法
  • +resolveClassMethod: -----用于类方法
1.2.2 动态解析过后,会重新走“消息发送”的流程
  • “从receiverClass的cache中查找方法”这一步开始执行
1.2.3 示例

ZSXPerson类有test方法,但是方法实现注释掉了

@interface ZSXPerson : NSObject

- (void)test;

@end

@implementation ZSXPerson

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

@end

此时我们调用 test 方法发就会报错unrecognized selector sent to instance

动态方法解析阶段给类对象增加方法实现

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

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 获取 qitafangfa
        Method method = class_getInstanceMethod(self, @selector(other));

        // 动态添加 test 方法的实现
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

将方法实现other设置为test的方法实现,这时候可以看到不会报错了,而是执行了- (void)other方法

1.2.3.1 类方法

动态方法解析类方法也是类似的,只不过用的是+resolveClassMethod:方法,并且class_addMethod应该给元类对象添加方法。使用object_getClass(self)获取元类对象

@interface ZSXPerson : NSObject

- (void)test;

+ (void)test1;

@end

@implementation ZSXPerson

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

//+ (void)test1 {
//    NSLog(@"ZSXPerson - %s", __func__);
//}

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

+ (BOOL)resolveInstanceMethod:(SEL)sel {
    if (sel == @selector(test)) {
        // 获取 qitafangfa
        Method method = class_getInstanceMethod(self, @selector(other));

        // 动态添加 test 方法的实现
        class_addMethod(self, sel, method_getImplementation(method), method_getTypeEncoding(method));

        // 返回YES代表有动态添加方法
        return YES;
    }
    return [super resolveInstanceMethod:sel];
}

+ (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

int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        ZSXPerson *person = [[ZSXPerson alloc] init];
//        [person test];

        [ZSXPerson test1];
    }
    return 0;
}

执行结果

1.3 消息转发

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

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

新建一个ZSXCat类,该类实现了- (void)test方法

@interface ZSXCat : NSObject

@end

@implementation ZSXCat

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

@end

实现forwardingTargetForSelector:方法,将消息接受者转发给ZSXCat对象

@interface ZSXPerson : NSObject

- (void)test;

@end


@implementation ZSXPerson

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

//+ (void)test1 {
//    NSLog(@"ZSXPerson - %s", __func__);
//}

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

@end

运行结果: 调用了ZSXCat- (void)test方法

1.3.2 methodSignatureForSelector:
@implementation ZSXPerson

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

//- (id)forwardingTargetForSelector:(SEL)aSelector {
//    if (aSelector == @selector(test)) {
//        return [[ZSXCat 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封装了一个方法调用,包括:方法调用者、方法签名、方法参数
// anInvocation.target  方法调用者
// anInvocation.selector  方法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
}

@end

返回方法签名,来到forwardInvocation:继续执行

2. 拓展

2.1 forwardInvocation:自定义逻辑

动态方法解析阶段,+resolveClassMethod:方法是可以给消息接受者动态增加一个`方法实现

消息转发阶段,forwardingTargetForSelector:方法是可以重新返回一个消息接受者,相当于是让另一个人来处理这个方法。

但是,来到methodSignatureForSelector:方法后,可以使用方法签名自定义更复杂的业务

2.1.1 方法签名阶段的其他用法
把方法调用 转发给ZSXCat对象
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
    // 把方法调用 转发给ZSXCat对象
    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];
}
使用参数

方法增加age参数- (void)test:(int)age

调用时传入参数:

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

方法签名使用参数

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

// NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
// anInvocation.target  方法调用者
// anInvocation.selector  方法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
    // 把方法调用 转发给ZSXCat对象
//    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];

    // 取出参数。参数顺序:receiver、selector、other arguments
    int age;
    [anInvocation getArgument:&age atIndex:2];
    NSLog(@"age is %d", age + 10);
}

打印结果:

2.2 消息转发 - 类方法

在处理消息转发的时候,会发现如果forwardingTargetForSelectormethodSignatureForSelector方法,使用+开头写法时代码提示没有这俩方法,所以有的人认为消息转发不支持类方案

其实是支持的,把方法的-号改成+即可:

@interface ZSXPerson : NSObject

- (void)test:(int)age;

+ (void)test1;

@end
@implementation ZSXCat

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

+ (void)test1 {
    NSLog(@"ZSXCat - %s", __func__);
}

@end

main.m

int main(int argc, const char * argv[]) {
    @autoreleasepool {
//        ZSXPerson *person = [[ZSXPerson alloc] init];
//        [person test:10];

        [ZSXPerson test1];
    }
    return 0;
}

ZSXPerson.m

@implementation ZSXPerson

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

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

//+ (id)forwardingTargetForSelector:(SEL)aSelector {
//    if (aSelector == @selector(test1)) {
//        return [ZSXCat class];
//    }
//    return [super forwardingTargetForSelector:aSelector];
//}

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

+ (NSMethodSignature *)methodSignatureForSelector:(SEL)aSelector {
    if (aSelector == @selector(test1)) {
        return [NSMethodSignature signatureWithObjCTypes:"v@:"]; //v16@0:8 可简写为 v@:
    }
    return [super methodSignatureForSelector:aSelector];
}

// NSInvocation封装了一个方法调用,包括:方法调用者、方法签名、方法参数
// anInvocation.target  方法调用者
// anInvocation.selector  方法名
// [anInvocation getArgument:NULL atIndex:0]  参数
- (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
//    // 把方法调用 转发给ZSXCat对象
//    [anInvocation invokeWithTarget:[[ZSXCat alloc]init]];

    // 取出参数。参数顺序:receiver、selector、other arguments
//    int age;
//    [anInvocation getArgument:&age atIndex:2];
//    NSLog(@"age is %d", age + 10);
}

+ (void)forwardInvocation:(NSInvocation *)anInvocation {
    NSLog(@"---- forwardInvocation");
    // 把方法调用 转发给ZSXCat对象
    [anInvocation invokeWithTarget:[ZSXCat class]];
}

@end

2.3 super

2.3.1 示例代码

ZSXStudent继承于ZSXPersonZSXPerson继承于NSObject。如下代码打印结果是什么

ZSXPerson.h

@interface ZSXPerson : NSObject

@end

ZSXStudent.h

@interface ZSXStudent : ZSXPerson

@end

ZSXStudent.m

@implementation ZSXStudent

- (instancetype)init {
    if (self = [super init]) {
        NSLog(@"[self class] - %@", [self class]);
        NSLog(@"[self superclass] - %@", [self superclass]);

        NSLog(@"--------------------------------");

        NSLog(@"[super class] - %@", [super class]);
        NSLog(@"[super superclass] - %@", [super superclass]);
    }
    return self;
}

@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是第一个要搜索的类

superself相比,它们消息接受者都是selfsuper会多传一个super_class,表示从super_class开始查找。

2.3.3 分析

ZSXStudent中,执行[super class],相当于执行这句:

objc_msgSendSuper({self, [ZSXPerson class]}, @selector(class));

表示:

  • 消息接收者:self
  • ZSXPerson类对象开始
  • 查找调用class方法

class方法存在于NSObject中的,此时不管从ZSXStudent开始查找,还是从ZSXPerson开始查找,最终都到NSObject才找到方法

class方法实现如下:

[super class][self class]他们的消息接受者都是self,也就是ZSXStudent,因此他们打印结果都是ZSXStudentsuperclass则都是ZSXPerson

@oubijiexi

标签:super,void,iOS,ZSXPerson,objc,msgSend,test,方法,class
From: https://blog.csdn.net/sharp521/article/details/137092967

相关文章

  • 【全开源】JAVA海外短剧国际版源码支持H5+Android+IOS_博纳软云
       在数字化快速发展的今天,海外短剧市场日益繁荣,成为了全球娱乐界的新宠。为了满足广大用户的需求,我们推出了一款基于JAVA开发的海外短剧国际版源码,支持H5、Android和IOS三大平台,让您轻松进军海外短剧市场。这款源码采用了JAVA语言进行开发,具有高度的可定制......
  • 【全开源】JAVA海外短剧国际版源码支持H5+Android+IOS_博纳软云
       在数字化快速发展的今天,海外短剧市场日益繁荣,成为了全球娱乐界的新宠。为了满足广大用户的需求,我们推出了一款基于JAVA开发的海外短剧国际版源码,支持H5、Android和IOS三大平台,让您轻松进军海外短剧市场。这款源码采用了JAVA语言进行开发,具有高度的可定制......
  • 【全开源】JAVA海外短剧国际版源码支持H5+Android+IOS_博纳软云
       在数字化快速发展的今天,海外短剧市场日益繁荣,成为了全球娱乐界的新宠。为了满足广大用户的需求,我们推出了一款基于JAVA开发的海外短剧国际版源码,支持H5、Android和IOS三大平台,让您轻松进军海外短剧市场。这款源码采用了JAVA语言进行开发,具有高度的可定制......
  • uniapp开发iOS——Xcode无法运行到运行真机提示 Executable Path is a Directory
    软件版本:Xcode14模拟器型号:Apple14Pro真机型号:Apple6sPlus异常描述:模拟器都能正常运行,Apple6sPlus运行就报错如下:解决方法:TARGET->BuildSettings->Architectures->ExcludeArchitectures里面把arm64都删掉,重新building就好了。注:删除这两个配置的时候双击会出现......
  • iOS组件化开发之私有库
    0、了解iOS组件化1、制作开源组件库预备工作:1、安装cocoapods2、准备github账号,gitee账号,和cocoapod账号其中github,gitee账号直接在线创建即可。而注册cocoapods账号需要的终端命令:[email protected]"xxx"然后在邮箱里找到验证链接,登录一下即可。podtrunk......
  • 15进程ps、主机信息top、磁盘信息iostat、网络状态sar
    进程什么是进程进程是指程序在操作系统内运行后被注册为系统内的一个进程,并拥有独立的进程程序运行在操作系统中,是被操作系统所管理的为管理运行的程序,每一个程序在运行的时候,便被操作系统注册为系统中的一个:进程并会为每一个进程都分配一个独有的:进程ID(进程号)查看进程可以......
  • iOS开发优势解析,费用探究以及软件开发详解
    摘要本文探讨了iOS开发的优势、费用以及软件开发方面的相关内容。通过分析iOS开发所采用的编程语言、开发环境、用户界面设计、应用审核流程以及应用领域等方面,展示了iOS开发的诸多优势和特点。虽然iOS开发具有高用户体验、统一的硬件和软件环境、良好的市场份额等优势,但也存在着......
  • 深入探讨iOS开发:从创建第一个iOS程序到纯代码实现全面解析
    iOS开发作为移动应用开发的重要领域之一,对于开发人员具有重要意义。本文将深入探讨iOS开发的各个方面,从创建第一个iOS程序到纯代码实现iOS开发,带领读者全面了解iOS应用程序的开发流程和技术要点。 ......
  • 怎么制作iOS证书
    ​ 首先我们登录appuploder官网搜索appuploder第一个就是我们官网啦,网址是:Appuploaderhome--Atoolimproveiosdevelopefficiencysuchassubmitipatoappstoreandmanageioscertificate可以跨平台开发,无论是Windows还是Mac都可以使用。   ​我们现在......
  • iOS、Android获取apk公钥MD5信息
    背景国家工信部规定,所有国内在线或即将上线的APP做备案,否则无法通过域名访问。其中iOS和Android备案所需的APP相关信息我们不能直接明文获取,有公钥,MD5,包名,APP名,接口服务的域名等对于iOS端,如果是开发者,直接在苹果的证书管理网站上下载证书到本地打开,即可获得。参考对于Androi......