runtime详解

作者: 进击的小矮人 | 来源:发表于2016-10-28 17:19 被阅读59次

    1.首先让我们来了解Class的定义:

    Objective-C类是由Class类型来表示的,它实际上是一个指向objc_class结构体的指针。它的定义如下:typedef struct objc_class *Class;

    2.查看objc/runtime.h中objc_class结构体的定义如下:

    struct objc_class {
        Class isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class super_class                       OBJC2_UNAVAILABLE;  // 父类
        const char *name                        OBJC2_UNAVAILABLE;  // 类名
        long version                            OBJC2_UNAVAILABLE;  // 类的版本信息,默认为0
        long info                               OBJC2_UNAVAILABLE;  // 类信息,供运行期使用的一些位标识
        long instance_size                      OBJC2_UNAVAILABLE;  // 该类的实例变量大小
        struct objc_ivar_list *ivars            OBJC2_UNAVAILABLE;  // 该类的成员变量链表
        struct objc_method_list **methodLists   OBJC2_UNAVAILABLE;  // 方法定义的链表
        struct objc_cache *cache                OBJC2_UNAVAILABLE;  // 方法缓存
        struct objc_protocol_list *protocols    OBJC2_UNAVAILABLE;  // 协议链表
    #endif
    } OBJC2_UNAVAILABLE;
    
    isa:需要注意的是在Objective-C中,所有的类自身也是一个对象,这个对象的Class里面也有一个isa指针,它指向metaClass(元类),我们会在后面介绍它。

    super_class:指向该类的父类,如果该类已经是最顶层的根类(如NSObject或NSProxy),则super_class为NULL。

    cache:用于缓存最近使用的方法。一个接收者对象接收到一个消息时,它会根据isa指针去查找能够响应这个消息的对象。在实际使用中,这个对象只有一部分方法是常用的,很多方法其实很少用或者根本用不上。这种情况下,如果每次消息来时,我们都是methodLists中遍历一遍,性能势必很差。这时,cache就派上用场了。在我们每次调用过一个方法后,这个方法就会被缓存到cache列表中,下次调用的时候runtime就会优先去cache中查找,如果cache没有,才去methodLists中查找方法。这样,对于那些经常用到的方法的调用,但提高了调用的效率。

    version:我们可以使用这个字段来提供类的版本信息。这对于对象的序列化非常有用,它可是让我们识别出不同类定义版本中实例变量布局的改变

    SEL

    SEL又叫选择器,是表示一个方法的selector的指针,其定义如下:

          1.typedef struct objc_selector *SEL;
    
    objc_selector结构体的详细定义没有在头文件中找到。方法的selector用于表示运行时方 法的名字。Objective-C在编译时,会依据每一个方法的名字、参数序列,生成一个唯一的整型标识(Int类型的地址),这个标识就是SEL。如下 代码所示:
         1.SEL sel1 = @selector(method1);
         2.NSLog(@"sel : %p", sel1);
         上面的输出为:
          2014-10-30 18:40:07.518 RuntimeTest[52734:466626] sel : 0x100002d72
    

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

         1.- (void)setWidth:(int)width;
         2.- (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实际上是一个函数指针,指向方法实现的首地址。其定义如下:

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

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

    前面介绍过的SEL就是为了查找方法的最终实现IMP的。由于每个方法对应唯一的SEL,因此我们可以通过SEL方便快速准确地获得它所对应的 IMP,查找过程将在下面讨论。取得IMP后,我们就获得了执行这个方法代码的入口点,此时,我们就可以像调用普通的C语言函数一样来使用这个函数指针 了。

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

    Method

    Method用于表示类定义中的方法,则定义如下:

    typedef struct objc_method *Method;
     
    struct objc_method {
        SEL method_name                 OBJC2_UNAVAILABLE;  // 方法名
        char *method_types                  OBJC2_UNAVAILABLE;
        IMP method_imp                      OBJC2_UNAVAILABLE;  // 方法实现
    }
    

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

    runtime type说明

    1.type定义参考:https://developer.apple.com/library/mac/#documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html
       2."v@:@",解释v-返回值void类型,@-self指针id类型,:-SEL指针SEL类型,@-函数第一个参数为id类型
       3."@@:",解释@-返回值id类型,@-self指针id类型,:-SEL指针SEL类型,
    

    runtime使用场景:

         1.动态创建类,添加属性、成员变量、方法和协议。
         2.动态交互两个方法的实现(Method Swizzling)
         3.分类添加成员变量    3.1、类别能不能添加Ivar。
          提示:类别不能添加成员变量(Ivar),因为在category对于的结构体(category_t)中没有保存成员变量的结构体指针。
    
          无法添加实例变量的(因为在运行期,对象的内存布局已经确定,如果添加实例变量就会破坏类的内部局部,这对编译语言来说是灾难性的。
         4.自动实现归档和解归档
         5.模型字典相互转化
    

    Demo如下:

    动态创建类

        //动态创建类
        /*
         参数1:父类
         参数2:类的名字
         参数3:额外分配的空间,一般都是0
         */
        Class cls = objc_allocateClassPair([NSObject class], "Person", 0);
        
        //在注册之前一定要添加需要的方法,成员变量等。
        /*
         参数1:该成员变量添加到哪个类中。
         参数2:成员变量的名字
         参数3:成员变量的大小
         参数4:log2(成员变量的大小)
         参数5:变量的类型:参考资源素材中的“Runtime Type Encodings”
         */
        class_addIvar(cls, "_name", sizeof(NSString *), log2(sizeof(NSString *)), @encode(NSString *));
        class_addIvar(cls, "_age", sizeof(int), log2(sizeof(int)), @encode(int));
    

    添加方法

        /*
        //添加方法
        class_addMethod(<#__unsafe_unretained Class cls#>, <#SEL name#>, <#IMP imp#>, <#const char *types#>)
        //添加属性
        class_addProperty(<#__unsafe_unretained Class cls#>, <#const char *name#>, <#const objc_property_attribute_t *attributes#>, <#unsigned int attributeCount#>)
        //添加协议
        class_addProtocol(<#__unsafe_unretained Class cls#>, <#Protocol *protocol#>)
         */
        
        //必须注册后才能用,注册后类的结构就确定好了。
        objc_registerClassPair(cls);
        
        
        char *type = @encode(void);
        printf("%s",type);
        
        
        id person = [[cls alloc] init];
        //赋值
        [person setValue:@"小明" forKey:@"_name"];
        [person setValue:@(20) forKey:@"_age"];
        
        NSString *name = [person valueForKey:@"_name"];
        int age = [[person valueForKey:@"_age"] intValue];
        
        NSLog(@"name = %@ , age = %d",name, age);
        
        //objc_setAssociatedObject(<#id object#>, <#const void *key#>, <#id value#>, objc_AssociationPolicy policy)
    

    动态交换两个方法实现: Method Swizzling

        /*
         动态交换两个方法实现: Method Swizzling
         */
        //获取两个方法的实现
        Method test1 = class_getInstanceMethod([self class], @selector(test1));
        Method test2 = class_getInstanceMethod([self class], @selector(test2));
        //交互两个方法的实现
        method_exchangeImplementations(test1, test2);
        
        Method request1 = class_getClassMethod([HttpRequest class], @selector(GET:));
        Method request2 = class_getInstanceMethod([self class], @selector(customGet));
        //交互两个方法的实现
        method_exchangeImplementations(request1, request2);
        
        [HttpRequest GET:@"url"];
    }
    
    - (void)customGet
    {
        
    }
    
    - (void)test1
    {
        NSLog(@"test111111方法调用了");
        
        [self test1];
        
    }
    
    - (void)test2
    {
        NSLog(@"test22222方法调用了");
    }
    
    
    

    相关文章

      网友评论

        本文标题:runtime详解

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