IOSiOS程序猿iOS学习笔记

Load和Initialize实现原理

2018-11-22  本文已影响14人  SunshineBrother

Load和Initialize实现原理

+Load实现原理

+load方法会在runtime加载分类时调用

每个类、分类的+load,在程序运行过程中只调用一次

+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用

调用顺序

objc4源码解读过程
objc-os.mm 文件

_objc_init方法是RunTime运行的入口

void _objc_init(void)
{
static bool initialized = false;
if (initialized) return;
initialized = true;

// fixme defer initialization until an objc-using image is found?
environ_init();
tls_init();
static_init();
lock_init();
exception_init();

_dyld_objc_notify_register(&map_images, load_images, unmap_image);
}

小知识:images是镜像的意思

我们在_objc_init方法中找到load_imagesload_images是Load加载镜像的意思,所有我们可以猜测这个里面应该有我们load的加载方法的相关实现

我们点击进入load_images方法

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();
}

里面有两个需要我们注意的

我们点击进入prepare_load_methods((const headerType *)mh)准备加载Load方法

void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;

runtimeLock.assertWriting();

classref_t *classlist = 
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}

category_t **categorylist = _getObjc2NonlazyCategoryList(mhdr, &count);
for (i = 0; i < count; i++) {
category_t *cat = categorylist[i];
Class cls = remapClass(cat->cls);
if (!cls) continue;  // category for ignored weak-linked class
realizeClass(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}

我们可以看到执行顺序

查看类的load方法

我们查看schedule_class_load(remapClass(classlist[i]));方法里面还有哪些实现

static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized());  // _read_images should realize

if (cls->data()->flags & RW_LOADED) return;

// Ensure superclass-first ordering
schedule_class_load(cls->superclass);

add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED); 
}

走到这里我们大概是清楚了类中load方法的加载添加过程,就是先把父类添加带数组中,然后再把自己添加到数组中

查看分类的load方法

我们点击add_category_to_loadable_list(cat)进入查看方法实现

void add_category_to_loadable_list(Category cat)
{
IMP method;

loadMethodLock.assertLocked();

method = _category_getLoadMethod(cat);

// Don't bother if cat has no +load method
if (!method) return;

if (PrintLoading) {
_objc_inform("LOAD: category '%s(%s)' scheduled for +load", 
_category_getClassName(cat), _category_getName(cat));
}

if (loadable_categories_used == loadable_categories_allocated) {
loadable_categories_allocated = loadable_categories_allocated*2 + 16;
loadable_categories = (struct loadable_category *)
realloc(loadable_categories,
loadable_categories_allocated *
sizeof(struct loadable_category));
}

loadable_categories[loadable_categories_used].cat = cat;
loadable_categories[loadable_categories_used].method = method;
loadable_categories_used++;
}

loadable_categories_used++;分类没有什么特殊的方法,应该就是按照编译顺序添加到数组的。

实现

我们刚才看到了类分类中的添加顺序,我们在来看看加载顺序
点击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;
}

上面直接有官方文档给我们的顺序

Demo

我们这里来一个测试demo

编译顺序

image

打印顺序

image

所有上面总结是十分准确的

我们是否注意到了另一个问题,为什么在有分类的时候还加载类的load方法,不应该是分类覆盖类吗?

我们在查看load的源码实现的时候发现,+load方法是根据方法地址直接调用,并不是经过objc_msgSend函数调用,如果使用objc_msgSend会出现分类覆盖类,但是load直接是根据指针找方法的,所以不会覆盖。

Initialize实现原理

+initialize方法会在类第一次接收到消息时调用

调用顺序

objc4源码解读过程

objc-runtime-new.mm

我们在objc-runtime-new.mm文件中找到class_getInstanceMethod,里面就有一个主要实现方法lookUpImpOrNil

Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls  ||  !sel) return nil;
#warning fixme build and search caches
lookUpImpOrNil(cls, sel, nil, 
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}

里面没有什么实现我们继续点击lookUpImpOrNil进入实现

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查看实现

if (initialize  &&  !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}

这个里面有一个if判断里面有一些东西,就是在没有实现isInitialized的时候,调用_class_initialize方法,我们点击进入查看相关实现

if (supercls  &&  !supercls->isInitialized()) {
_class_initialize(supercls);
}
callInitialize(cls);

里面有这两个主要的函数

我们在点击callInitialize发现具体是通过objc_msgSend来实现的。

void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}

Demo

测试案例1
我们创建父类Person,然后创建子类Student&Teacher,子类不实现initialize方法,父类实现该方法

[Teacher alloc];
[Student alloc];
image

结果打印三次[Person initialize]方法,打印一次我们是能够想到了,因为实现过程是先看看父类有没有已经实现,如果没有实现就先实现父类的。但是另外两次是怎么来的呢。

[Student alloc]的实现大概是这样的

objc_msgSend([Person class], @selector(initialize));
objc_msgSend([Student class], @selector(initialize));

测试案例2

我们创建父类Person,然后创建分类Person+Eat,都是实现initialize方法

[Person alloc];
image

这句代码就是证明了如果分类实现了+initialize,就覆盖类本身的+initialize调用

总结

load、initialize方法的区别什么?

-2.调用时刻
- 1> load是runtime加载类、分类的时候调用(只会调用1次)
- 2> initialize是类第一次接收到消息的时候调用,每一个类只会initialize一次(父类的initialize方法可能会被调用多次)

load、initialize的调用顺序?

1.load

2.initialize

上一篇下一篇

猜你喜欢

热点阅读