本文属笔记性质,主要针对本身理解不太透彻的地方进行记录。前端
推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。ios
经过Runtime加载某个类的全部Category数据数组
把全部Category的方法、属性、协议数据,合并到一个大数组中 后面参与编译的Category数据,会在数组的前面bash
将合并后的分类数据(方法、属性、协议),插入到类原来数据的前面学习
编译阶段,分类文件内的信息会被编译成一个个结构体ui
分类文件在编译时,会被转化成以下格式的结构体spa
struct _category_t {
const char *name; //主类名
struct _class_t *cls;
const struct _method_list_t *instance_methods; //对象方法列表
const struct _method_list_t *class_methods; //类方法列表
const struct _protocol_list_t *protocols; //协议列表
const struct _prop_list_t *properties; //属性列表
};
复制代码
MJPerson+Test
将会依次对应的传入参数并生成一个_category_t
类型的结构体对象_OBJC_$_CATEGORY_MJPerson_$_Test
指针
static struct _category_t _OBJC_$_CATEGORY_MJPerson_$_Test __attribute__ ((used, section ("__DATA,__objc_const"))) =
{
//主类名
"MJPerson",
0, // &OBJC_CLASS_$_MJPerson,
//对象方法列表
(const struct _method_list_t *)&_OBJC_$_CATEGORY_INSTANCE_METHODS_MJPerson_$_Test,
//类方法列表
(const struct _method_list_t *)&_OBJC_$_CATEGORY_CLASS_METHODS_MJPerson_$_Test,
//协议列表--未实现
0,
//属性列表--未实现
0,
};
复制代码
运行阶段,以前编译好的分类结构体会被追加进主类/元类对象中。code
在加载每个类时会调用此方法,将其分类也提取出来进行下一步处理orm
/***********************************************************************
* remethodizeClass
* Attach outstanding categories to an existing class.
* Fixes up cls's method list, protocol list, and property list. * Updates method caches for cls and its subclasses. * Locking: runtimeLock must be held by the caller **********************************************************************/ static void remethodizeClass(Class cls) { category_list *cats; bool isMeta; runtimeLock.assertWriting(); 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); } } 复制代码
按照加载顺序逆序的方式,将每一个分类的(方法,属性,协议)信息分别添加到单独的二维数组内备用。
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;
// i--,后加载的分类会被放在前面。也就是"覆盖"效果
while (i--) {
//取出单个分类
auto& entry = cats->list[i];
//取出分类中的方法
method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
if (mlist) {
//将信息添加进二维数组
//mlists[0] = cat0.mlist ,mlists[1] = cat1.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;
}
}
// class_rw_t 结构体(存放类中的可读写信息)
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);
}
复制代码
对类本来的信息列表进行扩容
将本来的信息放到列表末尾
将分类的信息附加到列表前端
/**
将分类(单个)信息列表,附加给主类
@param addedLists mlists[0] = cat0.mlist ,mlists[1] = cat1.mlist
@param addedCount mlists.count
*/
void attachLists(List* const * addedLists, uint32_t addedCount) {
if (addedCount == 0) return;
if (hasArray()) {
// many lists -> many lists
uint32_t oldCount = array()->count;
uint32_t newCount = oldCount + addedCount;
//将原有的方法列表array = [原方法列表] 从新分配内存
//变成 array = [原方法列表,0,0,0];
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//内存移动
//array = [原方法列表,0,0,0];
//变成array = [0,0,0,原方法列表];
memmove(array()->lists + addedCount, array()->lists,
oldCount * sizeof(array()->lists[0]));
//数组copy
//array = [0,0,0,原方法列表];
//变成array = [分类1方法列表,分类2方法列表,分类3方法列表,原方法列表];
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]));
}
}
复制代码
综上所述吧,看一遍上面的就懂了。
长这个样子,因此才会覆盖本来的方法。
methodlist = [加载的第3个分类的方法列表,加载的第2个分类的方法列表,加载的第1个分类的方法列表,原方法列表];
cats是按照加载顺序的,i--就会将其逆序
while (i--) {
//取出单个分类
auto& entry = cats->list[i]
}
复制代码
Class Extension在编译的时候,它的数据就已经包含在类信息中
Category是在运行时,才会将数据合并到类信息中
由一个manager(
AssociationsManager
)经过二维map(AssociationsHashMap
)统一管理
AssociationsHashMap
以object
做为key,另外一个map(ObjectAssociationMap
)做为value。ObjectAssociationMap
以key
做为key,具体的value
和policy
做为value。因此实际上objc_setAssociatedObject
关联对象并不会改变类的状态以及实例的分布。
void objc_setAssociatedObject(id object, const void * key,
id value,
objc_AssociationPolicy policy)
复制代码
key
要求为指针,内部会使用其地址进行操做。