Runtime顾名思义为运行时,他其实是一套底层纯C语言的API,OC的代码最终都会被编译器转化为运行时代码,通过消息机制决定函数调用方式,这也是OC作为动态语言的基础,他使OC具备了面向对象性和动态性。
所谓动态性是基于c/c++这样的静态语言区别的,在c/c++中我们编译成什么就会执行什么,且不实现定义时会报错,而OC则不会,我们甚至可以使用方法调换技术,改变我们编译时执行的函数A,令其执行函数B。
那么我们之前写的OC代码是怎么和Runtime交互的,大概分为三种层次,OC源代码,NSObject方法,Runtime函数,交互程度是由高到低的。
下面探索一下OC对象的实质,他底层是怎么样的c语言代码。
这里的话需要下载一下runtime源码——Runtime
我们找到源码中关于类和对象的定义可以看到。
类和对象的本质都是结构体。
我们点进去对象这个结构体。
struct objc_object {
private:
// 私有成员变量: isa指针
isa_t isa;
// 自定义的一些成员变量
public:
// 一些共有方法.
}
可以看到这块一个系统自带的变量isa指针,点进去发现他是个共用体。
// MARK: - isa的声明
// 这里分析一下共用体
/* 共用体和结构体其实都可以定义很多变量,区别就是共用体占用的内存为变量最宽的内存。
共同体中所有的成员变量共同使用这一块内存,很节省空间,但容易覆盖
*/
union isa_t {
/// isa_t的构造函数
isa_t() { }
isa_t(uintptr_t value) : bits(value) { }
Class cls;
uintptr_t bits; // 八个字节 64位
#if defined(ISA_BITFIELD)
struct {
// isa 结构的定义
/* 其实所有的数据都存储在成员变量bits里面,这个结构体利用位域
来增加代码可读性,让我们看看bits对应的位上分别存储什么。
*/
ISA_BITFIELD; // defined in isa.h
};
#endif
};
# if __arm64__
# define ISA_MASK 0x0000000ffffffff8ULL
# define ISA_MAGIC_MASK 0x000003f000000001ULL
# define ISA_MAGIC_VALUE 0x000001a000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; /* isa是否经过优化,如果指为0,代表无优化,就是一个isa指针,
64位全部用来存储对象所属类的地址,如果为1,则采取当前位分配。*/ \
uintptr_t has_assoc : 1; /*判断有无关联对象,如果没有快速销毁对象。 */ \
uintptr_t has_cxx_dtor : 1; /*判断当前对象有无析构函数,更快销毁 */ \
uintptr_t shiftcls : 33; /*当前对象所属类的地址信息 */ \
uintptr_t magic : 6; /*标记调试时,当前对象是否初始化 */ \
uintptr_t weakly_referenced : 1; /*标记弱引用表里是否有当前对象弱指针数组,也就是是否被若指针指向,以及是否有弱引用*/ \
uintptr_t deallocating : 1; /*当前对象是否释放。 */ \
uintptr_t has_sidetable_rc : 1; /*引用计数表里面是否有当前对象的引用记数*/ \
uintptr_t extra_rc : 19/*对象的引用计数 - 1,存不下了就回放到引用计数表中*/
# define RC_ONE (1ULL<<45)
# define RC_HALF (1ULL<<18)
# elif __x86_64__
# define ISA_MASK 0x00007ffffffffff8ULL
# define ISA_MAGIC_MASK 0x001f800000000001ULL
# define ISA_MAGIC_VALUE 0x001d800000000001ULL
# define ISA_BITFIELD \
uintptr_t nonpointer : 1; \
uintptr_t has_assoc : 1; \
uintptr_t has_cxx_dtor : 1; \
uintptr_t shiftcls : 44; /*MACH_VM_MAX_ADDRESS 0x7fffffe00000*/ \
uintptr_t magic : 6; \
uintptr_t weakly_referenced : 1; \
uintptr_t deallocating : 1; \
uintptr_t has_sidetable_rc : 1; \
uintptr_t extra_rc : 8
# define RC_ONE (1ULL<<56)
# define RC_HALF (1ULL<<7)
# else
# error unknown architecture for packed isa
# endif
// SUPPORT
可见对象的本质是一个objc_object
结构体,该结构体的内部有一个固定的成员变量isa
,在64位操作系统以后对isa
做了优化,它不再是一个指针,而是一个isa_t
的共用体,其占用8个字节64位,在不同架构(arm64,x86)下各位段长度不一样,在arm64中,33位被用来存储对象所属类的地址信息,19位存储对象的引用计数-1这一操作,存不下的放到引用计数表里(存值范围为0-255),1位表示是否有若引用,其他位也有各种标记信息。
struct objc_class : objc_object {
// Class ISA;
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,内部存储方法,属性,遵循的协议等
}
// MARK: - class_rw_t结构声明
struct class_rw_t {
// Be warned that Symbolication knows the layout of this structure.
uint32_t flags;
uint32_t version;
// class_ro_t成员变量 ,只读性
const class_ro_t *ro;
method_array_t methods;//存储着该类的所有实例方法,包括分类
property_array_t properties;//存储着该类的所有属性,包括分类
protocol_array_t protocols;// 存储着该类所有遵守的协议信息,包括分类
Class firstSubclass;
Class nextSiblingClass;
char *demangledName;
}
// MARK: - class_ro_t结构声明
// 该类的只读信息ro-readonly
struct class_ro_t {
uint32_t flags;
uint32_t instanceStart;
uint32_t instanceSize;
#ifdef __LP64__
uint32_t reserved;
#endif
const uint8_t * ivarLayout;
const char * name;
method_list_t * baseMethodList;//存储该类本身的实例方法
protocol_list_t * baseProtocols;//本身遵守的协议
const ivar_list_t * ivars;// 存储着该类本身的成员变量信息
const uint8_t * weakIvarLayout;
property_list_t *baseProperties;// 存储该类本身的属性信息
method_list_t *baseMethods() const {
return baseMethodList;
}
};
可以看出类的本质还是一个 objc_class
类型结构体,只不过其内部结构套了两层rw
和ro
。
class_ro_t
:只读信息,内部存储着经过编译后一个类本身定义的所有实例方法,属性,协议,成员变量。
class_rw_t
是在运行时系统生成的,把ro里本身定义的信息复制到rw里,并把分类相关的信息合并到rw中,rw是可读可写的,这就解释了分类为什么不能给类增加成员变量。
所谓元类,是指一个类所属的类,我们每创建一个类,系统就会自动帮我们创建好该类所属的类——即元类。因此在OC里对象其实分为实例对象、类对象、元类对象三类,我们开发中经常说的“对象”其实是指狭义的对象一实例对象,知道了这一点就好理解了,实例对象有它所属的类-即一个类对象,类对象也有它所属的类–即一个元类对象,元类对象也有它所属的类–即基类的元类对象。
其实元类和类的本质都是objc_class结构体,只不过它们的用途不一样,类的methods成员变量里存储着该类所有的实例方法信息,而元类的methods成员变量里存储着该类所有的类方法信息。
所有元类都使用根元类作为他们的类,根元类的isa指针指向了它自己。
三者的关系。
使用最常见的这张图进行分析 instance(实例) Subclass(子类)
每一个实例变量的isa都指向自己所属的类,每一个类的isa都指向自己所属的元类,同时,父类的元类也是子类的元类的父类,父类的元类也是根元类的子类。而父类的元类和子类的元类都属于根元类。根元类的isa指向自己,同时根元类的父类也是自己的下属类(NSObject元类的父类是NSObject类)。
分类是OC的一个高级特性,我们一般用它给系统的类或者第三方库的类扩展方法,属性和协议,或者把一个类不同功能分散到不同的模块中实现。
在runtime中查看一下分类的源码。
// MARK: - category 完整声明结构
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;// 拓展的实例方法。
// Fields below this point are not always present on disk.
struct property_list_t *_classProperties;// 类拓展的协议
method_list_t *methodsForMeta(bool isMeta) {
if (isMeta) return classMethods;
else return instanceMethods;
}
property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
};
就是一个结构体,然后可以看到里面是没有成员变量列表存在的,这样可以解释分类为什么不能添加成员变量。
通过上面我们可以分析得到,我们知道一个类的实例方法存储在类里面,所有的类方法在元类里面,而对象调用方法isa指针先到相应的类的和元类,然后在其方法列表中查找,那么分类是这么实现这一功能的。
因为isa指针指向了分类的所属类,所以肯定不是这一套,正确的应该是编译器在运行时利用Runtime动态把分类的数据合并到类和元类,分类的数据在类本身数据之前,越晚编译的分类越在前面,所以如果分类里面有和类里面同名的方法,就会优先调用分类里的方法,如果多个分类有同名的方法,会优先调用后编译方法里面的方法,我们可以去Compile里控制分类编译顺序。
那么Runtime具体是怎么实现的?
运行时,系统读取镜像阶段,会读取所有的类,如果发现分类,遍历所有的分类,根据分类的cls指针找到它所属的类,重新组织一下这个类内部的结构——即合并分类的数据。
以下是runtime.new中的read_images方法,这个方法是处理镜像,这里取出了关于分类的部分。
// Discover categories.
for (EACH_HEADER) {
// 读取当前类所有的分类
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
// 倒序取出每一个分类
// 越晚编译的分类的实例方法列表反而在最前面
for (i = 0; i < count; i++) {
// 读取某一个分类
category_t *cat = catlist[i];
// 根据这个分类的cls指针找个这个分类所属的类
Class cls = remapClass(cat->cls);
// 处理cls指针缺失的特殊情况
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// 开始合并分类的数据
// 先将实例方法,协议,属性添加到基类里面
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// 取出当前分类(没有合并过的),合并实例方法,以及协议。
addUnattachedCategoryForClass(cat, cls, hi);
if (cls->isRealized()) {
// 合并到所属类的方法列表里面
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// 然后将拓展的类方法,类协议,类属性添加到基类里面
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
// 获取未合并的分类的信息,这里ISA指的所属类的元类
// 与上文提到的类方法存储在元类中符合
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
// 合并到元类中
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
这里有两个关键的函数。
1.addUnattachedCategoryForClass,这个函数就是对比当前分类是否被合并
static void addUnattachedCategoryForClass(category_t *cat, Class cls,
header_info *catHeader)
{
runtimeLock.assertLocked();
// DO NOT use cat->cls! cls may be cat->cls->isa instead
NXMapTable *cats = unattachedCategories();
category_list *list;
list = (category_list *)NXMapGet(cats, cls);
if (!list) {
list = (category_list *)
calloc(sizeof(*list) + sizeof(list->list[0]), 1);
} else {
list = (category_list *)
realloc(list, sizeof(*list) + sizeof(list->list[0]) * (list->count + 1));
}
list->list[list->count++] = (locstamped_category_t){cat, catHeader};
NXMapInsert(cats, cls, list);
}
2.remethodizeClass,这个方法就是合并数据的方法。
// 将未处理的分类数据添加到现在的类,修复所属类的方法列表,协议列表,属性列表。
// 更新类和其子类的方法缓存
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
isMeta = cls->isMetaClass();
// Re-methodizing: check for more categories
if ((cats = unattachedCategoriesForClass(cls, false/*not realizing*/))) {
if (PrintConnecting) {
_objc_inform("CLASS: attaching categories to class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
attachCategories(cls, cats, true /*flush caches*/);
free(cats);
}
}
/将方法列表、属性和协议从类别附加到类。//假设cat的分类都是加载并按加载顺序排序的,//最老的分类优先。
static void
attachCategories(Class cls, category_list *cats, bool flush_caches)
{
if (!cats) return;
if (PrintReplacedMethods) printReplacements(cls, cats);
bool isMeta = cls->isMetaClass();
// 开辟二维数组存放每个分类里面的某个列表。
// fixme rearrange to remove these intermediate allocations
method_list_t **mlists = (method_list_t **)
malloc(cats->count * sizeof(*mlists));
property_list_t **proplists = (property_list_t **)
malloc(cats->count * sizeof(*proplists));
protocol_list_t **protolists = (protocol_list_t **)
malloc(cats->count * sizeof(*protolists));
// Count backwards through cats to get newest categories first
int mcount = 0;
int propcount = 0;
int protocount = 0;
int i = cats->count;
bool fromBundle = NO;
// 可以看到这里时倒序遍历所有分类。
while (i--) {
// 获取一个分类
auto& entry = cats->list[i];
// 获取分类的类方法,存入二维数组
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
mlists[mcount++] = mlist;
fromBundle |= entry.hi->isBundle();
}
// 属性列表存入二维数组
property_list_t *proplist =
entry.cat->propertiesForMeta(isMeta, entry.hi);
if (proplist) {
proplists[propcount++] = proplist;
}
// 协议列表存入二维数组
protocol_list_t *protolist = entry.cat->protocols;
if (protolist) {
protolists[protocount++] = protolist;
}
}
// 将这个二维数组内所有一维数组的收地址存入rw类的methods成员变量指向的地址
auto rw = cls->data();
prepareMethodLists(cls, mlists, mcount, NO, fromBundle);
// 给当前类的实例方法列表附加所有分类的实例方法列表
rw->methods.attachLists(mlists, mcount);
free(mlists);
if (flush_caches && mcount > 0) flushCaches(cls);
// 存入属性
rw->properties.attachLists(proplists, propcount);
free(proplists);
// 存入协议
rw->protocols.attachLists(protolists, protocount);
free(protolists);
}
// 添加方法列表具体实现
// addedLists 所有分类的方法列表,就是二维数组
// addedCount 分类个数
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
// 获取原来的methods元素个数(元素为数组)
uint32_t oldCount = array()->count;
// 计算新的
uint32_t newCount = oldCount + addedCount;
// 重新为methods成员变量指向的数组分配内存,一个指针八个字节。
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
// 分配完内存后对内村数据进行移动和复制
// 先将旧的数据移动到新开辟的空间的最后面,可以内存覆盖
// 其实就是原来列表的首地址放在后面
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
// 再将新列表的新数据复制到新开辟的空间最前面,没有内存覆盖
// 新列表的首地址复制到前面
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
else if (!list && addedCount == 1) {
// 0 lists -> 1 list
list = addedLists[0];
}
else {
// 1 list -> many lists
List* oldList = list;
uint32_t oldCount = oldList ? 1 : 0;
uint32_t newCount = oldCount + addedCount;
setArray((array_t *)malloc(array_t::byteSize(newCount)));
array()->count = newCount;
if (oldList) array()->lists[addedCount] = oldList;
memcpy(array()->lists, addedLists,
addedCount * sizeof(array()->lists[0]));
}
}
这样就完成了将所有分类的数据合并到类的操作。最终类的方法列表,分类的在前面,而原来的方法列表在后面。