runtime - 消息发送(objc_msgSend)

在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方法编译时可能出现报错的情况,对应的解决办法如下:

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

Demo地址

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,607评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,047评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,496评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,405评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,400评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,479评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,883评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,535评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,743评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,544评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,612评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,309评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,881评论 3 306
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,891评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,136评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,783评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,316评论 2 342

推荐阅读更多精彩内容