美文网首页
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语言基础

    一、面向对象 OC语言是面向对象的,c语言是面向过程的,面向对象和面向过程只是解决问题的两种思考方式,面向过程关注...

  • 编写高质量代码--第一章

    第一条 Object-C语言的起源 oc是面向对象语言 虽然oc是面向对象的,但是对比java c++这类面向对象...

  • oc-理解对类、对象等概念

    OC是基于C语言的面向对象的语言。C语言中没有对象的概念,为了便于开发者理解和使用面向对象的思想,OC将C语言中的...

  • 第一章:熟悉OC

    OC语言特性:-1. OC是在C的语言基础上添加了面向对象特性。 -2. OC与C++,JAVA等面向对象的语言类...

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

    objc让C语言面向对象的方式 简介:objc使用结构体让C语言支持面向对象。本文主要对这些结构体的功能和实际运行...

  • OC总结(1)

    1.如何理解OC C语言是一种面向过程的语言,OC则是面相对象的语言,所以想要理解OC就要先了解面向过程和面向对象...

  • 第一章: IOS优化(1) (Effective Object

    第一章: 熟悉OC 第一条: 了解OC的起源 一: OC面向对象语言特性 OC 和Java, C++面向对象语言...

  • iOS从零开发(第一期) --- Objective-C编程

    一 . Objective-C概述 OC语言的历史 Objective-C简称OC,扩充自C语言的面向对象编程语言...

  • iOS开发之OC语言基础

    Objective-C是面向对象的语言Objective-c: 面向对象的Object-c 简称:oc、obj-c...

  • OC—面向对象

    1.OC: Object- C ,面向对象的语言 2、OC和C的区别 <1> oc是 c的超集 ,oc是在c语言的...

网友评论

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

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