ios 类的加载之类与分类搭配加载分析
在类的加载这篇文章中,_read_images
这个函数中我们分析到doneOnce
,类的插入表,重映射和非懒加载类的加载过程,还有分类里面的数据是怎么加载的我们还没有分析,今天我们继续。
1、懒加载类和非懒加载类
在_read_images
中我们发现有一段代码是处理非懒加载类的
// Realize non-lazy classes (for +load methods and static instances)
// 实现非懒加载的类,对于load方法和静态实例变量
for (EACH_HEADER) {
classref_t *classlist =
_getObjc2NonlazyClassList(hi, &count);
for (i = 0; i < count; i++) {
Class cls = remapClass(classlist[i]);
// ✅我们在这里打印一下类,看一下非懒加载类是什么时候初始化
printf("non-lazy Class:%s\n",cls->mangledName());
// 省略代码
// 实现所有非懒加载的类(实例化类对象的一些信息,例如rw)
realizeClassWithoutSwift(cls);
}
}
我们在一个工程中创建三个类LGPerson
、LGStudent
和 LGTeacher
,我们执行打印一下:
non-lazy Class:OS_dispatch_queue
non-lazy Class:OS_dispatch_channel
non-lazy Class:OS_dispatch_source
non-lazy Class:OS_dispatch_mach
non-lazy Class:OS_dispatch_queue_runloop
non-lazy Class:OS_dispatch_semaphore
non-lazy Class:OS_dispatch_group
non-lazy Class:OS_dispatch_workloop
non-lazy Class:OS_dispatch_queue_serial
non-lazy Class:OS_dispatch_queue_concurrent
non-lazy Class:OS_dispatch_queue_main
non-lazy Class:OS_dispatch_queue_global
non-lazy Class:OS_dispatch_queue_pthread_root
non-lazy Class:OS_dispatch_queue_mgr
non-lazy Class:OS_dispatch_queue_attr
non-lazy Class:OS_dispatch_mach_msg
non-lazy Class:OS_dispatch_io
non-lazy Class:OS_dispatch_operation
non-lazy Class:OS_dispatch_disk
non-lazy Class:OS_voucher
non-lazy Class:OS_dispatch_data_empty
non-lazy Class:OS_xpc_connection
non-lazy Class:OS_xpc_service
non-lazy Class:OS_xpc_null
non-lazy Class:OS_xpc_bool
non-lazy Class:OS_xpc_double
non-lazy Class:OS_xpc_pointer
non-lazy Class:OS_xpc_date
non-lazy Class:OS_xpc_data
non-lazy Class:OS_xpc_string
non-lazy Class:OS_xpc_uuid
non-lazy Class:OS_xpc_fd
non-lazy Class:OS_xpc_shmem
non-lazy Class:OS_xpc_mach_send
non-lazy Class:OS_xpc_array
non-lazy Class:OS_xpc_dictionary
non-lazy Class:OS_xpc_error
non-lazy Class:OS_xpc_endpoint
non-lazy Class:OS_xpc_serializer
non-lazy Class:OS_xpc_pipe
non-lazy Class:OS_xpc_mach_recv
non-lazy Class:OS_xpc_bundle
non-lazy Class:OS_xpc_service_instance
non-lazy Class:OS_xpc_activity
non-lazy Class:OS_xpc_file_transfer
non-lazy Class:OS_xpc_int64
non-lazy Class:OS_xpc_uint64
non-lazy Class:OS_os_log
non-lazy Class:OS_os_activity
non-lazy Class:__IncompleteProtocol
non-lazy Class:Protocol
non-lazy Class:__NSUnrecognizedTaggedPointer
non-lazy Class:NSObject
non-lazy Class:__NSArray0
non-lazy Class:__NSPlaceholderArray
non-lazy Class:__NSPlaceholderSet
non-lazy Class:NSConstantArray
non-lazy Class:__NSDictionary0
non-lazy Class:NSConstantDictionary
non-lazy Class:__NSPlaceholderDate
non-lazy Class:__NSPlaceholderTimeZone
non-lazy Class:__NSPlaceholderOrderedSet
non-lazy Class:NSConstantDate
non-lazy Class:__NSPlaceholderDictionary
non-lazy Class:NSConstantIntegerNumber
non-lazy Class:NSConstantFloatNumber
non-lazy Class:NSConstantDoubleNumber
non-lazy Class:NSApplication
non-lazy Class:NSBinder
non-lazy Class:NSColorSpaceColor
non-lazy Class:NSNextStepFrame
non-lazy Class:_DKEventQuery
non-lazy Class:LGTeacher
non-lazy Class:LGStudent
看我们控制台打印出了一堆东西,但是我们仔细看发现只打印出了LGTeacher
和LGStudent
,为什么没有LGPerson
呢?
2020-01-20 17:20:04.051474+0800 objc-debug[20679:803821] +[LGTeacher load]
2020-01-20 17:20:04.051925+0800 objc-debug[20679:803821] +[LGStudent load]
看到这两行打印,也许大家就明白了,LGTeacher
和LGStudent
里面实现了+ (void)load
方法
-
load
方法的调用是在主函数之前,系统先帮你把这个类进行初始化,不管你有没有用到,这就是非懒加载类 -
懒加载的类就是没有实现
load
方法,系统没有帮你进行初始化,是需要你在用到的时候才进行初始化的
在类的加载中我们分析了非懒加载类是如何读取数据和进行rw
和ro
操作的,那么懒加载类是什么时候进行这些操作的呢?
懒加载类操作rw
和ro
刚我们还说懒加载类是在我们用的时候进行初始化,也就是alloc
。在消息查找流程-慢速查找中,消息会来到lookUpImpOrForward
,在这个函数中有一段代码:
// ✅ 这里判断是否是懒加载,如果是懒加载则执行
if (!cls->isRealized()) {
cls = realizeClassMaybeSwiftAndLeaveLocked(cls, runtimeLock);
// runtimeLock may have been dropped but is now locked again
}
我们根据这个源码往下查找:
static Class
realizeClassMaybeSwiftMaybeRelock(Class cls, mutex_t& lock, bool leaveLocked)
{
lock.assertLocked();
if (!cls->isSwiftStable_ButAllowLegacyForNow()) {
// Non-Swift class. Realize it now with the lock still held.
// fixme wrong in the future for objc subclasses of swift classes
realizeClassWithoutSwift(cls);
if (!leaveLocked) lock.unlock();
} else {
// Swift class. We need to drop locks and call the Swift
// runtime to initialize it.
lock.unlock();
cls = realizeSwiftClass(cls);
assert(cls->isRealized()); // callback must have provoked realization
if (leaveLocked) lock.lock();
}
return cls;
}
在这里我们可以看到,懒加载经过消息转发最后又回到realizeClassWithoutSwift
方法,和非懒加载类进行一模模一样样的执行。
2、类和分类的搭配分析
2.1、懒加载类+非懒加载分类
上面我们分析到,非懒加载肯定会执行_read_images
,_read_images
里面有这样一段处理分类的代码:
// ✅ 发现和处理所有Category
for (EACH_HEADER) {
// ✅ 外部循环遍历找到当前类,查找类对应的Category数组
category_t **catlist =
_getObjc2CategoryList(hi, &count);
bool hasClassProperties = hi->info()->hasCategoryClassProperties();
for (i = 0; i < count; i++) {
// ✅ 内部循环遍历当前类的所有Category
category_t *cat = catlist[i];
Class cls = remapClass(cat->cls);
if (!cls) {
// Category's target class is missing (probably weak-linked).
// Disavow any knowledge of this category.
catlist[i] = nil;
if (PrintConnecting) {
_objc_inform("CLASS: IGNORING category \?\?\?(%s) %p with "
"missing weak-linked target class",
cat->name, cat);
}
continue;
}
// Process this category.
// First, register the category with its target class.
// Then, rebuild the class's method lists (etc) if
// the class is realized.
// ✅ 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// ✅ 将Category添加到对应Class的value中,value是Class对应的所有category数组
addUnattachedCategoryForClass(cat, cls, hi);
// ✅ 将Category的method、protocol、property添加到Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// ✅ 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
// ✅ 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
}
}
我们知道非懒加载类会实现load
方法,在_objc_init
中_dyld_objc_notify_register(&map_images, load_images, unmap_image);
这三个参数load_images
,我们进去看看:
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
{
mutex_locker_t lock2(runtimeLock);
prepare_load_methods((const headerType *)mh);
}
// Call +load methods (without runtimeLock - re-entrant)
call_load_methods();
}
在这里的作用域里面调用了prepare_load_methods
函数:
void prepare_load_methods(const headerType *mhdr)
{
size_t count, i;
runtimeLock.assertLocked();
classref_t *classlist =
_getObjc2NonlazyClassList(mhdr, &count);
for (i = 0; i < count; i++) {
schedule_class_load(remapClass(classlist[i]));
}
// ✅ 在这里获取非懒加载categorylist,如果存在,则进行realizeClassWithoutSwift
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
if (cls->isSwiftStable()) {
_objc_fatal("Swift class extensions and categories on Swift "
"classes are not allowed to have +load methods");
}
realizeClassWithoutSwift(cls);
assert(cls->ISA()->isRealized());
add_category_to_loadable_list(cat);
}
}
在prepare_load_methods
函数中获取到非懒加载的categoryList
,如果存在非懒加载categoryList
,就会在此时调用realizeClassWithoutSwift
方法来初始化该class
,然后开始调用methodizeClass
方法,在methodizeClass
方法中就会开始绑定categoryList
到对应的class
,代码如下:
static void methodizeClass(Class cls)
{
runtimeLock.assertLocked();
bool isMeta = cls->isMetaClass();
auto rw = cls->data();
auto ro = rw->ro;
// Methodizing for the first time
if (PrintConnecting) {
_objc_inform("CLASS: methodizing class '%s' %s",
cls->nameForLogging(), isMeta ? "(meta)" : "");
}
// Install methods and properties that the class implements itself.
method_list_t *list = ro->baseMethods();
if (list) {
prepareMethodLists(cls, &list, 1, YES, isBundleClass(cls));
rw->methods.attachLists(&list, 1);
}
property_list_t *proplist = ro->baseProperties;
if (proplist) {
rw->properties.attachLists(&proplist, 1);
}
protocol_list_t *protolist = ro->baseProtocols;
if (protolist) {
rw->protocols.attachLists(&protolist, 1);
}
// Root classes get bonus method implementations if they don't have
// them already. These apply before category replacements.
if (cls->isRootMetaclass()) {
// root metaclass
addMethod(cls, SEL_initialize, (IMP)&objc_noop_imp, "", NO);
}
// Attach categories.
// ✅
category_list *cats = unattachedCategoriesForClass(cls, true /*realizing*/);
attachCategories(cls, cats, false /*don't flush caches*/);
...省略代码
}
懒加载类本来是在发送消息的时候才开始初始化,但是因为分类是非懒加载的需要提前初始化,经过read_images
但是没有实现类,在load_iamges
的prepare_load_methods
提前实现了类的初始化realizeClassWithoutSwift
.
2.2、懒加载类+懒加载分类
我们知道,懒加载是在初始化的时候发送消息,调用lookupImpOrForward
方法,方法流程跟上面懒加载是一样的。
2.3、非懒加载类+懒加载分类
因为类为非懒加载,直接执行read_iamges
方法,进行非懒加载类执行过程,而懒加载的分类直接在编译时加载在ro
里面了。
2.4、非懒加载类+非懒加载分类
在read_images
方法里面处理类和分类的一段代码
// ✅ 首先,通过其所属的类注册Category。如果这个类已经被实现,则重新构造类的方法列表。
bool classExists = NO;
if (cat->instanceMethods || cat->protocols
|| cat->instanceProperties)
{
// ✅ 将Category添加到对应Class的value中,value是Class对应的所有category数组
addUnattachedCategoryForClass(cat, cls, hi);
// ✅ 将Category的method、protocol、property添加到Class
if (cls->isRealized()) {
remethodizeClass(cls);
classExists = YES;
}
if (PrintConnecting) {
_objc_inform("CLASS: found category -%s(%s) %s",
cls->nameForLogging(), cat->name,
classExists ? "on existing class" : "");
}
}
// ✅ 这块和上面逻辑一样,区别在于这块是对Meta Class做操作,而上面则是对Class做操作
// ✅ 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
我们在分类中添加的是类方法,会被存储在元类中,所以,我们只需关注:
// ✅ 根据下面的逻辑,从代码的角度来说,是可以对原类添加Category的
if (cat->classMethods || cat->protocols
|| (hasClassProperties && cat->_classProperties))
{
addUnattachedCategoryForClass(cat, cls->ISA(), hi);
if (cls->ISA()->isRealized()) {
remethodizeClass(cls->ISA());
}
if (PrintConnecting) {
_objc_inform("CLASS: found category +%s(%s)",
cls->nameForLogging(), cat->name);
}
}
这里会进入到remethodizeClass
:
static void remethodizeClass(Class cls)
{
category_list *cats;
bool isMeta;
runtimeLock.assertLocked();
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);
}
}
然后添加分类attachCategories
:
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;
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);
}
看到这里是不是跟类的加载这篇文章中处理非懒加载类很像。