http://www.jianshu.com/p/95c8cb186673

在OC中,我们对方法的调用都会被转换成内部的消息发送执行对objc_msgSend方法的调用,掌握好消息发送,可以让我们在编程中更方便灵活。

首先来看下方法定义:

/**  定义:'为某个类对象发送消息,并且返回一个值'
参数1: 消息接收的对象实例
参数2: 要执行的方法
...: 一系列其他参数 */
id objc_msgSend(id self, SEL op, ...)

这里有官方文档的解释

我们创建一个MessageSendTest文件,在.m文件中定义四个方法用于测试:

// 无参数 无返回值
- (void)noArgumentsAndNoReturnValue
{
NSLog(@"方法名:%s", __FUNCTION__);
} // 带一个参数 无返回值
- (void)hasArguments:(NSString *)arg
{
NSLog(@"方法名:%s, 参数:%@", __FUNCTION__, arg);
} // 无参数 有返回值
- (NSString *)noArgumentsButReturnValue
{
NSLog(@"方法名:%s, 返回值:%@", __FUNCTION__, @"不带参数,但是带有返回值");
return @"不带参数,但是带有返回值";
} // 带两个参数 有返回值
- (int)hasArguments:(NSString *)arg andReturnValue:(int)arg1
{
NSLog(@"方法名:%s, 参数:%@, 返回值:%d", __FUNCTION__, arg, arg1);
return arg1;
}

然后我们再定义一个测试方法:

+ (void)test
{
}

在测试方法里边,我们:

  • 调用无参无返回值方法

      // 1、创建对象
    // 给'MessageSendTest'类发送消息,创建对象,这句话等同于 MessageSendTest *test = [MessageSendTest alloc];
    MessageSendTest *test = ((MessageSendTest * (*)(id,SEL)) objc_msgSend)((id)[MessageSendTest class], @selector(alloc)); // 2、初始化对象
    // 给'test'对象发送消息进行初始化,这句话等同于 [test init];
    test = ((MessageSendTest *(*)(id,SEL))objc_msgSend)((id)test, @selector(init));
    NSLog(@"test:%@", test); // 3、调用无参无返回值方法
    ((void(*)(id,SEL))objc_msgSend)((id)test, @selector(noArgumentsAndNoReturnValue));

    从上边三行代码我们不难看出,每次给对象发送消息,objc_msgSend都至少要带有(id, SEL)两个参数,其中'1'和'2'里边返回值类型为MessageSendTest *类型,'3'里边返回值类型为void类型。这样我们就创建了一个MessageSendTest的对象,并且调用了noArgumentsAndNoReturnValue方法

    打印结果:(由打印结果可见,我们已经达到了预期目的)

      2016-07-04 15:59:46.543 ZFRuntime[1378:216191] test:<MessageSendTest: 0x7fafe0c733d0>
    2016-07-04 15:59:46.543 ZFRuntime[1378:216191] 方法名:-[MessageSendTest noArgumentsAndNoReturnValue]
  • 调用带一个参数但无返回值的方法

      // 4、调用带一个参数但无返回值的方法
    ((void(*)(id,SEL,NSString *))objc_msgSend)((id)test, @selector(hasArguments:), @"带一参数但是没有返回值");

    相比于上边的方法,这里我们在 = 左边多了个NSString@"带一参数但是没有返回值",这就是我们要给这个方法传递的参数

    打印结果:(由打印结果可见,我们已经达到了预期目的)

      2016-07-04 16:10:24.800 ZFRuntime[1415:225374] 方法名:-[MessageSendTest hasArguments:], 参数:带一参数但是没有返回值
  • 调用带返回值,但是不带参数的方法

      NSString *returnStr = ((NSString * (*) (id, SEL)objc_msgSend))((id)test, @selector(noArgumentsButReturnValue));
    NSLog(@"5. 返回值为:%@", reuturnStr);

    打印结果:(由打印结果可见,我们已经达到了预期目的)

      2016-07-04 16:13:53.764 ZFRuntime[1434:229571] 方法名:-[MessageSendTest noArgumentsButReturnValue], 返回值:不带参数,但是带有返回值
    2016-07-04 16:13:53.764 ZFRuntime[1434:229571] 5. 返回值为:不带参数,但是带有返回值
  • 调用带参数带返回值的方法

      int returnInt = ((int *(id, SEL, NSString *, int))objc_msgSend)((id)test, @selector(hasArguments:andReturnValue:), @"参数1", 1024);
    NSLog(@"6. return value is %d", reuturnInt);

    打印结果:(由打印结果可见,我们已经达到了预期目的)

      2016-07-04 16:18:38.679 ZFRuntime[1455:234403] 方法名:-[MessageSendTest hasArguments:andReturnValue:], 参数:参数1, 返回值:1024
    2016-07-04 16:18:38.679 ZFRuntime[1455:234403] 6. return value is 1024
  • 我们还可以给类动态地添加方法

      class_addMethod([test class], NSSelectorFromString(@"cStyleFunc"), (IMP)cStyleFunc, "i@:r^vr^v");
    int returnValue = ((int *(id, SEL, const void *, const void *))objc_msgSend)((id)test, NSSelectorFromString(@"cStyleFunc"), "参数1", "参数2");
    NSLog(@"7. 返回值:%d", returnValue); // 然后我们实现方法 `cStyleFunc`
    int cStyleFunc(id receiver, SEL sel, const void *arg1, const void *arg2)
    {
    NSLog(@"方法名:%s, 参数1:%@, 参数2:%@", __FUNCTION__, [NSString stringWithUTF8String:arg1], [NSString stringWithUTF8String:arg1]);
    return 1;
    }

    先来看下打印结果:

      2016-07-04 16:24:36.670 ZFRuntime[1477:241676] 方法名:cStyleFunc, 参数1:参数1, 参数2:参数1
    2016-07-04 16:24:36.670 ZFRuntime[1477:241676] 7. 返回值:1

    从打印来看,结果是正常的,那么我们再来分析下class_addMethod这个方法,对于这个API苹果是这样定义的

      /**
    * 定义:给定名称和实现,从而为类增加新的方法
    * cls:要增加方法的那个类
    * name:方法选择器
    * imp:具体的实现函数
    * types:一串描述方法参数的字符串
    */
    BOOL class_addMethod(Class cls, SEL name, IMP imp, const char *types)

    对于上边这个方法,参数1、2和3我们都没有疑惑,可是第4个参数,让人看着云里雾里,完全不知道是什么东西,其实查看官方文档不难发现

    对于 "i@:r^vr^v"

      第1个字符:表示函数(方法)返回值类型,这里返回值类型是 `int` ,故为 `i`
    第2、3个字符:苹果解释是由于函数(方法)至少带有两个参数(self和_cmd)还记得之前的 (id,SEL) 么,所以第2、3个字符必须是 ‘@:’,其实我们当做固定写法就好了
    第4个字符之后的是什么呢?

    不要着急,我们在刚才的输出语句 NSLog(@"7. 返回值:%d", returnValue); 之后加一句打印:

      NSLog(@"%s", @encode(const void *));

    这个时候控制台输出了一句话:

      2016-07-04 16:44:32.300 ZFRuntime[1522:262609] r^v

    看到这里我们就明白了,r^v其实代表的是cStyleFunc函数第3、4个参数类型,当然对于不同情况,这里的r^v是不同的,具体其他情况可以参考苹果给出的解释

备注

在使用objc_msgSend方法编译时可能出现报错的情况,对应的解决办法如下:

本篇笔记部分参考自一下:

runtime - 消息发送(objc_msgSend)的更多相关文章

  1. Runtime - 消息发送原理

    Runtime - 消息发送原理. Objective-C运行时的核心就在于消息分派器objc_msgSend,消息分派器把选择器映射为函数指针,并调用被引用的函数. 要想理解objc_msgSend ...

  2. iOS 消息发送与转发详解

    Objective-C 是一门动态语言,它将很多静态语言在编译和链接时期做的事情,放到了运行时来处理.之所以能具备这种特性,离不开 Runtime 这个库.Runtime 很好的解决了如何在运行时期找 ...

  3. runtime——消息机制

    本文授权转载,作者:Sindri的小巢(简书) 从异常说起 我们都知道,在iOS中存在这么一个通用类类型id,它可以用来表示任何对象的类型 —— 这意味着我们使用id类型的对象调用任何一个方法,编译器 ...

  4. Objective-C中的消息发送总结

    关于OC中的消息发送的实现,在去年也看过一次,当时有点不太理解,但是今年再看却很容易理解. 我想这跟知识体系的构建有关,如果你不认识有砖.水泥等这些建筑的基本组成部分,那么我们应该很难理解建筑是怎么建 ...

  5. ios学习路线—Objective-C(Runtime消息机制)

    RunTime简称运行时.就是系统在运行的时候的一些机制,其中最主要的是消息机制.对于C语言,函数的调用在编译的时候会决定调用哪个函数( C语言的函数调用请看这里 ).编译完成之后直接顺序执行,无任何 ...

  6. runtime - 消息机制

    Xcode中使用runtime代码时,建议先做下配置: 使用runtime代码时会有适当的提醒. OC方法调用的本质是消息转发,消息机制的本质 创建一个Person类,添加方法 - (void)eat ...

  7. runtime消息转发机制

    Objective-C 扩展了 C 语言,并加入了面向对象特性和 Smalltalk 式的消息传递机制.而这个扩展的核心是一个用 C 和 编译语言 写的 Runtime 库.它是 Objective- ...

  8. ActiveMQ点对点的消息发送案例

    公司最近会用MQ对某些业务进行处理,所以,这次我下载了apache-activemq-5.12.0-bin把玩下. 基于练习方便需要,使用Windows的版本. 参考的优秀文章: activemq的几 ...

  9. 【ActiveMQ】ActiveMQ在Windows的安装,以及点对点的消息发送案例

    公司最近会用MQ对某些业务进行处理,所以,这次我下载了apache-activemq-5.12.0-bin把玩下. 基于练习方便需要,使用Windows的版本. 参考的优秀文章: activemq的几 ...

随机推荐

  1. Docker图形化工具——Portainer

    目标搭建docker-ui 一.Docker图形化工具 docker 图形页面管理工具常用的有三种,DockerUI ,Portainer ,Shipyard .DockerUI 是 Portaine ...

  2. 第三节:EF Core上下文DbContext相关配置和生命周期

    一. 配置相关 1. 数据库连接字符串的写法 (1).账号密码:Server=localhost;Database=EFDB01;User ID=sa;Password=123456; (2).win ...

  3. 怎么看系统是UEFI还是Legacy BIOS启动模式?

    在命令行 cmd 中输入  msinfo32 ,找到右侧[BIOS模式],看到这里显示的是[uefi],那么说明是[uefi]方式启动的,反之显示为[BIOS],那么就是传统[BIOS]启动模式.如下 ...

  4. 在vps上搭建hexo博客

    最近更换了服务器,需要把自己的Hexo Next重新部署到新服务器上,本文记录一下在vps上搭建hexo博客的过程. 在vps上搭建hexo博客需要下面这些工具: Nginx: 用于博客展示 SSH: ...

  5. python第五章程序练习题

    5.2 def isOdd(a): if a%2!=0: return True else: a=eval(input()) print(isOdd(a)) 5.3 def isNum(x): try ...

  6. thinkPHP中 query()和execute()的区别

    query()执行的是查询(select)的SQL语句. execute()执行的是插入(insert)和修改(update)的SQL语句.execute()方法将返回影响的记录数. 如果在TP中使用 ...

  7. DEV 总结

    转自:https://www.cnblogs.com/yuerdongni/archive/2012/09/08/2676753.html 1. 如何解决单击记录整行选中的问题 View->Op ...

  8. Jwt身份验证

    转载自博友(TerryTon)  1.因为json是通用的,所以jwt可以在绝大部分平台可以通用,如java,python,php,.net等  2.基于jwt是无状态的,jwt可以用于分布式等现在比 ...

  9. pytest之mark功能

    pytest系列(一)中给大家介绍了pytest的特性,以及它的编写用例的简单至极. 那么在实际工作当中呢,我们要写的自动化用例会比较多,不会都放在一个py文件里. 如下图所示,我们编写的用例存放在不 ...

  10. vuex简单化理解和安装使用

     1.简单化理解 首先你要明白 vuex 的目的 就是为了 集中化的管理项目中 组件所有的 数据状态 (state) 0. 第一步你要明白 , store 的重要性 , store 类似一个中央基站, ...