runtime objc_msgSend
runtime objc_msgSend
前言
想要通过runtime发送消息,就必须要掌握runtime如何发送消息,是调用哪个函数?又是如何调用的?本篇文章只是记录笔者学习objc_msgSend函数的使用笔记,若有误解之处,还请指出。谢谢!
objc_msgSend
我们先来看看官方函数objc_msgSend的声明:
/* Basic Messaging Primitives
*
* On some architectures, use objc_msgSend_stret for some struct return types.
* On some architectures, use objc_msgSend_fpret for some float return types.
* On some architectures, use objc_msgSend_fp2ret for some float return types.
*
* These functions must be cast to an appropriate function pointer type
* before being called.
*/
void objc_msgSend(void /* id self, SEL op, ... */ )
从这个函数的注释可以看出来了,这是个最基本的用于发送消息的函数。另外,这个函数并不能发送所有类型的消息,只能发送基本的消息。比如,在一些处理器上,我们必须使用objc_msgSend_stret来发送返回值类型为结构体的消息,使用objc_msgSend_fpret来发送返回值类型为浮点类型的消息,而又在一些处理器上,还得使用objc_msgSend_fp2ret来发送返回值类型为浮点类型的消息。
最关键的一点:无论何时,要调用objc_msgSend函数,必须要将函数强制转换成合适的函数指针类型才能调用。
从objc_msgSend函数的声明来看,它应该是不带返回值的,但是我们在使用中却可以强制转换类型,以便接收返回值。另外,它的参数列表是可以任意多个的,前提也是要强制函数指针类型。
学习使用
我们建立一个类,就专门学习如何运用objc_msgSend函数来发送消息。我们建立了一个HYBMsgSend类来学习。
1.创建并初始化对象
我们一直以来都是使用类似这样的[[HYBMsgSend alloc] init]来创建并初始化对象吧,其实在编译时,这一行代码也会转换成类似如下的代码:
// 1.创建对象
HYBMsgSend *msg = ((HYBMsgSend * (*)(id, SEL))objc_msgSend)((id)[HYBMsgSend class], @selector(alloc));
// 2.初始化对象
msg = ((HYBMsgSend * (*)(id, SEL))objc_msgSend)((id)msg, @selector(init));
要发送消息给msg对象,并将创建的对象返回,那么我们需要强转函数指针类型。(HYBMsgSend * (*)(id, SEL)这是带一个对象指针返回值和两个参数的函数指针,这样就可以调用了。
2.发送无参数无返回值消息
我们先定义一个方法:
- (void)noArgumentsAndNoReturnValue {
NSLog(@"%s was called, and it has no arguments and return value", __FUNCTION__);
}
然后,我们发送消息,测试一下结果。
// 2.调用无参数无返回值方法
((void (*)(id, SEL))objc_msgSend)((id)msg, @selector(noArgumentsAndNoReturnValue));
结果如下,说明成功地接收到消息并处理了:
-[HYBMsgSend noArgumentsAndNoReturnValue] was called, and it has no arguments and return value
3.带参数不带返回值消息
我们定义一个方法只带参数,不带返回值:
- (void)hasArguments:(NSString *)arg {
NSLog(@"%s was called, and argument is %@", __FUNCTION__, arg);
}
然后尝试发送消息试试:
// 3.调用带一个参数但无返回值的方法
((void (*)(id, SEL, NSString *))objc_msgSend)((id)msg, @selector(hasArguments:), @"带一个参数,但无返回值");
同样,我们也是需要强转函数指针类型,否则会报错的。其实,只有调用runtime函数来发送消息,几乎都需要强转函数指针类型为合适的类型。
其打印结果说明成功接收到消息并处理了:
-[HYBMsgSend hasArguments:] was called, and argument is 带一个参数,但无返回值
4.带返回值不带参数消息
当消息带有返回值时,我们如何接收呢?我们先声明一个方法,让它带有返回值,但是不带参数:
- (NSString *)noArgumentsButReturnValue {
NSLog(@"%s was called, and return value is %@", __FUNCTION__, @"不带参数,但是带有返回值");
return @"不带参数,但是带有返回值";
}
然后发送消息,并接收返回值:
// 4.调用带返回值,但是不带参数
NSString *retValue = ((NSString * (*)(id, SEL))objc_msgSend)((id)msg, @selector(noArgumentsButReturnValue));
NSLog(@"5. 返回值为:%@", retValue);
打印结果说明成功地发送了消息并得到处理,且成功地获取到了返回值:
-[HYBMsgSend noArgumentsButReturnValue] was called, and return value is 不带参数,但是带有返回值
5.带参数带返回值的消息
定义一个带参数带普通返回值的方法:
- (int)hasArguments:(NSString *)arg andReturnValue:(int)arg1 {
NSLog(@"%s was called, and argument is %@, return value is %d", __FUNCTION__, arg, arg1);
return arg1;
}
发送消息,并接收返回值:
// 6.带参数带返回值
int returnValue = ((int (*)(id, SEL, NSString *, int))
objc_msgSend)((id)msg,
@selector(hasArguments:andReturnValue:),
@"参数1",
2016);
NSLog(@"6. return value is %d", returnValue);
其结果如下,说明调用成功,参数也传过去了,返回值也接收到了:
-[HYBMsgSend hasArguments:andReturnValue:] was called, and argument is 参数1, return value is 2016
6. return value is 2016
6.动态添加方法再调用
我们声明一个C语言函数:
// C函数
int cStyleFunc(const void *arg1, const void *arg2) {
NSLog(@"%s was called, arg1 is %s, and arg2 is %s", __FUNCTION__, arg1, arg2);
return 1;
}
这个函数并不属性对象方法,因此我们不能直接调用,但是我们可以动态地添加方法到对象中,然后再发送消息:
class_addMethod(msg.class, NSSelectorFromString(@"cStyleFunc"), (IMP)cStyleFunc, "v@:");
returnValue = ((int (*)(id, SEL, const void *, const void *))
objc_msgSend)((id)msg,
NSSelectorFromString(@"cStyleFunc"),
"参数1",
"参数2");
NSLog(@"7. return value is %d", returnValue);
7.带浮点返回值的消息
对于发送带浮点返回值类型的消息,我们可以使用objc_msgSend_fpret也可以使用objc_msgSend。不过这两个函数返回结果是一样的。笔者并不是很清楚,这两个函数的主要区别是什么。根据注释说明,只是说有一些处理器上,需要使用objc_msgSend_fpret来发送带浮点返回值类型的消息。
float retFloatValue = ((float (*)(id, SEL))objc_msgSend_fpret)((id)msg, @selector(returnFloatType));
NSLog(@"%f", retFloatValue);
retFloatValue = ((float (*)(id, SEL))objc_msgSend)((id)msg, @selector(returnFloatType));
NSLog(@"%f", retFloatValue);
8.带结构体返回值的消息
对于返回值类型为结构体的消息,我们必须使用objc_msgSend_stret而不能直接使用objc_msgSend函数,否则会crash:
// 9.返回结构体时,不能使用objc_msgSend,而是要使用objc_msgSend_stret,否则会crash
CGRect frame = ((CGRect (*)(id, SEL))objc_msgSend_stret)((id)msg, @selector(returnTypeIsStruct));
NSLog(@"9. return value is %@", NSStringFromCGRect(frame));
源代码
大家可以到GITHUB下载源代码:https://github.com/CoderJackyHuang/RuntimeDemo
对应于Message分组。请随手给个star支持一下吧!
写在最后
文章中难免有说得不合理的地方,如果您认为说法不正确或者哪里有错误的地方,请在评论中留言,笔者会在第一时间修正!!!
关注我
如果在使用过程中遇到问题,或者想要与我交流,可加入有问必答QQ群:324400294
关注微信公众号:iOSDevShares
关注新浪微博账号:标哥Jacky
runtime objc_msgSend的更多相关文章
- runtime - 消息发送(objc_msgSend)
http://www.jianshu.com/p/95c8cb186673 在OC中,我们对方法的调用都会被转换成内部的消息发送执行对objc_msgSend方法的调用,掌握好消息发送,可以让我们在编 ...
- runtime运行时
/** * Describes the instance variables declared by a class. * * @param cls The class to inspect. * @ ...
- OC的runtime运行机制
什么是runtime runtime就是一套底层的c语言API(Application Programming Interface)里面包括很多强大实用的c语言类型.c语言函数. 实际上,平时我们编写 ...
- RunTime&RunLoop初见
什么是runtime 1> runtime是一套底层的c语言API(包括很多强大实用的c语言类型,c语言函数); 2>实际上,平时我们编写的oc代码,底层都是基于runtime实现的 也就 ...
- runtime 运行机制2
Mike_zh QQ:82643885 end: blogTitle 博客的标题和副标题 博客园 首页 新随笔 联系 订阅 <a id="MyLinks1_XMLLink" ...
- iOS Runtime原理及使用
runtime简介 因为Objc是一门动态语言,所以它总是想办法把一些决定工作从编译连接推迟到运行时.也就是说只有编译器是不够的,还需要一个运行时系统 (runtime system) 来执行编译后的 ...
- RunTime(运行时机制)
1>runtime实现的机制是什么,怎么用,一般用于干嘛? 这个问题我就不跟大家绕弯子了,直接告诉大家, runtime是一套比较底层的纯C语言API, 属于1个C语言库, 包含了很多底层的C语 ...
- ios runtime的相关知识
一.iOS runtime原理 对于runtime机制,在网上找到的资料大概就是怎么去用这些东西,以及查看runtime.h头文件中的实现,当然这确实是一种很好的学习方法,但是,其实我们还是不会知道r ...
- 关于 IOS Runtime Runloop 2
Runtime 也就是运行时组件,一个纯C语言写的基础库. 我们平时编写的OC代码中, 程序运行过程时, 其实最终都是转成了runtime的C语言代码 Objective-C编写出来的程序必须得到ru ...
随机推荐
- LeetCode.5-最长回文子串(Longest Palindromic Substring)
这是悦乐书的第342次更新,第366篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Medium级别的第3题(顺位题号是5).给定一个字符串s,找到s中最长的回文子字符串. 您可以假设s ...
- Python 36 GIL全局解释器锁 、vs自定义互斥锁
一:GIL全局解释器锁介绍 在CPython中,全局解释器锁(或GIL)是一个互斥锁, 它阻止多个本机线程同时执行Python字节码.译文:之所以需要这个锁, 主要是因为CPython的内存管理不是线 ...
- Your configuration specifies to merge with the ref 'refs/heads/v.autoCheckProduct.20190325' from the remote, but no such ref was fetched.
问题: 创建新的分支,当我们执行git pull,出现如下错误 解决办法: 1.切换到主分支(或者被依赖的分支,也就是你从哪个分支上拉取新的分支),博主这里是master分支 2.执行以下两个命令 3 ...
- B - Eleven
Problem description Eleven wants to choose a new name for herself. As a bunch of geeks, her friends ...
- 一种压缩图片的方法---Machine learning 之 K-Means
背景描述: RGB编码:对于一个直接用24bit表示每一个而像素的图像来说,每一个pixel使用8-bit无符号整数(0-255)来表示红or绿or蓝. 压缩目的: 将128x128大小的图片由原来的 ...
- “阻塞”与"非阻塞"与"同步"与“异步"
链接:http://www.zhihu.com/question/19732473/answer/20851256来源:知乎 “阻塞”与"非阻塞"与"同步"与“ ...
- Md2All版本更新记录
Md2All版本更新记录 版本号:V2.8.2更新日期:2018-06-281:结合云图床,解决了Latex公式复制到公众号时有可能报“图片粘贴失败的问题”;2:结合云图床,解决了Latex公式复制到 ...
- Android 概览屏幕
文章照搬过来的:原文地址https://developer.android.google.cn/guide/components/recents.html 概览屏幕(也称为最新动态屏幕.最近任务列表或 ...
- java Web(2)
Servlet与web容器的配合: 1)客户端向Web服务器发起一个HTTP请求. 2)HTTP请求被Web服务器接受,如果请求的是静态页面,则由Web服务器负责处理.如果请求的是Java Web组件 ...
- 安卓Queue的使用
Queue的成员函数 add 增加一个元索 如果队列已满,则抛出一个IIIegaISlabEepeplian异常 rem ...