美文网首页
Runtime 方法与消息

Runtime 方法与消息

作者: 小羊孩子 | 来源:发表于2017-05-22 10:33 被阅读28次

    基础数据类型
    SEL
    sel又叫选择器,是表示一个方法的selector的指针,其定义如下:

    typedef struct objc_selector *SEL;
    

    objc_selector结构体的详细定义没有在<objc/runtime.h>头文件中找到。方法的selector用于表示运行时方法的名字。Objective-C在编译时,会根据每一个方法的名字、参数序列,生成一个唯一的整数标识(int类型的指针),这个标识就是SEL。如下代码:

    SEL sel = @selector(method);
    NSLog(@"sel : %p", sell);
    

    sell以一串16进制地址格式输出

    两个类之间,不管它们是父类与子类的关系,还是之间没有这种关系,只要方法名相同,那么方法的SEL就是一样的。每一个方法都对应着一个个SEL。所以0C在同一个类(和类的继承体系)中。不能存在两个同名的方法,即使参数类型不同也不行。相同的方法只能对应一个SEL。这也就导致OC在处理相同方法名且参数个数不同的方法方面的能力很差。如:

    - (void)setWidth:(int)width;
    - (void)setWidth:(double)width;
    

    这样的定义呗认为是一种编译错误

    当然,不同的类可以拥有相同的selector,这个没问题。不同类的实例对象执行相同的selector时,会在各自的方法列表中去根据selector去寻找自己对应的IMP.

    工程中所有的sel组成一个set集合,set的特点就是唯一,因此sel是唯一的。因此,如果我们想到这个方法集合中查找某个方法时,只需要去找到这个方法对应的sel就行了,sel实际上就是根据方法名hash化了的一个个字符串,而对于字符串的比较仅仅需要比较它们的地址就行了,速度上很快。但是,有一个问题,就是数量增多会增大hash冲突而导致的性能下降(或是没有冲突,因为也可能用perfect hash)。但无论使用什么方法加速,如果可以将总量减少(多个方法对应同一个sel),那就很厉害。这样,就不难理解,为什么sel仅仅是函数名了。

    本质上,sel只是一个指向方法的指针(准确的说,只是一个根据方法名hash化了的key值,能唯一代表一个方法),它的存在只是为了加快方法的查询速度。

    我们可以在运行时添加新的selector,也可以在运行时获取已经存在的selector,通过下列三种方法获取sel:

      1.sel_registerName函数
      2.Objective-C编译器提供的@selector()
      3.NSSelectorFromString()方法
    

    IMP
    imp实际上是一个个函数指针,指向方法实现的首地址。定义如下:

    id (*IMP)(id, SEL,...)
    

    这个函数使用当前CPU架构实现的标准的c调用约定。第一个参数是指向self的指针(如果是实例方法,则是类实例的内存地址;如果是类方法,则是指向元类的指针),第二个参数是方法选择器,接下来是方法的实际参数列表

    前面介绍的SEL就是为了查找方法的最终实现IMP的。由于每一个方法对应唯一的SEL,、因此我们可以通过SEL方便快速准确获得它所对应的IMP,获得IMP后,我们就获得了执行这个方法代码的入口点,就可以像调用普通c函数一样使用

    通过取得IMP,我们可以跳过runtime消息传递机制,直接执行IMP指向的函数实现,这样省去了runtime消息传递过程中所做的一系列查找操作,会比直接向对象发送消息高效一些。

    Method

    介绍完SEL 和IMP,可以来讲下Method。Method用于表示类定义中的方法,定义如下:

    typedef struct objc_method *Method;
    
    struct objc_method {
            SEL method_name;
            char *method_types;
            IMP method_imp;
    }
    

    该结构体中包含了一个SEL和IMP,实际上相当于在SEL和IMP之间作了一个映射。有了SEL,便可以找到对应的IMP,从而调用方法的实现代码。具体流程:

    objc_method_description
    objc_merhod_description定义了一个OC方法:
    struct objc_method_description {SEL name; char *types};
    

    方法相关操作函数
    runtime提供了一系列的方法来处理与方法相关的操作。包括方法本身和sel。

    方法:
    方法相关函数包括:

    id method_invoke(id receiver, Method m, ...);  //调用指定方法的实现
    void method_invoke_stret (id receiver, Method m, ...)  //调用返回一个数据结构的方法的实现
    SEL method_getName (Method m);  //获取方法名
    IMP method_getImplementation (Method m);  //返回方法的实现
    const char * method_getTypeEncoding (Method m);   //获取描述方法参数和返回值类型的字符串
    char * method_copyReturnType (Method m);  //获取方法的返回值类型的字符串
    char * method_copyArgumentType (Method m, unsigned int index);  //获取方法的制定位置参数的类型字符串
    void method_getReturnType (Method m, char *dst, size_t dat_len);  //通过引用返回方法的返回值类型字符串
    unsigned int method_getNumberOfArguments (Method m);  //返回方法的参数的个数
    void method_getArgumentType (Method m, unsigned int index, char *dst, size_t dat_len);  //通过引用返回方法指定位置参数的类型字符串
    struct objc_method_description * method)getDescription (Method m);  //返回指定方法的方法描述结构体
    IMP method_setImplemetation (Method m, IMP imp);  //设置方法的实现
    void method_exchangeImplemetations (Method m1, Method m2);  //交换两个方法的实现
    

    method_invoke函数,返回的是实际实现的返回值。参数receiver不能为空。这个方法的效率会比method_getImplementation和method_getName更快。
    method_getName函数,返回的是一个SEL。如果想获取方法名的C字符串,可以使用sel_getName(method_getName(method))。
    method_getReturnType函数,类型字符串会被拷贝到dst中。
    method_setImplementation函数,注意该函数返回值是方法之前的实现

    方法选择器

    const char * sel_getName (SEL sel);  //返回给定选择器指定的方法的名称
    SEL sel_registerName (const char *str); //在oc runtime系统中注册一个方法,将方法名映射到一个选择器,并返回这个选择器
    SEL sel_getUid (const char *str);
    BOOL sel_isEqual (SEL ohs, SEL rhs);   //比较两个选择器
    

    sel_registerName函数:在我们将一个方法添加到类定义时,我们必须在Objective-C Runtime系统中注册一个方法名以获取方法的选择器

    方法调用流程

    在oc中,消息直到运行时才绑定到方法实现上。编译器会将消息表达式[receiver message]转化为一个消息函数的调用, 即objc_msgSend.这个函数将消息接受者和方法名作为其基础参数,如以下所示:

    objc_msgSend (receiver, selector)
    

    如果消息中还有其它参数。如下:

    objc_msgSend (receiver, selector, arg1, arg2, ...)
    

    这个函数完成了动态绑定的所有事情:
    1.首先它找到了selector对应的方法实现。因为同一个方法可能在不同的类中有不同的实现。所以需要依赖接受者的类来找到确切的实现 。
    2.它调用方法实现,并将接受者对象和方法的所有参数传给它。
    3.最后,它将实现返回的值作为它自己的返回值。

    消息的关键在于结构体objc_class,这个结构体有两个字段是在分发消息的关注的:
    1.指向父类的指针
    2.一个类方法分发表。即methodlists

    当创建新对象时,先为其分配内存,并初始化成员变量。其中isa指针也会被初始化,让对象可以访问类和类的继承体系

    一个消息的基本框架:


    runtime 消息基本框架.gif

    当消息发送给一个对象时,objc_msgSend通过对象的isa指针获取到类的结构体,然后在方法分发表中查找方法的selector,如果没有找到selector,则通过objc_msgSend结构体中指向父类的指针找到其父类,并在父类的分发表中查找方法的selector。以此,会一直沿着类的继承体系到达NSObject类。定位到selector,函数就会获取了实现的入口点,并传入相应的参数来执行方法的具体实现。如果没有定位到selector,则走消息转发流程。

    为了加速消息的处理,运行时系统缓存使用过的selector和对应的方法的地址。

    隐藏参数

    objc_msgSend有两个隐藏参数
    1.消息接受对象
    2.方法的selector
    这两个参数为方法的实现提供了调用者的消息。是在编译期被插入实现代码的

    获取方法地址

    Runtime 中方法的动态绑定让我们写代码时更具灵活性,如可以把消息转发给我们想要的对象,或者随意交换一个方法的实现等。灵活性的提升带来性能上的损耗。

    当我们需要在一个循环内频繁调用一个特定的方法时,通过获取方法实现的地址,像调用函数一样来直接调用它。可以提高程序的性能

    NSObject类提供了methodForSelector:方法,可以获取方法的指针,然后通过这个文件夹

    相关文章

      网友评论

          本文标题:Runtime 方法与消息

          本文链接:https://www.haomeiwen.com/subject/sojbxxtx.html