zoukankan      html  css  js  c++  java
  • runtime

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

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

  • 相关阅读:
    C++ 扩展 Op
    Python 扩展 Op
    VS Code 调试 OneFlow
    运行时数据获取
    OFRecord 图片文件制数据集
    OFRecord 数据集加载
    OFRecord 数据格式
    OneFlow 并行特色
    Consistent 与 Mirrored 视角
    作业函数的定义与调用
  • 原文地址:https://www.cnblogs.com/feng9exe/p/7132674.html
Copyright © 2011-2022 走看看