iOS底层原理15:类的加载原理(中)| 8月更文挑战

368 阅读8分钟

这是我参与8月更文挑战的第4天,活动详情查看: 8月更文挑战

readClass(类有了名字和地址)

read_image的流程中我们知道经过readClass之后,类有了名字地址;那么其流程究竟是如何处理的呢?源码如下:

/***********************************************************************
* readClass
* Read a class and metaclass as written by a compiler.
* Returns the new class pointer. This could be: 
* - cls
* - nil  (cls has a missing weak-linked superclass)
* - something else (space for this class was reserved by a future class)
*
* Note that all work performed by this function is preflighted by 
* mustReadClasses(). Do not change this function without updating that one.
*
* Locking: runtimeLock acquired by map_images or objc_readClassPair
**********************************************************************/
Class readClass(Class cls, bool headerIsBundle, bool headerIsPreoptimized)
{
    // 获取到类的名字
    const char *mangledName = cls->nonlazyMangledName();
    
    // 当前类的父类中如有丢失的weak-linked类,则返回nil,非研究核心
    if (missingWeakSuperclass(cls)) {
        // No superclass (probably weak-linked). 
        // Disavow any knowledge of this subclass.
        if (PrintConnecting) {
            _objc_inform("CLASS: IGNORING class '%s' with "
                         "missing weak-linked superclass", 
                         cls->nameForLogging());
        }
        addRemappedClass(cls, nil);
        cls->setSuperclass(nil);
        return nil;
    }
    
    cls->fixupBackwardDeployingStableSwift();

    // 判断是不是需要处理的未来类,正常情况下不会执行到这个条件里
    // 通过断点调试,确实没有进入if判断,因此此处不会对 ro 和 rw进行操作
    Class replacing = nil;
    if (mangledName != nullptr) {
        if (Class newCls = popFutureNamedClass(mangledName)) {
            // This name was previously allocated as a future class.
            // Copy objc_class to future class's struct.
            // Preserve future's rw data block.

            if (newCls->isAnySwift()) {
                _objc_fatal("Can't complete future class request for '%s' "
                            "because the real class is too big.",
                            cls->nameForLogging());
            }

            class_rw_t *rw = newCls->data();
            const class_ro_t *old_ro = rw->ro();
            memcpy(newCls, cls, sizeof(objc_class));

            // Manually set address-discriminated ptrauthed fields
            // so that newCls gets the correct signatures.
            newCls->setSuperclass(cls->getSuperclass());
            newCls->initIsa(cls->getIsa());

            rw->set_ro((class_ro_t *)newCls->data());
            newCls->setData(rw);
            freeIfMutable((char *)old_ro->getName());
            free((void *)old_ro);

            addRemappedClass(cls, newCls);

            replacing = cls;
            cls = newCls;
        }
    }
    
    // 判断当前的类是否存在
    if (headerIsPreoptimized  &&  !replacing) {
        // class list built in shared cache
        // fixme strict assert doesn't work because of duplicates
        // ASSERT(cls == getClass(name));
        ASSERT(mangledName == nullptr || getClassExceptSomeSwift(mangledName));
    } else {
        if (mangledName) { //some Swift generic classes can lazily generate their names
            addNamedClass(cls, mangledName, replacing); // 如果存在,将类加载到哈希表gdb_objc_realized_classes中
        } else {
            Class meta = cls->ISA();
            const class_ro_t *metaRO = meta->bits.safe_ro();
            ASSERT(metaRO->getNonMetaclass() && "Metaclass with lazy name must have a pointer to the corresponding nonmetaclass.");
            ASSERT(metaRO->getNonMetaclass() == cls && "Metaclass nonmetaclass pointer must equal the original class.");
        }
        // 插入表,即相当于从mach-O文件 读取到 内存 中
        addClassTableEntry(cls);
    }

    // for future reference: shared cache never contains MH_BUNDLEs
    if (headerIsBundle) {
        cls->data()->flags |= RO_FROM_BUNDLE;
        cls->ISA()->data()->flags |= RO_FROM_BUNDLE;
    }
    
    return cls;
}
  • cls->nonlazyMangledName():获取到类的名字;
  • missingWeakSuperclass(cls):判断当前类的父类中是否有weak-linked类;
  • addNamedClass:将类加载到哈希表gdb_objc_realized_classes中;
  • addClassTableEntry:将类插入到表中,相当于把类从MachO文件读取到内存中;

为了便于调试,我们在此处做拦截,拦截我们自定义的类Person,然后逐步研究:

继续调试:

addNamedClass方法源码如下:

将类添加到之前创建的gdb_objc_realized_classes哈希表中,此哈希表用来存放所有的类

继续执行代码:

addClassTableEntry方法源码如下:

通过此方法,将类添加到allocatedClasses表中,这个表在_objc_init函数中,执行runtime_init时创建;

继续执行,程序直接return,说明此处并未对rorw进行操作;

继续执行断点:

方法realizeClassWithoutSwift被调用;这个方法是我们接下来研究的重点,那么它做了哪些事情呢?

realizeClassWithoutSwift(处理ro,rw,isa,父类,元类)

person需要实现load方法

此方法在之前的消息的慢速查找中曾经提到过,慢速查找的流程lookUpImpOrForward-->realizeClassMaybeSwiftAndLeavelLocked-->realizeClassMayBeSwiftMaybeRelock-->realizeClassWithoutSwift

根据注释,此处最终返回了一个类的真实的数据结构,那么它里边都做了那些处理呢?继续断点执行:

该方法主要是实现类,然后将类的数据加载到内存中,步骤如下:

  • 读取data数据,设置rorw
  • 递归调用realizeClassWithoutSwift完善继承链
  • 通过methodizeClass方法初始化类

读取data数据

在这里,我们打印了一下ro,发现ro中已经有了一些数据,那么都有什么数据呢?因为ro是一个class_ro_t类型的数据,接下来我们就可以通过一下class_ro_t的一些方法来打印数据:

结果,在ro中已经存了在8个方法,那么都是什么方法呢:

(lldb) p $5.get(0).big()
(method_t::big) $8 = {
  name = "name"
  types = 0x0000000100003f98 "@16@0:8"
  imp = 0x0000000100003cb0 (KCObjcBuild`-[Person name])
}
(lldb) p $5.get(1).big()
(method_t::big) $9 = {
  name = "setName:"
  types = 0x0000000100003fa0 "v24@0:8@16"
  imp = 0x0000000100003ce0 (KCObjcBuild`-[Person setName:])
}
(lldb) p $5.get(2).big()
(method_t::big) $10 = {
  name = "nickName"
  types = 0x0000000100003f98 "@16@0:8"
  imp = 0x0000000100003d10 (KCObjcBuild`-[Person nickName])
}
(lldb) p $5.get(3).big()
(method_t::big) $11 = {
  name = "setNickName:"
  types = 0x0000000100003fa0 "v24@0:8@16"
  imp = 0x0000000100003d40 (KCObjcBuild`-[Person setNickName:])
}
(lldb) p $5.get(4).big()
(method_t::big) $12 = {
  name = "job"
  types = 0x0000000100003f98 "@16@0:8"
  imp = 0x0000000100003d70 (KCObjcBuild`-[Person job])
}
(lldb) p $5.get(5).big()
(method_t::big) $13 = {
  name = "setJob:"
  types = 0x0000000100003fa0 "v24@0:8@16"
  imp = 0x0000000100003da0 (KCObjcBuild`-[Person setJob:])
}
(lldb) p $5.get(6).big()
(method_t::big) $14 = {
  name = "job1"
  types = 0x0000000100003f98 "@16@0:8"
  imp = 0x0000000100003dd0 (KCObjcBuild`-[Person job1])
}
(lldb) p $5.get(7).big()
(method_t::big) $15 = {
  name = "setJob1:"
  types = 0x0000000100003fa0 "v24@0:8@16"
  imp = 0x0000000100003e00 (KCObjcBuild`-[Person setJob1:])
}
(lldb) 

Person类的属性namenickNamejobjob1四个属性的settergetter方法都已经加载了

断点继续向下执行:

在此处,把类的干净内存复制给一份,开始给rw赋值**(WWDC2020中针对运行时的优化中讲到,rw来自于roro只读的,而rw可读可写的)**,至此,类具备了读写功能;

  • ro表示readOnly,即只读;在编译期就确定了内存,包含FlagsSizeNameMethodsProtocolsIvarsProperties;属于clean memory,是加载后就不会发生更改的内存
  • rw表示readWrite,即可读可写;由于其动态性,可能会在类中添加属性方法协议;属于dirty memory,是指在运行时会发生更改的内存,一经使用就会变成dirty memory;
  • rwe类的额外信息WWDC2020提到过,大概只有10%的类会真正的修改它们的方法,所以就提出了rwe的概念;对于那些确实需要额外信息的类,才分配rwe,并将其添加到类中使用

递归调用realizeClassWithoutSwift

在该方法中,通过递归调用当前方法,来完善继承链,并设置当前父类元类的数据

继续向下执行:

设置父类元类isa;

给当前父类元类赋值;

然后通过addSubclass完善当前类信息,父类和子类关系

// Connect this class to its superclass's subclass lists
// 双向链表指向关系 父类中可以找到子类 子类中也可以找到父类
// 通过addSubclass把当前类放到父类的子类列表中去
if (supercls) {
    addSubclass(supercls, cls);
} else {
    addRootClass(cls);
}

接下来将会执行到:

在方法methodizeClass中对进行类别的附加;

methodizeClass

根据方法注释可以了解到在此方法中,将属性列表方法列表协议列表以及分类中的方法附加给了rwe;

list中包含了4个属性的settergetter方法,还有我们自定义的sayHello方法

prepareMethodLists(写入方法名并进行排序)

断点进入方法:

断点执行:

进入方法fixupMethodList修复方法列表;

将方法名字name赋值给meth

在此处针对方法进行排序;

我们来验证一下这个排序的情况,给Person添加方法:

@interface Person : NSObject
@property (nonatomic, copy) NSString *name;
@property (nonatomic, copy) NSString *nickName;
@property (nonatomic, copy) NSString *job;
@property (nonatomic, copy) NSString *job1;

+ (void)sayHello;
- (void)run;
- (void)smile;
- (void)eat;
- (void)talk;
- (void)learn;

@end

然后,修改main方法中的调用:

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        [Person sayHello];
        Person *p = [Person alloc];
        p.name = @"姓名";
        [p learn];
        NSLog(@"-->%@", p);
    }
    return 0;
}

然后在此处打印排序前后方法的顺序:

查看打印结果:

结论:方法前后确实被排序了

出现0x7fff********这样的地址,目前经过测试发现是因为有属性的原因,如果在main方法中只创建了类对象,而没有调用方法,那么方法排序前就会是顺序的

继续向下执行:

开始处理rwe,但是经过断点发现,程序并未执行进入attachLists方法,也就是此处rwe并没有值,那么rwe什么时候才会赋值呢?在此之前,需要补充一些关于懒加载类非懒加载类的知识;

懒加载类和非懒加载类

在调用realizeClassWithoutSwift方法的时候,根据注释可以知道,此处实现的是非懒加载类,那么究竟什么是非懒加载类?什么是非懒加载类呢?

根据注释_实现load方法或者静态实例_,那么是不是没有load方法,realizeClassWithoutSwift就不会执行呢?我们尝试将Person类的load方法注释,验证一下:

运行源码程序:

发现没有类load方法,此处将不再执行;

实现load方法,将一个类变成了非懒加载类

那么,既然realizeClassWithoutSwift里边处理类的数据结构,那么如果没有实现load方法的懒加载类在什么时候处理呢?

我们来通过堆栈信息确认一下:

非懒加载类堆栈信息:

非懒加载类通过_read_images流程调用realizeClassWithoutSwift

懒加载类堆栈信息:

懒加载类通过lookUpImpOrForward流程调用realizeClassWithoutSwift

总结