iOS底层-类的加载原理

背景

  • 在前面我们App的加载流程我们知道了dyld动态链接器的作用和原理,了解了map_images(),load_images(),那么他们是什么时候调用的又是如何执行的呢?
  • 类是怎么加载的呢?类的方法,协议,属性,ro,rw如何加载的呢?类加载的慢速流程是什么?
  • 懒加载,非懒加载的执行逻辑是什么呢?
  • load_images() 的load方法,cxx + load + main 调用顺序是什么,又是如何调用的呢?
  • dyld 到 主程序main的过程是什么?
  • objc_init()里面的一些初始化函数调用,它们都做了什么?

准备

  • 可编译的objc4工程
  • LLVM源码
  • Clang环境
  • dyld源码

_objc_init分析:

objc_init函数

  • environ_init():环境变量初始化,主要调试时候使用
  • tls_init():线程暂存缓存时,线程key的绑定,主要是本地线程池的初始化以及析构
  • static_init():全局静态C++函数调用
  • runtime_init():运行时机制两个表的初始化,主要分:分类、类相关的表初始化
  • exception_init():初始化libobjc的异常处理系统,由map_images()调用
  • cache_t::init():缓存初始化
  • _imp_implementationWithBlock_init():初始化回调机制,通常这不会做什么,所有进程都是惰性初始化的,但对于某些进程,我们是主动加载的,libobjc-trampolines.dylib
  • _dyld_objc_notify_register(&map_images, load_images, unmap_image):初始化注册dyld,处理map_images和load_images关联,dyld与objc的关联
  • didCallDyldNotifyRegister = true:设置dyld寄存器通知

environ_init:

环境变量初始化,主要调试时候使用

  • 核心代码:

在这里插入图片描述

  • opt环境变量
for (size_t i = 0; i < sizeof(Settings)/sizeof(Settings[0]); i++) {
            const option_t *opt = &Settings[i];
            _objc_inform("%s: %s", opt->env, opt->help);
        }
  • 第一个是disable关闭环境变量可以控制isa优化开关,从而优化整个内存结构,第二个load_method监控工程+(void)load方法使用场景
    在这里插入图片描述
  • 还有一种终端log打印,export OBJC_HELP=1
export OBJC_HELP=1

tls_init

线程暂存缓存时,线程key的绑定,主要是本地线程池的初始化以及析构
在这里插入图片描述

static_init

根据头部注释和验证

  • Run C++ static constructor functions.
  • libc calls _objc_init() before dyld would call our static constructors,
  • so we have to do it ourselves.
    运行c++静态构造函数。
    libc在dyld调用静态构造函数之前调用_objc_init()
    所以我们必须自己做
    在这里插入图片描述

runtime_init

运行时机制两个表的初始化,主要分:分类、类相关的表初始化
在这里插入图片描述

exception_init

初始化libobjc的异常处理系统。
由map_images()调用。
在这里插入图片描述

cache_t::init()

在这里插入图片描述

_imp_implementationWithBlock_init()

初始化回调机制,通常这不会做什么,所有进程都是惰性初始化的,但对于某些进程,我们是主动加载的,libobjc-trampolines.dylib
在这里插入图片描述

_dyld_objc_notify_register(&map_images, load_images, unmap_image)

初始化注册dyld,处理map_images和load_images关联,dyld与objc的关联:
//⚠️注意:仅供objc运行时使用
//注册当objc图像被映射,未映射和初始化时调用的处理程序。
// Dyld将回调"mapped"函数,它包含一个包含objc-image-info section的图像数组。
//那些dylibs的图像将会有refcount自动碰撞,所以objc将不再需要
//调用dlopen()来阻止它们被卸载。 在调用_dyld_objc_notify_register()时,
// dyld将调用已加载的objc图像的"mapped"函数。 在以后的任何dlopen()调用期间,
// dyld也会调用“mapped”函数。 当Dyld被调用时,Dyld将调用“init”函数
//初始化器。 这是当objc调用图像中的任何+load方法时。
在这里插入图片描述

didCallDyldNotifyRegister = true;

设置调用dyld寄存器通知,默认为true

dyld与objc关联

_dyld_objc_notify_register入口进入:
请添加图片描述
代码 -> 编译 -> macho -> 内存

registerObjcNotifiers

在这里插入图片描述
在这里插入图片描述

_dyld_objc_notify_register(&map_images, load_images, unmap_image)
&map_images 重要数据通过&进行指针传递,
load_images调用load方法,跟随map_images变化而同步变化

objc中map_images源码:
在这里插入图片描述
探索map_images_nolock,目标找到image的读取和映射(_read_images):
在这里插入图片描述

_read_images⭐️

_read_images(),镜像文件的读取和映射,代码过多关闭各判断代码块,从打印的log开始入手分析:
在这里插入图片描述
read_images函数内各模块含义:
1: 条件控制进行一次的加载
2: 修复预编译阶段的 @selector 的混乱问题
3: 错误混乱的类处理 4:修复重映射一些没有被镜像文件加载进来的 类 5: 修复一些消息!
6: 当我们类里面有协议的时候 : readProtocol 7: 修复没有被加载的协议
8: 分类处理
9: 类的加载处理
10 : 没有被处理的类 优化那些被侵犯的类

在这里插入图片描述
在这里插入图片描述
由于sel与sels[i]中的地址不同,因此要赋值。
在这里插入图片描述
类发生移动会把原始类删除,但有时候会删除不干净就会导致紊乱和野指针。
在这里插入图片描述
class通过readClass读取由编译器编写的类和元类。
*返回新的类指针。

readClass分析

readClass读取由编译器编写的类和元类。
*返回新的类指针。 这可能是:
*——cls

    • nil (cls缺少弱链接超类)
  • -其他的东西(这个类的空间被一个未来的类保留)

    *注意,所有由该函数执行的工作都由
  • mustReadClasses()。 不要在没有更新那个函数的情况下更改这个函数。

    *locking:由map_images或objc_readClassPair获取的runtimeLock
    在这里插入图片描述
    readClass->其中的addRemappedClass为哈希map结构
    在这里插入图片描述

readClass->其中的addClassTableEntry添加类到表中
在这里插入图片描述

代码试探分析:

//测试代码
const char *mangledName = cls->nonlazyMangledName();
const char *LMPersonName = "LMPerson";
if (strcmp(mangledName, LMPersonName) == 0) {
                //输出普通类
	printf("%s -resolvedFutureClass--:--%s\n", __func__, mangledName);
}

在这里插入图片描述

realizeClassWithoutSwift(cls, nil)类的实现分析

realizeClassWithoutSwift
*对类cls执行首次初始化,
*包括分配读写数据。
*不执行任何Swift端初始化。
*返回类的实际类结构。
*锁定:runtimeLock必须由调用方进行写锁定
在这里插入图片描述
ro->rw,ro赋值到rw的过程:
在这里插入图片描述
内容中:

  • 继承链:
    supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
    metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
  • isa走位:
    cls->setSuperclass(supercls);
    cls->initClassIsa(metacls);

要注意元类和类的名字一样,因此需要判断isMetaClass过滤掉元类的判断:
请添加图片描述
请添加图片描述
经过ro和rw赋值发现其实还是NULL,那么methodizeClass是不是有什么特殊处理呢?

methodizeClass分析

realizeClassWithoutSwift->methodizeClass

  • 修复cls的方法列表、协议列表和属性列表。
  • 附加任何未完成的类别。
    在这里插入图片描述

    prepareMethodLists

    其中进到prepareMethodLists方法:
    在这里插入图片描述

fixupMethodList

然后进到fixupMethodList方法,此方法是为了method修复及方法排序
在这里插入图片描述
在这里插入图片描述
拿到sel设置到method,这样就可以正常打印了。

再然后经过下面排序:

// Sort by selector address.
    // Don't try to sort small lists, as they're immutable.
    // Don't try to sort big lists of nonstandard size, as stable_sort
    // won't copy the entries properly.
    if (sort && !mlist->isSmallList() && mlist->entsize() == method_t::bigSize) {
        method_t::SortBySELAddress sorter;
        std::stable_sort(&mlist->begin()->big(), &mlist->end()->big(), sorter);
    }

请添加图片描述
处理完排序之后列表mlist->setFixedUp();
但是经过这一系列ro,rw及排序 操作,发现ro和rw还是打印NULL
小节:read_images->readClass(名字和类匹配)->realizeClassWithoutSwift(ro ,rw,superclass,isa处理)->methodizeClass()->prepareMethodLists(写入方法名以及对method排序)
问题来了rwe什么时候赋值?

类与分类

  • 1,非懒加载类 +非懒加载分类
非懒加载类非懒加载分类
_getObjc2NonlazyClassList、readClass、realizeClassWithoutSwift、 methodizeClassload_categories_nolock、attachCategories、attachToClass
一维数组结构二维数组结构
  • 2,懒加载类 + 懒加载分类 : 消息第一次调用 (加载数据)- 编译时期就完成
  • 3,非懒加载类 + 懒加载分类 : read_image 就加载数据
  • 4,懒加载类 + 非懒加载分类 : 迫使类成为非懒加载类

类 的 非懒加载与懒加载(+load)

  • 看底层代码注释,如果写+load方法会走这些方法,那么我们跟进流程探索一下:
    在这里插入图片描述
    类别必须推迟,以避免潜在的竞争
    其他线程之前调用新类别代码时
    如果写了+load 会走 prepare_load_methods()处理加载
    经lldb验证,类中去除+load方法的调用,就不会走上面这些流程的判断, 因此非懒加载执行prepare_load_methods。
  • 那么懒加载的按需加载是节省性能了,但怎么执行呢?
    代码执行经过bt 看到走了消息慢速查找中的lookUpImpOrForward->realizeAndInitializeIfNeeded_locked
    请添加图片描述
    跟着流程走到了realizeClassMaybeSwiftMaybeRelock
    在这里插入图片描述
    里面同样有realizeClassWithoutSwift方法

请添加图片描述
protocol_list_t *protolist = ro->baseProtocols; ro操作

分类与rwe赋值

  • 了解分类
    main文件经过Clang:
    请添加图片描述
  • _category_t结构体里的name是分类的名字
  • 分类没有元类
  • 分类定义的属性没有get和set,so需要关联对象
    通过查看objc源码,看到Category数据结构:
    在这里插入图片描述
    在这里插入图片描述
  • 那么分类是怎么加载到内存的呢?
    通过在methodizeClass方法里面有个attachToClass
    在这里插入图片描述
    在这里插入图片描述
  • 而methodizeClass方法里面rwe=ext(),进入ext方法:
    在这里插入图片描述

attachCategories分析

  • 源码看rwe跟extAllocIfNeeded方法开辟内存有关,全局搜索源码中extAllocIfNeeded
    在这里插入图片描述
    在这里插入图片描述

    • attachCategories方法里面,rwe在extAllocIfNeeded里面赋值
    • 表明rwe是在程序动态运行时处理赋值,
    • 那么所有的重点就集中在了attachCategories方法
    • 全局搜索attachCategories->发现跟attachToClassload_categories_nolock方法有关
    • 我们检索attachToClassload_categories_nolock这两个方法:其中attachToClassmethodizeClass方法里面调用,而load_categories_nolockloadAllCategories_read_images方法调用
    • Ro是CleanMemary,rw是DirtyMemary
      把Ro复制到rw,rw中有脏内存但是其内存比较昂贵,因此需要优化rw而节省内存,而runtime(运行时),添加分类、api、method、协议、属性等等,并不是每个类都是动态添加,这个时候写在rw会浪费rw内存性能,因此就有了rwe,rwe对rw的扩展。

attachLists分析

void attachLists(List* const * addedLists, uint32_t addedCount) {
        if (addedCount == 0) return;

        if (hasArray()) {
            // many lists -> many lists
            uint32_t oldCount = array()->count;
            uint32_t newCount = oldCount + addedCount;
            array_t *newArray = (array_t *)malloc(array_t::byteSize(newCount));
            newArray->count = newCount;
            array()->count = newCount;

            for (int i = oldCount - 1; i >= 0; i--)
                newArray->lists[i + addedCount] = array()->lists[i];
            for (unsigned i = 0; i < addedCount; i++)
                newArray->lists[i] = addedLists[i];
            free(array());
            setArray(newArray);
            validate();
        }
        else if (!list  &&  addedCount == 1) {
            // 0 lists -> 1 list
            list = addedLists[0];
            validate();
        } 
        else {
            // 1 list -> many lists
            Ptr<List> oldList = list;
            uint32_t oldCount = oldList ? 1 : 0;
            uint32_t newCount = oldCount + addedCount;
            setArray((array_t *)malloc(array_t::byteSize(newCount)));
            array()->count = newCount;
            if (oldList) array()->lists[addedCount] = oldList;
            for (unsigned i = 0; i < addedCount; i++)
                array()->lists[i] = addedLists[i];
            validate();
        }
    }
  • oldList:存放的是主类的方法指针列表,数组指针
  • addLists:分类的方法指针列表
  • 通过先一维数组遍历,然后一维转成二维数组,最后二维数组与二维数组遍历添加

类与分类小结:

  • 分类在Clang时类名是与类的类名一样,但是在runtime时才会变化为相应的分类名
  • 分类与类搭配加载:
    1,两个都有+load方法时,_read_images 非懒加载类 -> realizeClassWithoutSwift -> load_categories_nolock -> attachCategories
    2,主类中有load 分类没有load时,(data()里面获取),_read_images 懒加载类 -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> 没有走attachCategories
    3,主类没有load 分类有load时,(data()里面获取),_read_images 懒加载类 -> realizeClassWithoutSwift -> methodizeClass -> attachToClass -> 没有走attachCategories
    4,主类和分类都没有load时,推迟到runtime运行时 第一次消息发送的时候初始化 (data()里面获取)

5,多个分类,超过一个分类实现load时会迫使走attachCategories,如果创建一个分类里面没有任何实现时methodList里面并不会有这个没有任何实现的分类

类扩展vs分类

在这里插入图片描述

  • 分类加载是否需要排序?
    不用排序
  • class_ro_t:指针地址数据格式什么样的?
    通过格式数据结构赋值
    通过LLVM源码我们读取看到:
bool ClassDescriptorV2::class_ro_t::Read(Process *process, lldb::addr_t addr) {
  size_t ptr_size = process->GetAddressByteSize();

  size_t size = sizeof(uint32_t)   // uint32_t flags;
                + sizeof(uint32_t) // uint32_t instanceStart;
                + sizeof(uint32_t) // uint32_t instanceSize;
                + (ptr_size == 8 ? sizeof(uint32_t)
                                 : 0) // uint32_t reserved; // __LP64__ only
                + ptr_size            // const uint8_t *ivarLayout;
                + ptr_size            // const char *name;
                + ptr_size            // const method_list_t *baseMethods;
                + ptr_size            // const protocol_list_t *baseProtocols;
                + ptr_size            // const ivar_list_t *ivars;
                + ptr_size            // const uint8_t *weakIvarLayout;
                + ptr_size;           // const property_list_t *baseProperties;

  DataBufferHeap buffer(size, '\0');
  Status error;

  process->ReadMemory(addr, buffer.GetBytes(), size, error);
  if (error.Fail()) {
    return false;
  }

  DataExtractor extractor(buffer.GetBytes(), size, process->GetByteOrder(),
                          process->GetAddressByteSize());

  lldb::offset_t cursor = 0;

  m_flags = extractor.GetU32_unchecked(&cursor);
  m_instanceStart = extractor.GetU32_unchecked(&cursor);
  m_instanceSize = extractor.GetU32_unchecked(&cursor);
  if (ptr_size == 8)
    m_reserved = extractor.GetU32_unchecked(&cursor);
  else
    m_reserved = 0;
  m_ivarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
  m_name_ptr = extractor.GetAddress_unchecked(&cursor);
  m_baseMethods_ptr = extractor.GetAddress_unchecked(&cursor);
  m_baseProtocols_ptr = extractor.GetAddress_unchecked(&cursor);
  m_ivars_ptr = extractor.GetAddress_unchecked(&cursor);
  m_weakIvarLayout_ptr = extractor.GetAddress_unchecked(&cursor);
  m_baseProperties_ptr = extractor.GetAddress_unchecked(&cursor);

  DataBufferHeap name_buf(1024, '\0');

  process->ReadCStringFromMemory(m_name_ptr,

请添加图片描述

类扩展

eg:

@interface ViewController ()
@end

类扩展是在类的声明之后,实现之前。通过clang我们可以看到是编译到主类中,跟主类的加载流程一样 而分类的底层是category_t结构体类型
类扩展具有跟主类同样的成员变量,属性,方法等。一般的私有属性或者想要独立区别的属性写到类扩展。
在_read_images断点:

关联对象

关联对象:意义是用来存储,因为分类没有属性,因此在set和get方法时需要关联对象来存储value。然后根据policy关联策略(retain,copy等),把cate_name和value关联

关联对象:底层分析(_object_set_associative_reference)
void
_object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
{
    // This code used to work when nil was passed for object and key. Some code
    // probably relies on that to not crash. Check and handle it explicitly.
    // rdar://problem/44094390
    if (!object && !value) return;

    if (object->getIsa()->forbidsAssociatedObjects())
        _objc_fatal("objc_setAssociatedObject called on instance (%p) of class %s which does not allow associated objects", object, object_getClassName(object));

    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    //
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());

        if (value) {
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            auto &refs = refs_result.first->second;
            auto result = refs.try_emplace(key, std::move(association));
            if (!result.second) {
                association.swap(result.first->second);
            }
        } else {
            auto refs_it = associations.find(disguised);
            if (refs_it != associations.end()) {
                auto &refs = refs_it->second;
                auto it = refs.find(key);
                if (it != refs.end()) {
                    association.swap(it->second);
                    refs.erase(it);
                    if (refs.size() == 0) {
                        associations.erase(refs_it);

                    }
                }
            }
        }
    }

    // Call setHasAssociatedObjects outside the lock, since this
    // will call the object's _noteAssociatedObjects method if it
    // has one, and this may trigger +initialize which might do
    // arbitrary stuff, including setting more associated objects.
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // release the old value (outside of the lock).
    association.releaseHeldValue();
}

而其中AssociationsManager析构函数,

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    static Storage _mapStorage;//作用域,全局变量

public:
    AssociationsManager()   { AssociationsManagerLock.lock(); }//构造函数
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }//析构函数
    AssociationsHashMap &get() {//单例
        return _mapStorage.get();
    }

    static void init() {
        _mapStorage.init();
    }
}

AssociationsHashMap单例,唯一的一张表
在这里插入图片描述
try_emplace:
在这里插入图片描述
InsertIntoBucket:插入bucket筒子,对Ptr赋值,insertIntoBucketImpl:3/4扩容并两倍扩容:
在这里插入图片描述
在这里插入图片描述
然后第二次进入try_emplace:
在这里插入图片描述
由于第一次进入try_emplace时刚创建插入bucket,都为空,因此第二次走try_emplace ObjectAssociationMap,在bucket里面插入ObjectAssociation,里面是key和value。

AssociationsManager ->AssociationsHashMap(Buckets 内部结构):
DisguisedPtr<objc_object> ObjectAssociationMap
DisguisedPtr<objc_object> ObjectAssociationMap
DisguisedPtr<objc_object> ObjectAssociationMap
….

-> ObjectAssociationMap(内部结构):
const void * ObjcAssociation
const void * ObjcAssociation
const void * ObjcAssociation
….

-> ObjectAssociation
uintptr_t _policy
id _value
….

AssociationsHashMap结构:
在这里插入图片描述
其中Buckets结构:
在这里插入图片描述

关联对象:设置流程

1: 创建⼀个 AssociationsManager 管理类
2: 获取唯⼀的全局静态哈希Map
3: 判断是否插⼊的关联值是否存在:
3.1: 存在⾛第4步
3.2: 不存在就⾛ : 关联对象插⼊空流程
4: 创建⼀个空的 ObjectAssociationMap 去取查询的键值对
5: 如果发现没有这个 key 就插⼊⼀个 空的 BucketT进去 返回
6: 标记对象存在关联对象
7: ⽤当前 修饰策略 和 值 组成了⼀个 ObjcAssociation 替换原来 BucketT 中的空
8: 标记⼀下 ObjectAssociationMap 的第⼀次为 false

关联对象:插⼊空流程

1: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
2: 清理迭代器
3: 其实如果插⼊空置 相当于清除

关联对象:取值流程

1: 创建⼀个 AssociationsManager 管理类
2: 获取唯⼀的全局静态哈希Map
3: 根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器
4: 如果这个迭代查询器不是最后⼀个 获取 : ObjectAssociationMap (这⾥有策略和value)
5: 找到ObjectAssociationMap的迭代查询器获取⼀个经过属性修饰符修饰的value
6: 返回_value

总结:

其实就是两层哈希map , 存取的时候两层处理(类似⼆位数组)

总结分析

  • 我们通过探索_objc_init函数知道了里面初始化内容的意义
  • 通过分析_dyld_objc_notify_register(&map_images, load_images, unmap_image)我们知道map_images和load_images的关联
  • 通过分析map_images知道了read_images中readClass读取由编译器编写的类和元类
  • 通过对read_iamges->realizeClassWithoutSwift分析知道了类中ro和rw的处理
  • dyld->images->内存->类(方法、协议、…)
  • images(macho)->地址->表->类->初始化(rw-ro)->methodlist
  • read_images->read_class(名字+类)->realizeClassWithoutSwift(ro-rw superclass isa)->methodizeClass()->prepareMethodLists(方法名写入+排序)
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

☆MOON

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值