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

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语言面向对象。
  • 以上为查看源码和膜拜大神解释后自己的理解,不能保证完全正确,但整体上来看这样理解是基本正确的。

结束

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容