zoukankan      html  css  js  c++  java
  • iOS Class结构分析

    objc_class结构体

    类在OC中是objc_class的结构体指针

    typedef struct objc_class *Class;

    在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;

    下面我们来看一下objc_class的定义,我们在使用runtime以class为前缀的方法时主要就是针对这个结构体中的各个字段的。

    指向元类的指针(isa)

    在OC中所有的类其实也是一个对象,那么这个对象也会有一个所属的类,这个类就是元类也就是结构体里面isa指针所指的类。

    那什么是元类呢?
    元类的定义:元类就是类对象的类。每个类都有自己的元类,因为每个类都有自己独一无二的方法。

    简单点说就是:

    • 当你给对象发送消息时,消息是在寻找这个对象的类的方法列表。(实例方法)
    • 当你给类发消息时,消息是在寻找这个类的元类的方法列表。(类方法)

    那元类的类是什么呢?
    元类,就像之前的类一样,它也是一个对象。你也可以调用它的方法。自然的,这就意味着他必须也有一个类。

    所有的元类都使用根元类(继承体系中处于顶端的类的元类)作为他们的类。这就意味着所有NSObject的子类(大多数类)的元类都会以NSObject的元类作为他们的类

    根据这个规则,所有的元类使用根元类作为他们的类,根元类的元类则就是它自己。也就是说基类的元类的isa指针指向他自己。

    这里有一副图可以很好的展现这些关系:


     
     

    runtime方法

    // 判断给定的Class是否是一个元类
    BOOL class_isMetaClass ( Class cls );
    class_isMetaClass函数,如果是cls是元类,则返回YES;如果否或者传入的cls为Nil,则返回NO。

    指向父类的指针(super_class)

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

    // 获取类的父类
    Class class_getSuperclass ( Class cls );

    class_getSuperclass函数,当cls为Nil或者cls为根类时,返回Nil。不过通常我们可以使用NSObject类的superclass方法来达到同样的目的。

    类名(name)

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

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

    版本(version)

    版本相关的操作包含以下函数:

     

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

     

    实例变量大小(instance_size)

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

    成员变量(ivars)及属性

    objc_class中,所有的成员变量、属性的信息是放在链表ivars中的。ivars是一个数组,数组中每个元素是指向Ivar(变量信息)的指针。runtime提供了丰富的函数来操作这一字段。大体上可以分为以下几类:

     

     

    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 );

    这一种方法也是针对ivars来操作,不过只操作那些是属性的值。

     

    方法(methodLists)

     

    objc_method_list方法链表中存放的是该类的成员方法(-方法),类方法(+方法)存在meta-class的objc_method_list链表中。

     

    方法操作主要有以下函数:

    // 添加方法
    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 ....
    }

    与成员变量不同的是,我们可以为类动态添加方法,不管这个类是否已存在。

    另外,参数types是一个描述传递给方法的参数类型的字符数组,这就涉及到类型编码,我们将在后面介绍。

    这里我们的void的前面没有+、-号,因为只是C的代码。

     

    class_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:方法来达到相同目的。

     

    缓存(cache)

     

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

     

    协议(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()手动释放。

     

    objc_class结构体的应用

     

    没有实际应用的知识讲解都是耍流氓

     

    @property的本质

    这里有一个孙源的面试题是:@property 的本质是什么?ivar、getter、setter 是如何生成并添加到这个类中的。

    简单点说就是:@property = ivar + getter + setter;

    也就是生成实例变量及对应的存取方法。

    详细的回答请看面试题第六题。

    那这跟我们这里所讲的objc_class结构体有什么关系呢?

    因为@property对应的ivar、getter和setter都会对应添加到我们结构体中的ivar_list、method_list中。也就是说我们每次增加一个属性,系统都会在ivar_list添加一个成员变量的描述,在 method_list 中增加 setter 与 getter 方法的描述。

    其他Runtime结构体

    objc_object结构体

    除了类有对应的结构体,对象也有对应的结构体。

    typedef struct objc_object *id;

    id就是指向对象对应的结构体。对象的结构体只有 isa 指针,指向它所属的类。而类的结构体也有 isa 指针指向它的元类。

    所以在OC中objc_class 结构体是继承自 objc_object:

    struct objc_object {
        Class isa  OBJC_ISA_AVAILABILITY;
    };
    
    struct objc_class : objc_object {
        Class superclass;
        cache_t cache;             // formerly cache pointer and vtable
        class_data_bits_t bits;    // class_rw_t * plus custom rr/alloc flags
        class_rw_t *data() { 
            return bits.data();
        }
    }; 

    Category结构体

    Category的定义如下:

    typedef struct objc_category *Category;

     

    Category是一个objc_category结构体的指针,objc_category的定义如下:

    struct objc_category {
        char *category_name                                      OBJC2_UNAVAILABLE;
        char *class_name                                         OBJC2_UNAVAILABLE;
        struct objc_method_list *instance_methods                OBJC2_UNAVAILABLE;
        struct objc_method_list *class_methods                   OBJC2_UNAVAILABLE;
        struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
    }                                                            OBJC2_UNAVAILABLE;

    通过上面的结构体,大家可以很清楚的看出存储的内容。我们继续往下看,打开objc源代码,在 objc-runtime-new.h中我们可以发现如下定义:

    struct category_t {
        const char *name;
        classref_t cls;
        struct method_list_t *instanceMethods;
        struct method_list_t *classMethods;
        struct protocol_list_t *protocols;
        struct property_list_t *instanceProperties;
    };

    上面的定义需要提到的地方有三点:

     

    name 是指 class_name 而不是 category_name

    cls是要扩展的类对象,编译期间是不会定义的,而是在Runtime阶段通过name对应到对应的类对象

    instanceProperties表示Category里所有的properties,这就是我们可以通过objc_setAssociatedObject和objc_getAssociatedObject增加实例变量的原因,不过这个和一般的实例变量是不一样的

     

     

    作者:齐滇大圣

    链接:https://www.jianshu.com/p/73e454178e77

    來源:简书

  • 相关阅读:
    《ASP.NET Core跨平台开发从入门到实战》Web API自定义格式化protobuf
    .NET Core中文分词组件jieba.NET Core
    .NET Core 2.0及.NET Standard 2.0
    Visual Studio 2017 通过SSH 调试Linux 上.NET Core
    Visual Studio 2017 ASP.NET Core开发
    Visual Studio 2017正式版离线安装及介绍
    在.NET Core 上运行的 WordPress
    IT人员如何开好站立会议
    puppeteer(二)操作实例——新Web自动化工具更轻巧更简单
    puppeteer(一)环境搭建——新Web自动化工具(同selenium)
  • 原文地址:https://www.cnblogs.com/jiuyi/p/10213688.html
Copyright © 2011-2022 走看看