2019-10-15数组
类的方法包括实例方法(instance method)和类方法(class method),二者保存在彻底不一样的地方,实例方法保存在类的方法列表中,类方法保存在元类的方法列表中。在Runtime源代码解读(实现面向对象初探)中已介绍过方法的基本数据结构objc_method
,以及方法的基本响应链,本文介绍方法列表的具体实现原理。缓存
在 Runtime源代码解读2(类和对象)中介绍过:类的class_ro_t
包含method_list_t
类型的baseMethodList
成员;类的class_rw_t
包含method_array_t
类型的methods
成员。二者一样保存方法列表,却使用不一样的数据结构,那么它们之间有什么关系呢?因为class_ro_t
是保存的基本上是编译时决议的数据,baseMethodList
显然是保存类定义时所定义的方法,这些方法是编译时决议的。而class_rw_t
的methods
实际上才是类的完整方法列表,并且class_rw_t
的methods
包含了class_ro_t
的baseMethodList
。bash
类的class_rw_t
保存方法列表保存形式并非简单的一维数组结构,而是二维数组结构,这是为了区分类构建阶段定义的基本方法,以及不一样分类之间的定义的方法。objc_class
保存方法列表的数组结构是method_array_t
类,method_array_t
继承list_array_tt
模板类。数据结构
Runtime 定义list_array_tt
类模板表示二维数组容器。list_array_tt
保存的数据主体是一个联合体,包含list
和arrayAndFlag
成员,表示:容器要么保存一维数组,此时联合体直接保存一维数组的地址,地址的最低位必为0
;要么保存二维数组,此时联合体保存二维数组的首个列表元素的地址,且最低位置为1
。调用hasArray()
方法能够查询容器是否保存的是二维数组,返回arrayAndFlag & 1
,即经过最低位是否为1
进行判断;调用list_array_tt
的array()
方法能够获取二维数组容器的地址,返回arrayAndFlag & ~1
,即忽略最低位。函数
当联合体保存二维数组时,联合体的arrayAndFlag
指向list_array_tt
内嵌定义的array_t
结构体。该结构体是简单的一维数组容器,但其元素为指向列表容器的指针,所以array_t
的本质是二维数组。调用array_t
的byteSize()
能够返回列表容器占用的总字节数,为array_t
结构体自己的size
与保存列表元素的连续内存区块的size
之和。post
// 二维数组容器模板
template <typename Element, typename List>
class list_array_tt {
// 定义二维数组的外层一维数组容器
struct array_t {
uint32_t count;
List* lists[0];
static size_t byteSize(uint32_t count) {
return sizeof(array_t) + count*sizeof(lists[0]);
}
// 计算容器占用字节数
size_t byteSize() {
return byteSize(count);
}
};
protected:
// 内嵌迭代器的定义(后文 1.1.1 节介绍)
...
private:
union {
List* list; // 要么指向一维数组容器
uintptr_t arrayAndFlag; // 要么指向二维数组容器
};
// 容器是否保存的是二维数组
bool hasArray() const {
return arrayAndFlag & 1;
}
// 获取容器保存的二维数组的地址
array_t *array() {
return (array_t *)(arrayAndFlag & ~1);
}
void setArray(array_t *array) {
arrayAndFlag = (uintptr_t)array | 1;
}
public:
// 计算方法列表二维数组容器中全部方法的数量
uint32_t count() {
uint32_t result = 0;
for (auto lists = beginLists(), end = endLists();
lists != end;
++lists)
{
result += (*lists)->count;
}
return result;
}
// 获取方法列表二维数组容器的起始迭代器
iterator begin() {
return iterator(beginLists(), endLists());
}
// 获取方法列表二维数组容器的结束迭代器
iterator end() {
List **e = endLists();
return iterator(e, e);
}
// 获取方法列表二维数组容器包含的方法列表数量
uint32_t countLists() {
if (hasArray()) {
return array()->count;
} else if (list) {
return 1;
} else {
return 0;
}
}
// 获取方法列表二维数组容器的起始地址
List** beginLists() {
if (hasArray()) {
return array()->lists;
} else {
return &list;
}
}
// 获取方法列表二维数组容器的结束地址
List** endLists() {
if (hasArray()) {
return array()->lists + array()->count;
} else if (list) {
return &list + 1;
} else {
return &list;
}
}
void attachLists(List* const * addedLists, uint32_t addedCount) {
// 后文 1.1.2 节介绍
...
}
void tryFree() {
// 后文 1.1.2 节介绍
...
}
template<typename Result> Result duplicate() {
// 后文 1.1.2 节介绍
...
}
};
复制代码
list_array_tt
容器模板的内嵌迭代器iterator
类的定义以下,包含三个成员:ui
lists
:当前迭代到的数组的位置;listsEnd
:二维数组的外层容器的结尾;m
:当前迭代到的数组 中的元素的位置;mEnd
:当前迭代到的数组的结尾;注意:构建
list_array_tt
的迭代器时,只能从方法列表到方法列表,不能从容器中的方法列表的某个元素的迭代器开始迭代。this
class iterator {
List **lists;
List **listsEnd;
typename List::iterator m, mEnd;
public:
// 构建从begin指向的列表,到end指向的列表的迭代器
iterator(List **begin, List **end)
: lists(begin), listsEnd(end)
{
if (begin != end) {
m = (*begin)->begin();
mEnd = (*begin)->end();
}
}
const Element& operator * () const {
return *m;
}
Element& operator * () {
return *m;
}
bool operator != (const iterator& rhs) const {
if (lists != rhs.lists) return true;
if (lists == listsEnd) return false; // m is undefined
if (m != rhs.m) return true;
return false;
}
//迭代时,若达到当前数组的结尾,则切换到下一个数组的开头
const iterator& operator ++ () {
assert(m != mEnd);
m++;
if (m == mEnd) {
assert(lists != listsEnd);
lists++;
if (lists != listsEnd) {
m = (*lists)->begin();
mEnd = (*lists)->end();
}
}
return *this;
}
};
复制代码
list_array_tt
容器模板的三个操做类型的公开方法以下:编码
attachLists(...)
:将列表元素添加到二维数组容器的开头,注意到list_array_t
没有定义构造函数,这是由于构造逻辑均在attachLists(...)
中,包含容器从空转化为保存一位数组,再转化为保存二维数组的处理逻辑;tryFree()
:释放二维数组容器占用内存;duplicate(...)
:复制二维数组容器;// 向二维数组容器中添加列表
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// 当容器中保存的是二维数组
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
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) {
// 当容器中无任何方法,直接将list成员指向addedLists
list = addedLists[0];
}
else {
// 当容器中保存的是一维数组
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]));
}
}
// 释放容器占用的内存空间
void tryFree() {
if (hasArray()) {
for (uint32_t i = 0; i < array()->count; i++) {
try_free(array()->lists[I]);
}
try_free(array());
}
else if (list) {
try_free(list);
}
}
// 拷贝容器
template<typename Result>
Result duplicate() {
Result result;
if (hasArray()) {
array_t *a = array();
result.setArray((array_t *)memdup(a, a->byteSize()));
for (uint32_t i = 0; i < a->count; i++) {
result.array()->lists[i] = a->lists[i]->duplicate();
}
} else if (list) {
result.list = list->duplicate();
} else {
result.list = nil;
}
return result;
}
复制代码
类的方法列表信息保存在class_rw_t
结构体的methods
成员中,类型为method_array_t
。method_array_t
是按list_array_tt
模板构建,以method_t
(方法)为元素,以method_list_t
(方法列表)为列表容器的二维数组容器,用于存储类的方法列表。method_list_t
继承自entsize_list_tt
顺序表模板,entsize_list_tt
在 Runtime源代码解读2(类和对象) 介绍过,是具备固定类型元素的顺序表容器,注意到FlagMask
指定为0x03
,所以method_list_t
的entsizeAndFlags
成员最低两位预留有特殊功能:若最低两位均为1
,表示该方法列表已排序。spa
由method_array_t
中扩展list_array_tt
的方法的方法名可见,method_array_t
是为 category 量身定作的,显然 category 中定义的全部方法都存储在该容器中,每一个 category 定义的方法对应method_array_t
二维数组容器中的一个元素,也就是一个方法列表method_list_t
结构体的指针。扩展的方法以下:
beginCategoryMethodLists()
:指向容器的第一个数组;endCategoryMethodLists(Class cls)
:当类的class_rw_t
中不存在baseMethodList
时,直接返回容器最后一个数组,当存在baseMethodList
时,返回容器的倒数第二个数组。static uint32_t fixed_up_method_list = 3;
// 方法列表
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0x3> {
// 查询方法列表是否已排序
bool isFixedUp() const {
return flags() == fixed_up_method_list;
}
// 标记方法列表已排序
void setFixedUp() {
runtimeLock.assertLocked();
assert(!isFixedUp());
entsizeAndFlags = entsize() | fixed_up_method_list;
}
// 新增返回方法在顺序表中的索引值的方法
uint32_t indexOfMethod(const method_t *meth) const {
uint32_t I =
(uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
assert(i < count);
return I;
}
};
// 方法列表二维数组容器
class method_array_t :
public list_array_tt<method_t, method_list_t>
{
typedef list_array_tt<method_t, method_list_t> Super;
public:
method_list_t **beginCategoryMethodLists() {
return beginLists();
}
method_list_t **endCategoryMethodLists(Class cls) {
method_list_t **mlists = beginLists();
method_list_t **mlistsEnd = endLists();
if (mlists == mlistsEnd || !cls->data()->ro->baseMethods())
{
return mlistsEnd;
}
return mlistsEnd - 1;
};
method_array_t duplicate() {
return Super::duplicate<method_array_t>();
}
};
复制代码
在 Runtime源代码解读2(类和对象) 中介绍过类的加载过程。从 class realizing 时调用的methodizeClass(...)
函数的处理逻辑能够看出:class_rw_t
中的method_array_t
容器保存了类的完整方法列表,包括静态编译的类的基本方法、运行时决议的 category 中的方法以及运行时动态添加的方法。并且class_rw_t
中method_array_t
容器的最后一个数组实际上就是class_ro_t
的baseMethodList
。
再结合 1.1.2 介绍的list_array_tt
的attachLists(...)
方法逻辑,能够基本了解方法列表容器的工做机制。当使用class_addMethod(...)
动态添加类,或者应用加载阶段加载 category 时,均调用了该方法。因为attachLists(...)
添加方法时,将方法添加到容器的开头,将原有的method_list_t
集体后移,所以类的同名方法的IMP
的优先级从高到低排序以下:
class_addMethod(...)
动态添加的方法;类的方法列表的结构可总结以下图所示。其中绿色表示method_list_t
容器,蓝色表示保存method_t
结构体,黄色表示指向method_list_t
结构体的指针。上图为method_array_t
保存一维数组容器(method_list_t
)时的内存结构,此时method_array_t
的联合体list
成员有效;下图为method_array_t
保存二维数组容器时的内存结构,此时method_array_t
的联合体arrayAndFlag
成员有效。
运行时调用class_addMethod (...)
函数能够给类动态添加方法,调用class_replaceMethod(...)
能够替换方法的实现。实际上二者都调用了addMethod(...)
函数,原理是先根据传入的方法名、方法IMP、类型编码构建method_t
,而后新建method_list_t
方法列表容器将method_t
添加到其中,最后调用attachList(...)
将方法列表添加到类的class_rw_t
的methods
方法列表二维数组容器中。
class_addMethod (...)
的源代码以下:
// 添加方法
BOOL
class_addMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return NO;
rwlock_writer_t lock(runtimeLock);
return ! addMethod(cls, name, imp, types ?: "", NO);
}
// 替换方法实现
IMP
class_replaceMethod(Class cls, SEL name, IMP imp, const char *types)
{
if (!cls) return nil;
rwlock_writer_t lock(runtimeLock);
return addMethod(cls, name, imp, types ?: "", YES);
}
static IMP
addMethod(Class cls, SEL name, IMP imp, const char *types, bool replace)
{
IMP result = nil;
runtimeLock.assertWriting();
assert(types);
assert(cls->isRealized());
method_t *m;
if ((m = getMethodNoSuper_nolock(cls, name))) {
// 若方法已经存在
if (!replace) {
result = m->imp;
} else {
result = _method_setImplementation(cls, m, imp); // 设置方法IMP
}
} else {
// 若方法不存在,则构建方法列表容器
method_list_t *newlist;
newlist = (method_list_t *)calloc(sizeof(*newlist), 1);
newlist->entsizeAndFlags =
(uint32_t)sizeof(method_t) | fixed_up_method_list;
newlist->count = 1;
newlist->first.name = name;
newlist->first.types = strdup(types);
if (!ignoreSelector(name)) {
newlist->first.imp = imp;
} else {
newlist->first.imp = (IMP)&_objc_ignored_method;
}
prepareMethodLists(cls, &newlist, 1, NO, NO); // 后文 2.1 中详细介绍
// 添加方法到类的class_rw_t的methods中
cls->data()->methods.attachLists(&newlist, 1);
flushCaches(cls); // 后文 2.2中详细介绍
result = nil;
}
return result;
}
// 设置方法IMP
static IMP
_method_setImplementation(Class cls, method_t *m, IMP imp)
{
runtimeLock.assertWriting();
if (!m) return nil;
if (!imp) return nil;
// 对于打开ARC的编译环境,忽略retain/release等MRC方法
if (ignoreSelector(m->name)) {
return m->imp;
}
IMP old = m->imp;
m->imp = imp;
flushCaches(cls); // 清空cls类的方法缓冲
updateCustomRR_AWZ(cls, m); // 对retain/release等MRC方法,以及allocWithZone方法的特殊处理
return old;
}
复制代码
向类动态添加方法,类的方法列表结构先后对比图示以下。其中红色标记部分为新增结构。处理过程为:将添加方法封装到method_list_t
容器,而后插入到method_array_t
的外层一位数组开头,其余元素总体后移,外层一位数组长度增长1
。
addMethod(...)
函数中还调用了prepareMethodLists(...)
函数,该方法主要调用了fixupMethodList(...)
函数。fixupMethodList(...)
作了三件事情:
sel_registerNameNoLock
函数将方法列表中全部方法的SEL
注册到内存,注册的内存实际是以 方法名字符串 为键值,将SEL
插入到系统维护的namedSelectors
哈希表中;isFixedUp
,表示方法列表已排序;之因此对方法列表排序,是为了使方法列表支持二分查找,从而下降经过SEL
在方法列表中搜索IMP
的时间复杂度。prepareMethodLists(...)
函数相关代码以下:
static void
prepareMethodLists(Class cls, method_list_t **addedLists, int addedCount,
bool baseMethods, bool methodsFromBundle)
{
if (addedCount == 0) return;
for (int i = 0; i < addedCount; i++) {
method_list_t *mlist = addedLists[I];
assert(mlist);
// Fixup selectors if necessary
if (!mlist->isFixedUp()) {
fixupMethodList(mlist, methodsFromBundle, true/*sort*/);
}
}
}
static void
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
for (auto& meth : *mlist) {
const char *name = sel_cname(meth.name);
// 注册方法列表中的方法名
SEL sel = sel_registerNameNoLock(name, bundleCopy);
meth.name = sel;
if (ignoreSelector(sel)) {
meth.imp = (IMP)&_objc_ignored_method;
}
}
// 按照SEL对方法列表排序
if (sort) {
method_t::SortBySELAddress sorter;
std::stable_sort(mlist->begin(), mlist->end(), sorter);
}
// 标记方法列表已经排好序并固定。
// method_list_t的entsizeAndFlags最低两位均为1表示方法列表已经排好序并固定
mlist->setFixedUp();
}
复制代码
所谓方法缓冲(cache),是使用哈希表保存类的最近被调用的方法,利用哈希表的 O(1) 的查询时间复杂度提升方法调用的效率。objc_class
的cache_t cache
成员就是类的方法缓冲。cache_t
结构体代码以下。其实是指向一个能够动态扩容的 Key-Value 形式的哈希表,以方法名SEL
为Key,以方法IMP
为Value,分别对应bucket_t
结构体的_sel
、_imp
成员。cache_t
结构体包含如下成员:
_buckets
:保存哈希表全部元素的数组,元素类型为bucket_t
结构体,不是指向bucket_t
的指针; _mask
:间接表示哈希表的容量,为所有位为1
的二进制数。哈希表容量最小为4,知足公式:哈希表容量 = _mask + 1
。扩容时设置_mask =(_mask + 1) & _mask
; _occupied
:哈希表实际缓存的方法个数;
前文addMethod(...)
的代码中,调用flushCache(...)
时用于清空指定类的方法缓冲。之因此清空是由于addMethod(...)
可能会触发方法IMP
变动,必须保证方法缓冲中保存的全部方法SEL
与IMP
映射的正确性。关于方法缓冲的具体细节见 Runtime 源代码objc-cache.mm
不深刻介绍。
// 方法缓冲哈希表的元素,_sel为key,_imp为value
struct bucket_t {
private:
#if __arm64__
uintptr_t _imp;
SEL _sel;
#else
SEL _sel;
uintptr_t _imp;
#endif
...
public:
...
};
// 方法缓冲的数据结构
struct cache_t {
struct bucket_t *_buckets; //保存哈希表全部元素的数组
mask_t _mask; // 间接表示哈希表的容量
mask_t _occupied; //
public:
struct bucket_t *buckets();
mask_t mask();
mask_t occupied();
void incrementOccupied();
void setBucketsAndMask(struct bucket_t *newBuckets, mask_t newMask);
void initializeToEmpty();
mask_t capacity();
bool isConstantEmptyCache();
bool canBeFreed();
static size_t bytesForCapacity(uint32_t cap);
static struct bucket_t * endMarker(struct bucket_t *b, uint32_t cap);
void expand();
void reallocate(mask_t oldCapacity, mask_t newCapacity);
struct bucket_t * find(cache_key_t key, id receiver);
static void bad_cache(id receiver, SEL sel, Class isa) __attribute__((noreturn));
};
复制代码
调用方法经常使用的方式包括:
@interface
定义的公开接口;NSObject
类performSelector
系列方法;NSInvocation
类触发;objc_msgSend(...)
消息发送函数;注意:
<objc/message.h>
公开的接口形式是无参的,能够尝试用相似BOOL (*msg)(Class, SEL, id) = (typeof(msg))objc_msgSend
处理,作强制转换后再调用msg
函数指针以实现正常传参,第一个参数是接收消息的对象(实例方法)/类(类方法),第二个参数是SEL
方法选择器,后续可变长参数列表为方法的参数列表
形式各有不一样,可是本质上是作一样的事情:首先转化为objc_msgSend(...)
消息发送的形式,而后经过 target
、selector
定位到方法的IMP
,最后调用IMP
指向的函数指针。因而关键点来到了方法IMP
定位上。从class_getMethodImplementation(Class cls, SEL sel)
源代码能够知道 Runtime 如何经过方法名SEL
定位 方法的实现IMP
。代码以下:
IMP class_getMethodImplementation(Class cls, SEL sel)
{
IMP imp;
if (!cls || !sel) return nil;
imp = lookUpImpOrNil(cls, sel, nil,
YES/*initialize*/, YES/*cache*/, YES/*resolver*/);
// 继承链无该SEL对应的IMP,则从消息转发流程中搜索IMP
if (!imp) {
return _objc_msgForward;
}
return imp;
}
IMP lookUpImpOrNil(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
IMP imp = lookUpImpOrForward(cls, sel, inst, initialize, cache, resolver);
if (imp == _objc_msgForward_impcache) return nil;
else return imp;
}
复制代码
最后定位到关键代码lookUpImpOrForward(...)
,顾名思义是用于在类中查找IMP
,或者进入转发。根据代码逻辑,总结方法根据方法名SEL
定位方法的实现IMP
的主要处理流程以下:
class_rw_t
中的方法列表都有多是不完整的;initialize()
方法,由于其中可能包含动态添加方法逻辑;method_array_t
方法列表二维数组容器中的全部method_list_t
,在method_list_t
中搜索方法。若method_list_t
已排序,则使用二分法搜索,若method_list_t
未排序,则用简单线性搜索。搜索到则马上返回;true
,则回到第4步再次尝试搜索;false
,则触发消息转发流程。返回_objc_msgForward_impcache
,返回该IMP
表示须要进入消息转发流程;注意:
initialize()
方法和load()
方法的做用有点类似,都是用于类的初始化。但二者在行为上有很大区别:一、load()
方法不具备继承特性,继承链上的全部类定义的全部load()
方法均会被调用,load()
方法中禁止使用super
关键字;intialize()
具备继承特性,可以使用super
关键字,其行为至关于普通的类方法。二、load()
方法在类加载时触发,且 runtime 严格控制一个类的load()
方法只会被执行一次;initialize()
方法 在第一次调用类的方法时,在lookUpImpOrForward(...)
中检查类是否初始化时触发,所以若父类实现了initialize()
方法,可是其全部子类均未实现,则不管是第一次调用子类仍是父类的方法,都会触发父类的initialize()
方法。
lookUpImpOrForward(...)
的源代码看起来很长,可是基本上每一步都包含很关键的信息,对理解消息的响应流程很是重要。所以只删除代码中读写锁、调试相关代码,在代码中作了详细的注释。
// Runtime预约义的统一的触发方法动态解析的SEL,注意实际值应该并非NULL
SEL SEL_resolveInstanceMethod = NULL;
// Runtime预约义的标记进入消息转发流程的IMP
extern void _objc_msgForward_impcache(void);
// 从cls类的`cache_t`方法缓存哈希表中查询SEL对应的IMP
extern IMP cache_getImp(Class cls, SEL sel);
// 在cls类查询SEL对应的IMP
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
Class curClass;
IMP imp = nil;
Method meth;
bool triedResolver = NO;
// 在方法缓冲中搜索方法
if (cache) {
imp = cache_getImp(cls, sel);
if (imp) return imp;
}
// 方法固定相关操做
if (!cls->isRealized()) {
realizeClass(cls);
}
// 若类的initialize()方法未调用,则先进行类的initialize初始化过程
if (initialize && !cls->isInitialized()) {
_class_initialize (_class_getNonMetaClass(cls, inst)); //调用类的initialize()方法
}
retry:
// 若ARC编译选项打开则须要忽略retain、release等方法,忽略
if (ignoreSelector(sel)) {
imp = _objc_ignored_method;
cache_fill(cls, sel, imp, inst);
goto done;
}
// 在类的cache_t方法缓存哈希表中查询SEL对应的IMP
imp = cache_getImp(cls, sel);
if (imp) goto done;
// 在类的method_array_t方法列表容器中查询SEL对应的IMP
meth = getMethodNoSuper_nolock(cls, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, cls);
imp = meth->imp;
goto done;
}
// 沿着类的继承链循环。在各级类及父类的方法缓冲、方法列表容器中查询SEL对应的IMP
curClass = cls;
while ((curClass = curClass->superclass)) {
// 在类的方法缓存中查询
imp = cache_getImp(curClass, sel);
if (imp) {
if (imp != (IMP)_objc_msgForward_impcache) {
// 若搜索到IMP,则将IMP写入父类的方法缓存
log_and_fill_cache(cls, imp, sel, inst, curClass);
goto done;
}
else {
break;
}
}
// 在类的方法列表中查询
meth = getMethodNoSuper_nolock(curClass, sel);
if (meth) {
log_and_fill_cache(cls, meth->imp, sel, inst, curClass);
imp = meth->imp;
goto done;
}
}
// 尝试动态解析方法
if (resolver && !triedResolver) {
runtimeLock.unlockRead();
_class_resolveMethod(cls, sel, inst);
triedResolver = YES;
goto retry;
}
// 触发消息转发流程
imp = (IMP)_objc_msgForward_impcache;
cache_fill(cls, sel, imp, inst);
done:
return imp;
}
// 当前类的方法列表中搜索方法
static method_t *
getMethodNoSuper_nolock(Class cls, SEL sel)
{
for (auto mlists = cls->data()->methods.beginLists(),
end = cls->data()->methods.endLists();
mlists != end;
++mlists)
{
method_t *m = search_method_list(*mlists, sel);
if (m) return m;
}
return nil;
}
// 简单线性遍历method_list_t搜索方法名为sel的方法,查到马上返回
static method_t *search_method_list(const method_list_t *mlist, SEL sel)
{
int methodListIsFixedUp = mlist->isFixedUp();
int methodListHasExpectedSize = mlist->entsize() == sizeof(method_t);
if (__builtin_expect(methodListIsFixedUp && methodListHasExpectedSize, 1)) {
//对于isFixedUp的方法列表用二分法搜索,这也是fixedUp时须要对方法列表排序的缘由
return findMethodInSortedMethodList(sel, mlist);
} else {
// 线性搜索,简单遍历
for (auto& meth : *mlist) {
if (meth.name == sel) return &meth;
}
}
return nil;
}
// 用二分查找从已排序的method_list_t中搜索方法名为key的方法
static method_t *findMethodInSortedMethodList(SEL key, const method_list_t *list)
{
assert(list);
const method_t * const first = &list->first;
const method_t *base = first;
const method_t *probe;
uintptr_t keyValue = (uintptr_t)key;
uint32_t count;
for (count = list->count; count != 0; count >>= 1) {
probe = base + (count >> 1);
uintptr_t probeValue = (uintptr_t)probe->name;
if (keyValue == probeValue) {
while (probe > first && keyValue == (uintptr_t)probe[-1].name) {
probe--;
}
return (method_t *)probe;
}
if (keyValue > probeValue) {
base = probe + 1;
count--;
}
}
return nil;
}
复制代码
当类及其继承链的方法列表不存在SEL
对应的方法时,会进入方法动态解析 以及 消息转发流程,Runtime 只公布了触发逻辑,实现细节则彻底隐藏在objc_msgSend
的实现中。触发方法动态解析经过objc_msgSend
向类发送SEL_resolveInstanceMethod
消息,推测objc_msgSend
内部只是直接调用类的resolveInstanceMethod
、resolveClassMethod
方法查询SEL
是否有动态解析,有则返回true
反之返回false
。
所谓动态方法解析实际上就是对不经常使用到的方法,在正式调用到该方法时才将方法添加到类的方法列表。所以,resolveInstanceMethod
、resolveClassMethod
中的代码通常是对须要动态解析的SEL
,调用class_addMethod(...)
动态添加该SEL
对应的方法,并返回YES
。若是只是返回YES
的话,而不添加方法极可能形成的后果是responseToSelector
虽然能够返回YES
,可是performSelector
时仍然抛出unrecognized selector
异常。
方法动态解析相关代码以下。
// 动态解析方法
void _class_resolveMethod(Class cls, SEL sel, id inst)
{
if (! cls->isMetaClass()) {
// 动态解析实例方法
_class_resolveInstanceMethod(cls, sel, inst);
}
else {
// 动态解析类方法
_class_resolveClassMethod(cls, sel, inst);
// 这里为何要给元类再动态解析实例方法
if (!lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
_class_resolveInstanceMethod(cls, sel, inst);
}
}
}
// 动态解析类方法
static void _class_resolveClassMethod(Class cls, SEL sel, id inst)
{
// 若类未实现resolveClassMethod方法,则不走方法动态解析流程
if (! lookUpImpOrNil(cls, SEL_resolveClassMethod, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 触发方法动态解析流程
bool resolved = msg(_class_getNonMetaClass(cls, inst),
SEL_resolveClassMethod, sel);
// lookUpImpOrNil触发将动态解析的SEL与IMP的映射添加到类的方法缓存
// 这样下次调用SEL时可直接从cache_t中查询到IMP
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}
}
// 动态解析实例方法
static void _class_resolveInstanceMethod(Class cls, SEL sel, id inst)
{
// 若类未实现resolveInstanceMethod方法,则不走方法动态解析流程
if (! lookUpImpOrNil(cls->ISA(), SEL_resolveInstanceMethod, cls,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/))
{
return;
}
BOOL (*msg)(Class, SEL, SEL) = (typeof(msg))objc_msgSend;
// 触发方法动态解析流程
bool resolved = msg(cls, SEL_resolveInstanceMethod, sel);
// lookUpImpOrNil触发将动态解析的SEL与IMP的映射添加到类的方法缓存
// 这样下次调用SEL时可直接从cache_t中查询到IMP
IMP imp = lookUpImpOrNil(cls, sel, inst,
NO/*initialize*/, YES/*cache*/, NO/*resolver*/);
}
复制代码
注意:完成动态解析后,又当即调用了
lookUpImpOrNil(...)
,其目的是将resolveInstanceMethod
、resolveClassMethod
方法实现代码中为响应SEL
而动态添加的IMP
加入到类的方法缓冲,则下一次调用可直接从方法缓冲中获取。
关于方法动态解析和消息转发流程的内部实现代码,Runtime 公开的源代码并很少。尤为是消息转发流程的实现,基本隐藏在objc_msgSend
函数的内部实现中。可是网上有不少关于 Runtime 消息转发机制的博文,这里只贴出方法动态解析及消息转发流程的大体流程再也不赘述。图中从左到右第一列是指,在类的继承链上存在响应方法;第二列是进入方法动态解析流程(method resolution);第三列是进入消息快速转发流程(fast forwarding);第四列是进入消息完整转发流程(normal forwarding)。
class_ro_t
的baseMethodList
只保存了类的基本方法列表,只包含类定义时定义的方法,这些方法都是编译时决议的; 类的class_rw_t
的methods
保存了类的完整方法列表,除了包含基本方法列表外,还包含运行时决议的,类的分类中定义的方法以及动态添加的方法;
class_rw_t
的methods
使用method_array_t
二维数组容器保存,包含一个或多个method_list_t
结构体,其中method_array_t
的外层一位数组容器的最后一个元素为指向class_ro_t
的baseMethodList
的指针;
类的 class realizing 阶段载入方法列表时,须要对全部方法列表根据SEL
进行排序,这是为了在搜索方法时能根据SEL
进行二分法搜索提升方法搜索效率;
调用class_addMethod(...)
添加方法时,将方法封装成method_list_t
,并添加到class_rw_t
的methods
的开头,其余元素总体后移;
类的方法缓冲缓存最近调用方法的SEL
和IMP
的映射,是可动态扩容的 Key-Value 形式的哈希表,可经过方法SEL
查找IMP
,引入方法缓冲可提升方法搜索的效率;
调用方法的本质是经过objc_msgSend
向 target 发送 selector 消息,target 响应消息时,前后在类的方法缓冲、类的方法列表、继承链上全部类的方法缓冲和方法列表中搜索方法IMP
,若方法无响应,则触发方法动态解析过程,执行resolveInstanceMethod
、resolveClassMethod
中的方法动态解析逻辑,若方法动态解析过程无响应,则进入消息转发流程;
下一篇介绍属性列表的实现。