4。iOS 进阶知识—回顾iOS 深度好文

Objective-C的底层实现(类与对象)

2017-03-30  本文已影响494人  大雄記

概述

对象

Objective-C中,类和对象的底层数据结构,可以参考Objective-C底层数据结构。
其中对象是通过struct objc_object结构实现

typedef struct objc_object *id;

创建一个Son类继承自Father类,Father类继承自Grandfather类;这样的对象在运行时候,会产生如下图的结构形式

我们把这个转换成种更容易理解的数据组织形式


真正的内存形式是这样的


从对象内存形式上来看,对象内的变量成员,是从祖类继承而来(子成父业啊),在对象内部生成副本。从对象的内存组织来看,对象本身并不关心行为(对象的方法或实例方法),重点都在数据的组织上。


对象中的isa指针
对象的都有一个isa实例变量,它是从继承层次最高的NSObject继承而来,isa是表示对象的关键。在Objective-C中,是不是第一等对象,isa就是其标志,就好像汽车都有一个标识符号一样,看到这个标识就能想到是什么品牌的车了。

isa是一个指针,指向了该对象的类。实质上,同一个类的实例,都指向同一个类对象(类也是一种特殊对象)。类中包含了实例方法,也就是说,同一个类的所有实例共用了这些实例方法。消息就是发送给对象,对象转交给其isa指向类去处理,这种现象类似于当下火热的云计算。

Objective-C的这种设计,既可以友好地实现面向对象,又可以有效地节约内存。降低冗余数据,对象对方法的调用是通过isa间接去调用,这样就造成了方法调用的动态性,主要原因是:
- 1、一个对象并不晓得它能否应答一个方法,它本身既不包含方法的实现,也不包含有方法的指针,而是间接通过isa转到自己的类才能知道。
- 2、类中的实例方法是以链表形式存在,运行时候,可以修改链表中的实例方法,即可以增删改查,这与C中的函数默认都是extern的不同。

从上面的分析,isa的意义就更加重大,倘若没有isa,一个对象就跟charint类型等没啥区别了,不具备回应消息的本领。所以,isa是什么,就好比是古时候官员的乌纱帽,有乌纱帽,就有权力,乌纱帽没了,就是凡夫俗子。

类是通过struct objc_class结构实现的

typedef struct objc_class *Class;

在运行时,将会产生如下图这样的结构


Objective-C的世界里,一看到isa的第一个反应就是,咦,对象!是的,Objective-C中的类其实也是一种对象。

既然天地生的万物,那么天地又是何物?如果人类是上古神仙女娲所造,那女娲又因何而生呢?一般程序语言,对象的尽头都是自己生了自己。自己下个蛋,爬出来了自己,似乎是悖逆的,但确实如此。作为根类的NSObject就是这样一个家伙!这看起来确实很困惑,但是譬如生物学中的造血干细胞可谓细胞的制造厂,但是造血干细胞又是谁制造的呢,咦,也是造血干细胞。

首先,类对象也是一种对象,那么它也会有自己的行为,这种行为称作类方法。与一般的类实例一样,类对象也不具备处理类方法的能力,也是要借助isa找到它所属的类,既元类,去调用类方法,类对象本身也是专注于数据的存储和布局形式;可参考类和元类。

但与常规的类实例不同,一般而言类对象的字段是固定的。即它默认情况下总是包含isasuper_classnameversioninfoinstance_sizeivarsmethodListscacheprotocols

一个类的数据形式会是如下


而它的数据在内存中的组织形式也是线性的


我们知道,在Objective-C中,我们一般的定义形式是声明一个类的实例变量、属性、实例方法和类方法。并不能声明类变量,所以一般而言类对象的数据形式就是如上图的那些固定的字段。除了我们介绍的isa,剩余的字段的含义如下:

| 字段名 | 含义 |
| ----:---- | ----:---- |
| super_class | 指向父类的指针。因为Objective-C借鉴了SimTALK,在类的继承实现上,是通过一条继承链实现的。super_class就是整个继承链的核心字段 |
| name | 类的名字 |
| version | 版本 |
| info | 信息 |
| instance_size | 实例的内存大小 |
| ivars | 是一个指向实例变量列表的指针 |
| methodLists | 是一个指向实例方法列表的指针 |
| cache | 缓存了常用的实例方法 |
| protocols | 是一个指向协议列表的指针 |

我们可以这样简单地去解释运行时的Objective-C的数据结构的含义
- 1.对象与实例变量有关,对象自身存储着实例变量
- 2.类对象与实例方法有关,实例方法必须通过类对象才能知晓
- 3.元类与类方法有关, 类方法必须通过元类才能知晓

运行时的类和对象

运行时的类和对象如下图


一般而言
- 1.isa关于对象是什么类
- 2.super_class关于继承链
- 3.所有元类都有同样的元类,因为他们的isa都指向同一个根元类

苹果已经将代码开源了,我们可以从:ObjC runtime 浏览源代码,或点此下载源代码。
从哪里入手呢?那当然是最基本的类与对象。与C++相比,ObjC中的类与对象结构要简洁与一致得多(参考《深度探索C++对象模型》,你就知道C++中类与对象结构的复杂)。本文将详细讲解ObjC中类与对象的结构,下回将讲如何在runtime时操作类。
我们可以在/usr/include/objc/objc.hruntime.h中找到对classobject的定义:

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

Class是一个objc_class结构类型的指针;而id(任意对象)是一个objc_object结构类型的指针,其第一个成员是一个objc_class结构类型的指针。注意这里有一关键的引申解读:内存布局以一个objc_class指针为开始的所有东东都可以当做一个object来对待!那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;
};

objc_class结构体的各成员介绍如下:

isa:是一个objc_class类型的指针,看到这里,想起我前面的引申解读了没?内存布局以一个objc_class指针为开始的所有东东都可以当做一个object来对待!这 就是说objc_class或者说类其实也可以当做一个objc_object对象来对待!对象是对象,类也是对象,是不是有点混淆?别急,ObjC发明(or重用)了一个术语来区分这两种不同的对象:类对象(class object)与实例对象(instance object)。OK,名称混淆的问题解决,下面我将使用这两个术语来区分不同的对象,而使用“对象”这一术语来泛指所有的对象。ObjC还对类对象与实例对象中的isa所指向的类结构作了不同的命名:类对象中的isa指向类结构被称作metaclassmetaclass存储类的static类成员变量与static类成员方法(+开头的方法);实例对象中的isa指向类结构称作class(普通的),class结构存储类的普通成员变量与普通成员方法(-开头的方法)。

super_class:一看就明白,指向该类的父类呗!如果该类已经是最顶层的根类(如NSObjectNSProxy),那么super_class就为NULL

好,先中断一下其他类结构成员的介绍,让我们理清一下在继承层次中,子类、父类、根类(这些都是普通 class)以及其对应的metaclassisasuper_class之间关系:
规则一:类的实例对象的isa指向该类;该类的isa指向该类的metaclass
规则二:类的super_class指向其父类,如果该类为根类则值为NULL
规则三:metaclassisa指向根metaclass,如果该metaclass是根metaclass则指向自身;
规则四:metaclasssuper_class指向父metaclass,如果该metaclass是根metaclass则指向该metaclass对应的类;

好吧,文字总是那么乏力,有图有真相!


那么classmetaclass有什么区别呢?classinstance object的类类型。当我们向实例对象发送消息(实例方法)时,我们在该实例对象的class结构的methodlists中去查找响应的函数,如果没找到匹配的响应函数则在该class的父类中的methodlists去查找(查找链为上图的中间那一排)。如下面的代码中,向string实例对象发送lowercaseString消息,会在NSString类结构的methodlists中去查找lowercaseString的响应函数。

NSString * string;
[string lowercaseString];

metaclassclass object的类类型。当我们向类对象发送消息(类方法)时,我们在该类对象的metaclass结构的methodlists中去查找响应的函数,如果没有找到匹配的响应函数则在该metaclass的父类中的methodlists去查找(查找链为上图的最右边那一排)。如下面的代码中,向NSString类对象发送stringWithString消息,会在NSStringmetaclass类结构的methodlists中去查找stringWithString的响应函数。

[NSString stringWithString:@"string"];

好,至此我们明白了类的结构层次,让我们接着看类结构中的其他成员。

name:一个C字符串,指示类的名称。我们可以在运行期,通过这个名称查找到该类(通过:id objc_getClass(const char *aClassName))或该类的metaclass(id objc_getMetaClass(const char *aClassName))

version:类的版本信息,默认初始化为0。我们可以在运行期对其进行修改(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

methodLists:与info的一些标志位有关,CLS_METHOD_ARRAY标识位决定其指向的东西(是指向单个objc_method_list还是一个objc_method_list指针数组),如果info设置了CLS_CLASSobjc_method_list存储实例方法,如果设置的是CLS_META则存储类方法;

cache:指向objc_cache的指针,用来缓存最近使用的方法,以提高效率;

protocols:指向objc_protocol_list的指针,存储该类声明要遵守的正式协议。

总结:
ObjC为每个类的定义生成两个objc_class,一个即普通的class,另一个即metaclass。我们可以在运行期创建这两个objc_class数据结构,然后使用objc_addClass动态地创建新的类定义。这个够动态够强大的吧?下回讲演示如何在运行期动态创建新类。

参考

原文地址:Objective-C的底层实现(类与对象)

上一篇 下一篇

猜你喜欢

热点阅读