美文网首页
OC 的Runtime 机制之类和对象

OC 的Runtime 机制之类和对象

作者: 小强简书 | 来源:发表于2018-03-30 17:43 被阅读20次

    RunTime简称运行时。就是系统在运行的时候的一些机制,其中最主要的是消息机制。OC的函数调用成为消息发送。属于动态调用过程。在编译的时候并不能决定真正调用哪个函数(事实证明,在编 译阶段,OC可以调用任何函数,即使这个函数并未实现,只要申明过就不会报错。)只有在真正运行的时候才会根据函数的名称找 到对应的函数来调用。

    1.下面我们来看最基本的类的定义
    Class实际上是一个指向objc_class结构体的指针
    class的定义:

    typedef struct objc_class *Class;
    

    class 是一个objc_class 结构类型的指针,id是一个objc_class类型的指针

    objc_class

    struct objc_class { 
    struct objc_class* isa;
    struct objc_class* super_class;
    const char* name;
    long version;
    long info;
    long instance_size;
    struct objc_ivar_list* ivars;
    struct objc_method_list** methodLists; 
    struct objc_cache* cache; 
    struct objc_protocol_list* protocols; 
    }; 
    
    

    也就是说class的结构是上面那样的,objc_class的成员介绍如下:
    isa:是一个objc_class类型的指针,其实是指向元类metaclass。
    super_class : 一个指向父类的objc_class类型的指针,如果是最顶层,那么super_class 为 null
    在继承关系中,通常会有,子类,父类,根类,他们的对应关系通常是这样的
    类的实例对象的isa指向该类,该类的isa指向该类的父类,类的super_class指向其父类,如此一层一层指向

    1.jpg

    class和metaclass
    class是instance object 的类类型 。当我们向实例对象发送消息(实例方法 )的时候,我们在该实例对象的class的方法列表(methodlists )中查找相应函数,如果没有找到就去改类的父类的方法列表中查找,然后一层一层去查找,直到找到为止。

    NSString * str;[str lowercaseString]; 
    向 str 实例对象发送 lowercaseString 消息,会在 NSString 类结构的 methodlists 中去查找 lowercaseString 的响应 函数。 
    

    metaclass是class object 类类型。当我们向类对象发送消息(类方法) 的时候,我们在该类对象的 metaclass 结构的 methodlists 中去查找响应的函数,如果没有找到匹配的响应函数则在该 metaclass 的父类中的 methodlists 去查找

    [NSString stringWithString:@"str"]; 
    向 NSString 类对象发送 stringWithString 消息,会在 NSString 的 metaclass 类结构的 methodlists 中去查找 stringWithString 的响应函数 
    

    name:表示类的名称,通过这个名称查找到该类(通过:id objc_getClass(const char *aClassName))或该类的 metaclass(id objc_getMetaClass(const char *aClassName));
    version:类的版本信息,可以在运行期对其进行修改(class_setVersion)或获 取(class_getVersion)。
    info:供运行期使用的一些位标识。
    有如下一些位掩码:CLS_CLASS (0x1L) 表示该类为普通 class ,其中包含实例方法和变量;
    CLS_META (0x2L) 表示该类为 metaclass,其中包含类方法;
    CLS_INITIALIZED(0x4L) 表示该类已经被运行期初始化了,这个标识位只被 objc_addClass 所设置;
    CLS_POSING (0x8L) 表示该类被 pose 成其他的类;(poseclass 在 ObjC 2.0 中被废弃了);
    CLS_MAPPED (0x10L) 为 ObjC 运行期所使用CLS_FLUSH_CACHE (0x20L) 为 ObjC 运行期所使用
    CLS_GROW_CACHE (0x40L) 为 ObjC 运行期所使用
    CLS_NEED_BIND (0x80L) 为 ObjC 运行期所使用
    CLS_METHOD_ARRAY (0x100L) 该标志位指示 methodlists 是指向一个objc_method_list 还是 一个包含 objc_method_list 指针的数组;

    instance_size:该类的实例变量大小(包括从父类继承下来的实例变量);
    ivars: 指向objc_ivar_list 的指针 ,储存每个实例变量的地址,如果没有的话是null
    objc_method_list:方法列表
    cache:方法的缓存
    protocols:协议列表

    2.对象
    objc_object 与id

    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    typedef struct objc_object *id;
    

    可以看出,objc_object结构体里只有一个字段,isa指针,当我们向一个obj对象发送消息的是时候,runtime会根据实例对象的isa指针找到这个实例对象所对应的类,然后去类的方法列表里找到对应的方法。
    Id类型也是一个objc_object的结构类型的指针。

    1. objc_cache
      objc_cache结构体定义如下:
      struct objc_cache {
      unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
      unsigned int occupied OBJC2_UNAVAILABLE;
      Method buckets[1] OBJC2_UNAVAILABLE;
      };

    1.mask:是一个int类型整数,指定分配缓存的bucket的总数,在方法查找的过程中,runtime使用这个字段来确定开始线性查找数组的索引位置。可以作为一个简单的hash算法
    2.occupied:一个int类型整数,指定实际占用缓存的bucket的总数
    3.buckets:指向method数据结构指针的数组的指针,这个数组可能为null,表示这个缓存bucket没有占用。

    4.关于metaclass(元类):
    meta-class是一个类对象的类。
    当我们向一个对象发送消息的时候,runtime会在这个对象所属的这个类的方法列表中查找方法,如果我们向一个类发送消息的时候,会在这个类的meta-class的方法列表中查找。
    Meta-class储存着一个类的所有的类方法。
    再深入一下,meta-class也是一个类,也可以向它发送一个消息,那么它的isa又是指向什么呢?为了不让这种结构无限延伸下去,Objective-C的设计者让所有的meta-class的isa指向基类的meta-class,以此作为它们的所属类。即,任何NSObject继承体系下的meta-class都使用NSObject的meta-class作为自己的所属类,而基类的meta-class的isa指针是指向它自己。这样就形成了一个完美的闭环。

    5.类和对象的runtime函数总结
    (1)类

    类名:

    // 获取类的类名
    const char * class_getName ( Class cls );
    

    对于class_getName函数,如果传入的cls为Nil,则返回一个字字符串。

    父类和元类

    // 获取类的父类
    Class class_getSuperclass ( Class cls );
     
    // 判断给定的Class是否是一个元类
    BOOL class_isMetaClass ( Class cls );
    

    class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。
    class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。

    实例变量大小(instance_size)

    // 获取实例大小
    size_t class_getInstanceSize ( Class cls );
    

    成员变量(ivars)和属性

    在obj_class中,所有的成员变量和属性的信息都是放在 struct objc_ivar_list* ivars中的,ivars是一个数组,数组中每个元素都是指向ivar的指针。

    1.成员变量操作函数

    // 获取类中指定名称实例成员变量的信息
    Ivar class_getInstanceVariable ( Class cls, const char *name );
     
    // 获取类成员变量的信息
    Ivar class_getClassVariable ( Class cls, const char *name );
     
    // 添加成员变量
    BOOL class_addIvar ( Class cls, const char *name, size_t size, uint8_t alignment, const char *types );
     
    // 获取整个成员变量列表
    Ivar * class_copyIvarList ( Class cls, unsigned int *outCount );
    

    class_getInstanceVariable函数,它返回一个指向包含name指定的成员变量信息的objc_ivar结构体的指针(Ivar)。

    class_getClassVariable函数,目前没有找到关于Objective-C中类变量的信息,一般认为Objective-C不支持类变量。注意,返回的列表不包含父类的成员变量和属性。

    Objective-C不支持往已存在的类中添加实例变量,因此不管是系统库提供的提供的类,还是我们自定义的类,都无法动态添加成员变量。但如果我们通过运行时来创建一个类的话,又应该如何给它添加成员变量呢?这时我们就可以使用class_addIvar函数了。不过需要注意的是,这个方法只能在objc_allocateClassPair函数与objc_registerClassPair之间调用。另外,这个类也不能是元类。成员变量的按字节最小对齐量是1<<alignment。这取决于ivar的类型和机器的架构。如果变量的类型是指针类型,则传递log2(sizeof(pointer_type))。

    class_copyIvarList函数,它返回一个指向成员变量信息的数组,数组中每个元素是指向该成员变量信息的objc_ivar结构体的指针。这个数组不包含在父类中声明的变量。outCount指针返回数组的大小。需要注意的是,我们必须使用free()来释放这个数组。

    2.属性操作

    // 获取指定的属性
    objc_property_t class_getProperty ( Class cls, const char *name );
     
    // 获取属性列表
    objc_property_t * class_copyPropertyList ( Class cls, unsigned int *outCount );
     
    // 为类添加属性
    BOOL class_addProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
     
    // 替换类的属性
    void class_replaceProperty ( Class cls, const char *name, const objc_property_attribute_t *attributes, unsigned int attributeCount );
    

    方法methodLists

    // 添加方法
    BOOL class_addMethod ( Class cls, SEL name, IMP imp, const char *types );
    
    // 获取实例方法
    Method class_getInstanceMethod ( Class cls, SEL name );
    
    // 获取类方法
    Method class_getClassMethod ( Class cls, SEL name );
    
    // 获取所有方法的数组
    Method * class_copyMethodList ( Class cls, unsigned int *outCount );
    
    // 替代方法的实现
    IMP class_replaceMethod ( Class cls, SEL name, IMP imp, const char *types );
    
    // 返回方法的具体实现
    IMP class_getMethodImplementation ( Class cls, SEL name );
    IMP class_getMethodImplementation_stret ( Class cls, SEL name );
    
    // 类实例是否响应指定的selector
    BOOL class_respondsToSelector ( Class cls, SEL sel );
    

    class_addMethod的实现会覆盖父类的方法实现,但不会取代本类中已存在的实现,如果本类中包含一个同名的实现,则函数会返回NO。如果要修改已存在实现,可以使用method_setImplementation。一个Objective-C方法是一个简单的C函数,它至少包含两个参数—self和_cmd。所以,我们的实现函数(IMP参数指向的函数)至少需要两个参数

    void myMethodIMP(id self, SEL _cmd)
    {
        // implementation ....
    }
    

    与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在
    lass_getInstanceMethod、class_getClassMethod函数,与class_copyMethodList不同的是,这两个函数都会去搜索父类的实现。
    class_copyMethodList函数,返回包含所有实例方法的数组,如果需要获取类方法,则可以使用class_copyMethodList(object_getClass(cls), &count)(一个类的实例方法是定义在元类里面)。该列表不包含父类实现的方法。outCount参数返回方法的个数。在获取到列表后,我们需要使用free()方法来释放它。
    class_replaceMethod函数,该函数的行为可以分为两种:如果类中不存在name指定的方法,则类似于class_addMethod函数一样会添加方法;如果类中已存在name指定的方法,则类似于method_setImplementation一样替代原方法的实现。
    class_getMethodImplementation函数,该函数在向类实例发送消息时会被调用,并返回一个指向方法实现函数的指针。这个函数会比method_getImplementation(class_getInstanceMethod(cls, name))更快。返回的函数指针可能是一个指向runtime内部的函数,而不一定是方法的实际实现。例如,如果类实例无法响应selector,则返回的函数指针将是运行时消息转发机制的一部分。
    class_respondsToSelector函数,我们通常使用NSObject类的respondsToSelector:或instancesRespondToSelector:方法来达到相同目的。

    协议(objc_protocol_list)

    // 添加协议
    BOOL class_addProtocol ( Class cls, Protocol *protocol );
     
    // 返回类是否实现指定的协议
    BOOL class_conformsToProtocol ( Class cls, Protocol *protocol );
     
    // 返回类实现的协议列表
    Protocol * class_copyProtocolList ( Class cls, unsigned int *outCount );
    

    class_conformsToProtocol函数可以使用NSObject类的conformsToProtocol:方法来替代。
    class_copyProtocolList函数返回的是一个数组,在使用后我们需要使用free()手动释放。

    版本(version)

    // 获取版本号
    int class_getVersion ( Class cls );
     
    // 设置版本号
    void class_setVersion ( Class cls, int version );
    

    相关文章

      网友评论

          本文标题:OC 的Runtime 机制之类和对象

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