Category 加载过程原理
- 是通过
runtime
加载类的所有Category
数据 - 把
Category
的方法、属性、协议数据,合并到一个大数组中(后面参与编译的Category
数据,会在数组前面) - 将合并后的分类数据(属性、方法、协议),插入到原来数据方法的前面
源码解读
15440831307747.jpg上图就是runtime
源码 category
的入口,通过上面可以发现category
其实是一个category_t
的结构体,而且还是个二维数组。
- 下面是
category
的主要处理方法
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;
}
}
//得到内对象里面的数据
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);
}
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;
//重新分配内存
setArray((array_t *)realloc(array(), array_t::byteSize(newCount)));
array()->count = newCount;
//内存移动,也就是将原来的东西挪动到后面
memmove(array()->lists + addedCount,
array()->lists,
oldCount * sizeof(array()->lists[0]));
//将addedLists 复制到 array()->lists, addedLists就是category的内容,
memcpy(array()->lists,
addedLists,
addedCount * sizeof(array()->lists[0]));
}
//...没有全部源码拿出来,主要处理逻辑已经在上面了。
从上面源码已经可以看到category
的整个处理过程,其实主要部分就是:
- 先拿到
category
的所有方法、属性、协议; - 然后给原来的方法重新分配一块大的内存;
- 接着把原来的内容移动到数据组的后面;
- 再把category的东西复制到数组的前面去。
+laod方法
-
+load
方法会在runtime
加载类的、分类的时候调用 - 每个类、分类的
+load
,在程序运行过程中只调用一次
源码解读
在runtime load_image()
函数里面,这个函数意思就是加载模块的意思
load_images(const char *path __unused, const struct mach_header *mh)
{
// Return without taking locks if there are no +load methods here.
if (!hasLoadMethods((const headerType *)mh)) return;
recursive_mutex_locker_t lock(loadMethodLock);
// Discover load methods
{
rwlock_writer_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
其中可以先看下call_load_methods()
里面的操作
void call_load_methods(void)
{
static bool loading = NO;
bool more_categories;
loadMethodLock.assertLocked();
// Re-entrant calls do nothing; the outermost call will finish the job.
if (loading) return;
loading = YES;
void *pool = objc_autoreleasePoolPush();
do {
// 1. Repeatedly call class +loads until there aren't any more
while (loadable_classes_used > 0) {
call_class_loads();
}
// 2. Call category +loads ONCE
more_categories = call_category_loads();
// 3. Run more +loads if there are classes OR more untried categories
} while (loadable_classes_used > 0 || more_categories);
objc_autoreleasePoolPop(pool);
loading = NO;
}
其中从上面源码可以看出他是先调用类的load
方法,那么是怎么调用类的load
的呢
static void call_class_loads(void)
{
int i;
// Detach current loadable list.
struct loadable_class *classes = loadable_classes;
int used = loadable_classes_used;
loadable_classes = nil;
loadable_classes_allocated = 0;
loadable_classes_used = 0;
// Call all +loads for the detached list.
for (i = 0; i < used; i++) {
Class cls = classes[i].cls;
load_method_t load_method = (load_method_t)classes[i].method;
if (!cls) continue;
if (PrintLoading) {
_objc_inform("LOAD: +[%s load]\n", cls->nameForLogging());
}
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
其实就是直接拿到类的方法,然后用load_method_t
存起来,而load_method_t
是一个指向方法的结构体,下面就是通过该指针直接调用该方法
通过上述源码可以总结如下几点:
-
调用顺序
- 先调用类的
+load
* 先编译,先调用 * 调用子类的`+load`方法之前,先调用父类的`+load`
- 再调用分类的
+load
- 先编译,先调用原则
- 先调用类的
+initialize方法
+initialize
方法会在类第一次接收到消息时调用
-
调用顺序
- 先调用父类的
+initialize
,再调用子类的+initialize
- (先初始化父类,再初始化子类,每个类只会初始化1次)
- 先调用父类的
-
+initialize
和+load
的很大区别是,+initialize
是通过objc_msgSend
进行调用的,所以有以下特点:- 如果子类没有实现
+initialize
,会调用父类的+initialize
(所以父类的+initialize可能会被调用多次) - 如果分类实现了
+initialize
,就覆盖类本身的+initialize
调用
- 如果子类没有实现
load、initialize方法的区别什么?
1.调用方式
- load是根据函数地址直接调用
- initialize是通过objc_msgSend调用
2.调用时刻
- load是runtime加载类、分类的时候调用(只会调用1次)
- initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)
load、initialize的调用顺序?
1.load
1> 先调用类的load
- 先编译,先调用
- 调用子类的load之前,会先调用父类的load
2> 再调用分类的load
- 先编译,先调用
2.initialize
- 先初始化父类
- 再初始化子类(可能最终调用的是父类的initialize方法)
网友评论