本文属笔记性质,主要针对自己理解不太透彻的地方进行记录。
推荐系统直接学习小码哥iOS底层原理班---MJ老师的课确实不错,强推一波。
干货
load
+load
方法会在runtime
加载类、分类时调用每个类、分类的
+load
,在程序运行过程中只调用一次调用顺序
先调用类的
+load
按照编译先后顺序调用(先编译,先调用)
调用子类的
+load
之前会先调用父类的+load
再调用分类的
+load
按照编译先后顺序调用(先编译,先调用)
initialize
+initialize
方法会在类第一次接收到消息时调用调用顺序
先调用父类的
+initialize
,再调用子类的+initialize
(先初始化父类,再初始化子类,每个类只会初始化1次)
二者区别
+initialize
和+load
的很大区别是,+initialize是通过objc_msgSend进行调用的,所以有以下特点
如果子类没有实现
+initialize
,会调用父类的+initialize
(所以父类的+initialize
可能会被调用多次)如果分类实现了
+initialize
,就覆盖类本身的+initialize
调用
load
程序加载时,调用所有挂起的类和类别+load方法
先调用类的+load,再调用分类的+load
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 {
/ **** 首先调用class的load方法 ***/
while (loadable_classes_used > 0) {
call_class_loads();
}
/ **** 首先调用category的load方法 ***/
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,先编译,先调用 通过内存地址(IMP)直接调用
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());
}
// 直接通过IMP函数指针调用
// IMP(Class,SEL),load(cls,SEL)
(*load_method)(cls, SEL_load);
}
// Destroy the detached list.
if (classes) free(classes);
}
复制代码
父类load先于子类调用
程序加载,去构建load列表
void 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();
}
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertWriting();
// _getObjc2NonlazyClassList的顺序与编译顺序相同
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
//定制load方法列表
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方法列表时,先添加父类load再添加子类load
static void schedule_class_load(Class cls)
{
if (!cls) return;
assert(cls->isRealized()); // _read_images should realize
if (cls->data()->flags & RW_LOADED) return;
//递归调用,先调用父类
schedule_class_load(cls->superclass);
//将类追加到列表末尾
add_class_to_loadable_list(cls);
cls->setInfo(RW_LOADED);
}
复制代码
为什么load方法系统调用时不会被分类覆盖
直接提取出IMP调用,并未经过消息转发 但如果[Person load],只能调用到分类方法
initialize
向一个类第一次主动发送消息时调用。
在进行方法查找时
当一个类的方法第一次被调用,会进入查找逻辑。并加入cache,第二次直接在cache读取。
Method class_getInstanceMethod(Class cls, SEL sel)
{
if (!cls || !sel) return nil;
// This deliberately avoids +initialize because it historically did so.
// This implementation is a bit weird because it's the only place that
// wants a Method instead of an IMP.
#warning fixme build and search caches
// Search method lists, try method resolver, etc.
lookUpImpOrNil(cls, sel, nil,
NO/*initialize*/, NO/*cache*/, YES/*resolver*/);
#warning fixme build and search caches
return _class_getMethod(cls, sel);
}
复制代码
查找IMP指针时尝试初始化
/**
在一个类对象中查找某个SEL的IMP实现
*/
IMP lookUpImpOrForward(Class cls, SEL sel, id inst,
bool initialize, bool cache, bool resolver)
{
...
//如果需要初始化,并且未被初始化
if (initialize && !cls->isInitialized()) {
runtimeLock.unlockRead();
_class_initialize (_class_getNonMetaClass(cls, inst));
runtimeLock.read();
}
...
return imp;
}
复制代码
初始化一个类
优先初始化父类,然后初始化自己
void _class_initialize(Class cls)
{
...
...
//优先初始化父类
if (supercls && !supercls->isInitialized()) {
_class_initialize(supercls);
}
...
...
if (reallyInitialize) {
...
@try
#endif
{
callInitialize(cls);
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: finished +[%s initialize]",
pthread_self(), cls->nameForLogging());
}
}
#if __OBJC2__
@catch (...) {
if (PrintInitializing) {
_objc_inform("INITIALIZE: thread %p: +[%s initialize] "
"threw an exception",
pthread_self(), cls->nameForLogging());
}
@throw;
}
@finally
#endif
{
// Done initializing.
lockAndFinishInitializing(cls, supercls);
}
return;
}
else if (cls->isInitializing()) {
// We couldn't set INITIALIZING because INITIALIZING was already set.
// If this thread set it earlier, continue normally.
// If some other thread set it, block until initialize is done.
// It's ok if INITIALIZING changes to INITIALIZED while we're here,
// because we safely check for INITIALIZED inside the lock
// before blocking.
if (_thisThreadIsInitializingClass(cls)) {
return;
} else if (!MultithreadedForkChild) {
waitForInitializeToComplete(cls);
return;
} else {
// We're on the child side of fork(), facing a class that
// was initializing by some other thread when fork() was called.
_setThisThreadIsInitializingClass(cls);
performForkChildInitialize(cls, supercls);
}
}
else if (cls->isInitialized()) {
// Set CLS_INITIALIZING failed because someone else already
// initialized the class. Continue normally.
// NOTE this check must come AFTER the ISINITIALIZING case.
// Otherwise: Another thread is initializing this class. ISINITIALIZED
// is false. Skip this clause. Then the other thread finishes
// initialization and sets INITIALIZING=no and INITIALIZED=yes.
// Skip the ISINITIALIZING clause. Die horribly.
return;
}
else {
// We shouldn't be here.
_objc_fatal("thread-safe class init in objc runtime is buggy!");
}
}
复制代码
对自身进行初始化
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
复制代码
如果子类没有实现,父类会被多次调用
Initialize
的调用属于消息转发,如果cls本身没有实现,则会去父类查找并调用。
void callInitialize(Class cls)
{
((void(*)(Class, SEL))objc_msgSend)(cls, SEL_initialize);
asm("");
}
复制代码