runtime
Runtime
Runtime是iOS动态特性的重要体现,也是iOS的内部核心之一。几乎所有的oc代码,底层都以基于runtime。其源码由c和汇编语言编写。
本文以及接下来的runtime的文章都是基于objc-750版本的源码
前沿
一些常见而重要的关键字:
typedef struct objc_object *id;
typedef struct objc_class *Class;
typedef struct objc_selector *SEL;
typedef struct objc_method *Method;
id
id 就是 OC 对象,它是指向某个类的实例的指针。定义如下:
struct objc_object {
Class isa;
};
通过源码可以看到 id 就是 objc_object *的别名,为一个指向objc_object 结构体的指针,而 objc_object 实际上就包含了 isa 指针和一些方法。根据 isa 指针就可以找到对象所属的类,isa 是一个非常重要而核心的东西,oc的动态方法都基于它实现。
KVO 的实现机理就是将被观察对象的 isa 指针指向一个中间类而不是真实类型
class
class 才是 oc 对象或方法最终的集合,上文 isa 就是 class 的指针, class 的定义如下:
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;
从 objc_class
可以看到,一个运行时类中关联了它的父类指针(super_class)、类名(name)、成员变量(ivars)、方法(methodLists)、缓存(cache)以及附属的协议(protocols)。
其中 objc_ivar_list
和 objc_method_list
分别定义 如下:
struct objc_ivar_list {
int ivar_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_ivar ivar_list[1] OBJC2_UNAVAILABLE;
}
struct objc_method_list {
struct objc_method_list * _Nullable obsolete OBJC2_UNAVAILABLE;
int method_count OBJC2_UNAVAILABLE;
#ifdef __LP64__
int space OBJC2_UNAVAILABLE;
#endif
/* variable length structure */
struct objc_method method_list[1] OBJC2_UNAVAILABLE;
}
objc_ivar_list 和 objc_method_list 分别保存了成员和方法的数组,由此可见我们其实可以动态的给一个 class 添加方法, 这也是 catgory 的实现思路。 运行时,category 会将定义的方法放入到方法列表的前头,因而会覆盖原来的方法(实际上没有覆盖, 只是 category 的方法在列表前, 被优先调用了)。
需要注意的是 objc_class 也包含了 isa 指针, 这说明 objc_class 也是 oc 对象, 其所属的类称作元类(Meta Class),Meta Class 表述了类对象本身所具备的元数据。
SEL
SEL(selector)是方法选择器,它对方法名进行包装,以便找到对应的方法实现。它的数据结构是:
typedef struct objc_selector *SEL;
关于objc_selector的源码暂时未找到,不过依然可以大致理解其作用和特点:
selector是个映射到方法的 C 字符串,你可以通过 Objc 编译器器命令@selector()
来获取一个方法的选择器。
不同类中相同名字的方法所对应的 selector 是相同的,由于变量的类型不同,所以不会导致它们调用方法实现混乱。
Method
Method 代表类中某个方法的类型:
struct objc_method {
SEL _Nonnull method_name OBJC2_UNAVAILABLE;
char * _Nullable method_types OBJC2_UNAVAILABLE;
IMP _Nonnull method_imp OBJC2_UNAVAILABLE;
}
typedef struct objc_method *Method;
由此可见 Method 保存了SEL 和 IMP 的联系。
Ivar
Ivar
是表示成员变量的类型:
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
}
typedef struct objc_ivar *Ivar;
其中 ivar_offset
是基地址偏移字节, 我们对 ivar 的访问就可以通过 对象地址 + ivar偏移字节
的方法。
` 个人理解:这里侧面告诉我们不能动态添加成员,因为会造成成员列表里的成员 偏移地址 混乱。`
IMP
IMP本质是一个函数指针,是由编译器生成的。当发起一个 ObjC 消息之后,最终它会执行的那段代码,就是由这个函数指针指定的。而 IMP
这个函数指针就指向了这个方法的实现。
如果得到了执行某个实例某个方法的入口,我们就可以绕开消息传递阶段,直接执行方法。
Cache
typedef struct objc_cache *Cache
struct objc_cache {
unsigned int mask /* total = mask + 1 */ OBJC2_UNAVAILABLE;
unsigned int occupied OBJC2_UNAVAILABLE;
Method buckets[1] OBJC2_UNAVAILABLE;
};
当实例对象接收到一个消息时,它不会直接在 isa 指针指向的类的方法列表中遍历查找能够响应的方法,因为每次都要查找效率太低了,而是优先在 Cache 中查找。
Runtime 系统会把被调用的方法存到 Cache 中,如果一个方法被调用,那么它有可能今后还会被调用,下次查找的时候就会效率更高。就像计算机组成原理中 CPU 绕过主存先访问 Cache 一样。
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);
};
从源码基本可以看出我们平时使用categroy的方式,对象方法,类方法,协议,和属性(不是成员)都可以找到对应的存储方式。并且我们发现分类结构体中是不存在成员变量的,因此分类中是不允许添加成员变量的。
由于method是基于函数名称和函数指针的联系的,所以Category中名称相同而返回值不同的方法依然会发生覆盖,具体可参考 struct objc_method 的定义
isa
关于isa 指针先看一张图: 先清楚几个概念:
- 实例的实例方法函数存在类结构体中
- 类方法函数存在metaclass结构体中
- metaClass 由汇编或者编译器生成(猜想)
- object-c 通过isa和metalClass的关系链来实现消息灵活传递机制
isa 本质是一个联合体,其构成如下:
union isa_t { Class cls; uintptr_t bits; struct { uintptr_t nonpointer : 1;//->表示使用优化的isa指针 uintptr_t has_assoc : 1;//->是否包含关联对象 uintptr_t has_cxx_dtor : 1;//->是否设置了析构函数,如果没有,释放对象更快 uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000 ->类的指针 uintptr_t magic : 6;//->固定值,用于判断是否完成初始化 uintptr_t weakly_referenced : 1;//->对象是否被弱引用 uintptr_t deallocating : 1;//->对象是否正在销毁 uintptr_t has_sidetable_rc : 1;//1->在extra_rc存储引用计数将要溢出的时候,借助Sidetable(散列表)存储引用计数,has_sidetable_rc设置成1 uintptr_t extra_rc : 19; //->存储引用计数 }; };
资料
苹果runtime的代码已经开源, 现在可以在网上找到各个版本的源代码,甚至还可以自己编译runtime来debug,例如以下为我编译的runtime工程:
Runtime: 苹果runtime源码编译后的工程
既已览卷至此,何不品评一二: