面试宝点底层原理

iOS 类的加载原理中

2021-07-28  本文已影响0人  晨曦的简书

类的加载原理:
iOS 类的加载原理上
iOS 类的加载原理中
iOS 类的加载原理下
分类的加载原理补充及类扩展 , 关联对象介绍

iOS 类的加载原理上 中我们讲到 readClass 方法,该方法通过类的地址对类的名称与地址进行绑定匹配。但是我们还不了解类的具体实现过程,这里我们来继续探究一下。

realizeClass 分析


我们在 _read_images 函数中找到跟类处理相关的代码,并在这两处地方加上测试代码并打上断点,运行之后发现代码只执行了 3781 行的打印(这里执行的前提是实例的 load 方法要有调用),紧接着执行了 realizeClassWithoutSwift 函数。那么我们就来分析一下 realizeClassWithoutSwift 函数做了哪些事情。

realizeClassWithoutSwift


首先我们判断 clsLGPerson 的时候在 auto ro = (const class_ro_t *)cls->data() 这一行打上断点,来打印 baseMethodList 的信息。正常的情况下这里应该会打印出 LGPerson 类的方法列表,但是这里并没有打印出来。说明在这里方法还没有被加载进来。所以我们继续往下看方法的加载是什么时候实现的。
static Class realizeClassWithoutSwift(Class cls, Class previously)
{
    auto ro = (const class_ro_t *)cls->data();
    auto isMeta = ro->flags & RO_META;
    // 判断是否是元类
    if (ro->flags & RO_FUTURE) {
        // This was a future class. rw data is already allocated.
        rw = cls->data();
        ro = cls->data()->ro();
        ASSERT(!isMeta);
        cls->changeInfo(RW_REALIZED|RW_REALIZING, RW_FUTURE);
    } else {
        // 不是元类的话会走到这里,这里主要是把 ro 的数据复制到 rw
        // Normal class. Allocate writeable class data.
        rw = objc::zalloc<class_rw_t>();
        rw->set_ro(ro);
        rw->flags = RW_REALIZED|RW_REALIZING|isMeta;
        cls->setData(rw);
    }

    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);

#if SUPPORT_NONPOINTER_ISA
    if (isMeta) {
        // 如果是元类的话就设置为 non pointer ISA
        cls->setInstancesRequireRawIsa();
    } else {
        bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
        bool rawIsaIsInherited = false;
        static bool hackedDispatch = false;

        // 如果做了环境变量相关的配置,这里不管是不是元类,instancesRequireRawIsa 都会赋值为 true
        if (DisableNonpointerIsa) {
            instancesRequireRawIsa = true;
        }

// SUPPORT_NONPOINTER_ISA
#endif

    /**
     supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
     metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
     上面这两行加上这里两行就构成了类的继承链关系及 isa 走位图
     */
    // 这里会设置父类及元类
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

    // Connect this class to its superclass's subclass lists
    if (supercls) {
        addSubclass(supercls, cls);
    } else {
        addRootClass(cls);
    }

    methodizeClass(cls, previously);
    return cls;
}

auto ro = (const class_ro_t *)cls->data() 之后主要做了以上注释中的这些事情,那么当执行到 methodizeClass(cls, previously) 这行的时候方法是否被加载进来了呢?我们在 methodizeClass 方法打断点看一下。


这里可以看到 baseMethodList 依然没有数据,说明在这里方法依然没被加载进来。那么我们继续来分析在 methodizeClass 方法中做了哪些事情。

methodizeClass 分析

我们继续往下执行,在执行到 prepareMethodLists 的时候可以看到 list 在这里是有值的,只是打印不出来。

prepareMethodLists

紧接着我们来到 prepareMethodLists 方法,在这里会执行到 fixupMethodList 方法,而且打印的 list 地址跟 method_list_t *list = ro->baseMethods(); 这里的地址是一样的。接着进入到修复方法。

fixupMethodList

static void 
fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
{
    runtimeLock.assertLocked();
    ASSERT(!mlist->isFixedUp());

    if (!mlist->isUniqued()) {
        mutex_locker_t lock(selLock);
    
        // Unique selectors in list.
        for (auto& meth : *mlist) {
            // sel_cname 方法会获取到 SEL
            const char *name = sel_cname(meth.name());
            
            printf("排序前 : %s - %p",name,meth.name());
            
            // 这里主要是把 sel 中的名字跟地址设置到 meth,这里之后就可以打印成功了
            meth.setName(sel_registerNameNoLock(name, bundleCopy));
        }
    }

    // 这里会判断是否是 isSmallList,如果时候 isSmallList 就不用重新进行排序,不是 isSmallList 的话就会根据地址对方法列表进行排序
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }
    
    for (auto& meth : *mlist) {
        // sel_cname 方法会获取到 SEL
        const char *name = sel_cname(meth.name());
        printf("排序后 : %s - %p",name,meth.name());
    }

    
    // Mark method list as uniqued and sorted.
    // Can't mark small lists, since they're immutable.
    if (!mlist->isSmallList()) {
        mlist->setFixedUp();
    }
}

fixupMethodList 方法中会遍历 mlist,把 sel 中的名字跟地址设置到 meth,然后根据地址对 mlist 进行重新排序。

最后我们通过打印验证可以看到,方法列表确实重新排序了。


在执行完 fixupMethodList 方法后我们再次打印 baseMethodList 还是没有数据,所以我们接着往下探究。

懒加载类与非懒加载类

非懒加载类

非懒加载类:当前类实现了 load 方法,map_images 的时候加载。

上面我们也讲过,执行 realizeClassWithoutSwift 方法的前提是当前类要实现 load 方法,这时候就是非懒加载类。然后就会执行 read_iamges -> read_class(名字 - 类) -> realizeClassWithoutSwift(ro - rw superclass isa) -> methodizeClass() -> prepareMethodLists(写入方法名 + 方法列表排序) 这个流程。但是这样的缺点就是比较耗时跟消耗内存,那么如果懒加载会执行哪些流程呢?这里我们注释 load 方法来看一下。

懒加载类

懒加载类:当前类没有实现 load 方法,当前类接收的第一条消息的时候才会加载,节约内存,提高加载速度。


当我们注释 load 方法运行的时候会来到这里执行 realizeClassWithoutSwift 方法。我们在这里加上测试代码,并打上断点 bt 输出函数调用堆栈信息。

这里可以看出懒加载类的加载流程 lookUpImpOrForward -> realizeClassMaybeSwiftMaybeRelock -> realizeClassWithoutSwift -> methodizeClass

而且在 main 函数这里可以看到懒加载类的加载执行是在 LGPerson 第一次调用方法的时候。

分类的本质

#import <Foundation/Foundation.h>
#import "LGPerson.h"
#import <objc/runtime.h>

extern void _objc_autoreleasePoolPrint(void);

@interface LGPerson (LG)

@property (nonatomic, copy, nullable) NSString *cate_name;

@property (nonatomic, assign) int cate_age;

- (void)cate_instanceMethod1;
- (void)cate_instanceMethod2;
+ (void)cate_classMethod3;

@end

@implementation LGPerson (LG)

- (void)cate_instanceMethod1 {
    NSLog(@"%s",__func__);
}
- (void)cate_instanceMethod2 {
    NSLog(@"%s",__func__);
}
+ (void)cate_classMethod3 {
    NSLog(@"%s",__func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        LGPerson *p = [LGPerson alloc];
        [p say1];
        
        NSLog(@"Hello, World!");
    }
    return 0;
}

__attribute__((constructor)) void kcFunc(void){
    printf("来了 : %s \n",__func__);
}

我们在 main.mLGPerson 类添加一个分类 LGPerson (LG)cdmain.m 所在文件目录,然后输入 clang -rewrite-objc main.m -o main.cpp 命令,最后会看到生成了一个 main.cpp 文件。




我们打开 main.cpp 文件,在最后可以看到 LGPerson (LG) 的底层代码。接着我们点进到 _category_t 查看分类的结构。我们可以看到结构体里面有 name,这里 name 的名称就是 LGcls 这里就是就是 LGPerson 类,而且可以看到 instance_methodsclass_methods,这里相对于类的结构多了 class_methods,这是因为分类没有元类,接着就是 protocols(协议)properties(关联属性)

这里我们也可以看到对应我们添加的类方法跟对象方法,但是这里没有 set 方法跟 get 方法,也验证了分类添加属性是通过关联对象进行处理的。这里是生成的 cpp 文件我们看到的,那么在源码中分类的结构是否跟这里一致呢?


这里可以看到结构跟我们在 cpp 文件中看到的基本一直,只是多了一个 _classProperties,但是类属性不是一直都有。
上一篇下一篇

猜你喜欢

热点阅读