美文网首页
OC让C语言面向对象的方式

OC让C语言面向对象的方式

作者: BangRaJun | 来源:发表于2018-09-10 15:55 被阅读0次

    objc让C语言面向对象的方式

    简介:objc使用结构体让C语言支持面向对象。本文主要对这些结构体的功能和实际运行时的机制做一个概括,有了这些底层机制会更加轻松地理解一些高级的机制,比如类别为什么可以添加方法而不能添加属性、动态绑定变量、类别天加的方法会“覆盖”原有方法等。

    Class和Object

    • 在<objc/objc.h>中定义了objc_object。
    • 在<objc/runtime.h>中定义了objc_class。
    • 其他相关的结构体也都在这两个文件中可以找到。
    • 如下(在objc/runtime.h中):
    struct objc_class {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    
    #if !__OBJC2__
        Class _Nullable super_class                              OBJC2_UNAVAILABLE;
        const char * _Nonnull name                               OBJC2_UNAVAILABLE;
        long version                                             OBJC2_UNAVAILABLE;
        long info                                                OBJC2_UNAVAILABLE;
        long instance_size                                       OBJC2_UNAVAILABLE;
        struct objc_ivar_list * _Nullable ivars                  OBJC2_UNAVAILABLE;
        struct objc_method_list * _Nullable * _Nullable methodLists                    OBJC2_UNAVAILABLE;
        struct objc_cache * _Nonnull cache                       OBJC2_UNAVAILABLE;
        struct objc_protocol_list * _Nullable protocols          OBJC2_UNAVAILABLE;
    #endif
    
    } OBJC2_UNAVAILABLE;
    /* Use `Class` instead of `struct objc_class *` */
    
    
    • 定义了一种objc_class的结构体,并使用Class代替结构体指针,具体如下(在objc/objc.h中):
    /// An opaque type that represents an Objective-C class.
    typedef struct objc_class *Class;
    
    
    • 使用的[self Class]就可以获取这种结构体指针,这个结构体指针指向的是self类的结构体,也就是实例的isa指针。
    • 在objc_class结构体中定义的主要内容有:isa指针、super指针、char*类型的类名、long型的实例大小、变量列表、方法列表、缓存、遵守的协议列表。
    • 依次介绍这些必要元素的作用:
    1. isa意思是这个class是个什么,通常实例的isa指针是指向类,而类(类对象)的isa指针指向类的原类,对于实例、类、原类,我个人是以他们的功能区分的,他们分别负责存储一个完整类的三个抽象层次的东西:属性、对象方法、类方法:在实例中主要存储着属性、在类中存储着实例方法(开头为-()的方法)、在原类中存储着类方法(开头为+()的方法),通常我们在h文件中声明的时候也都是声明这三种东西,而他们在抽象层次上是存储在三个位置的。
    2. super指针就是指向自身父类(对应的类结构体)的指针。
    3. name就是这个类的名字(当我们手动创建一个和KVO派生出的子类名字相同的类时编译不会报错,而运行时会报错,因此OC在区分类时就是靠名字区分的)。
    4. instance_size,就是一个对象占内存的大小,当我第一次看到实例结构体的定义时(见下面代码)让我大跌眼镜,为什么一个实例的结构体只有一个变量还是一个必不可少的isa指针,那我们定义的属性的值都装载哪里?肯定不会装载class结构体里面,因为class结构体是唯一的,而之后看到struct objc_ivar结构体的定义时才恍然大悟(见下下面代码),定义变量的结构体中有变量名、变量类型、offset和space,offset定义了这个实例的某个变量的地址相对于这个实例地址的偏移量,比如说一个实例的地址是10000,他的第一个变量是NSInteger,那么这个变量的offset就是0,space就是8,那么第二个变量不管是什么,他的offset都是8、他的space根据自己的类型大小不同。需要找第一个变量时就去10000+0的地址去找,需要找第二个变量时就去10000+8的地址去找。因此,一个objc_object结构体指针只会透露两个信息,一个是他的地址、一个是他的父类,当需要访问里面详细的信息时都需要根据class的规定和自身地址去寻址,从而查找变量。因此这个instance_size也就相当于varlist中所有变量的space之和了。
    struct objc_object {
        Class _Nonnull isa  OBJC_ISA_AVAILABILITY;
    };
    
    struct objc_ivar {
        char * _Nullable ivar_name                               OBJC2_UNAVAILABLE;
        char * _Nullable ivar_type                               OBJC2_UNAVAILABLE;
        int ivar_offset                                          OBJC2_UNAVAILABLE;
    #ifdef __LP64__
        int space                                                OBJC2_UNAVAILABLE;
    #endif
    }   
    
    1. var_list,就是上面所说一个objc_ivar结构体的列表,包含了所有变量的信息。
    2. method_list,类似变量列表,这个是方法列表,包含所有方法的信息,方法结构体的定义如下(见下面代码)。每个结构体包含:方法名(SEL)、返回类型、和方法实现(IMP)。SEL和IMP都是typedef定义的缩写,找到其定义(见下下面代码),更深一层没有找到对于objc_selector结构体的定义,可以姑且就把他看成是一个字符串用来标记方法的名字,而对于IMP可以清晰看它就是一个C的函数指针,这样就可以把一个名字和一个指针对应起来的,只要我给出名字,就可以得到这个名字对应实现的指针,然后执行那里相应的代码。(这里忽然想到categary相关的一些理解,我们在categary中添加方法的时候总是在load时把方法添加到这个list中,如果有了同名方法,并不会把之前的覆盖掉,而是直接添加到list的最前面,因此每次使用selector查指针的时候都是先查到前面那个,因此就好像把原有方法覆盖掉了一样,但是OC是不推荐这样做的,因此这样会导致我们即使不想引入类别,也会不得不调用类别中的同名方法,从而使原有方法没法调用;另一方面,类别中之所以不能添加属性是因为我们上面一条所说的instance_size是类一开始load的时候就会根据varlist中的变量个数和大小确定好了,之后再添加属性会可能会出现访问越界的问题,因此不能添加属性,而动态绑定的变量不会出现在varlist中也不会改变一个实例的大小,因此可以用这种方法实现“假装”添加属性,绑定变量的绑定关系保存在全局的一个哈希表中,而不是实例里面)。
    struct objc_method {
        SEL _Nonnull method_name                                 OBJC2_UNAVAILABLE;
        char * _Nullable method_types                            OBJC2_UNAVAILABLE;
        IMP _Nonnull method_imp                                  OBJC2_UNAVAILABLE;
    } 
    
    /// An opaque type that represents a method selector.
    typedef struct objc_selector *SEL;
    
    /// A pointer to the function of a method implementation. 
    #if !OBJC_OLD_DISPATCH_PROTOTYPES
    typedef void (*IMP)(void /* id, SEL, ... */ ); 
    #else
    typedef id _Nullable (*IMP)(id _Nonnull, SEL _Nonnull, ...); 
    #endif
    
    1. cache缓存结构体,cache装的什么?cache的定义(见下方代码),里面有个Method数组,Method就是我们之前看到的方法结构体,因此Cache是用来存储Method的。事实上当我们创建一个类的时候会写很多方法在里面,而runtime在查找这些方法的时候是根据SEL(方法名)一个一个对照着去查找的,而有些不常用的方法总是需要经过遍历,很浪费时间,因此就有了这个缓存,里面装的是那些常用的方法,每次只需要遍历一小部分方法名就可以快速找到对应的函数指针。
    struct objc_cache {
        unsigned int mask /* total = mask + 1 */                 OBJC2_UNAVAILABLE;
        unsigned int occupied                                    OBJC2_UNAVAILABLE;
        Method _Nullable buckets[1]                              OBJC2_UNAVAILABLE;
    };
    
    typedef struct objc_method *Method;
    
    1. protocol_list即是我们平时用尖括号围起来的协议了,其定义如下(见下方代码),可以看到protocollist中装的Protocol其实就是objc_object,这个没有查到特别具体的解释,我自己的理解是这些object指针指向的是类对象,从这样的类对象中也可以读到name,这些name被当做协议名而不是类名,而在方法列表中只能读取到方法名SEL,不能读取到具体的IMP实现指针,因此protocol和class是相同的存储形式,根据不同的读取规则产生了不同的作用。
    #ifdef __OBJC__
    @class Protocol;
    #else
    typedef struct objc_object Protocol;
    #endif
    
    • 最终,根据以上的这些结构体,OC成功实现了让C语言面向对象。
    • 以上为查看源码和膜拜大神解释后自己的理解,不能保证完全正确,但整体上来看这样理解是基本正确的。

    结束

    相关文章

      网友评论

          本文标题:OC让C语言面向对象的方式

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