美文网首页
OC底层探索(十四): 类的加载(二)

OC底层探索(十四): 类的加载(二)

作者: ShawnAlex | 来源:发表于2022-05-14 23:55 被阅读0次

    所用版本:

    • 处理器: Intel Core i9
    • MacOS 12.3.1
    • Xcode 13.3.1
    • objc4-838

    建议先看下 OC底层探索(十三): 类的加载(一)

    还是先看下类的初始化方法_objc_init代码以及示意图

    /***********************************************************************
    * _objc_init
    * Bootstrap initialization. Registers our image notifier with dyld.
    * Called by libSystem BEFORE library initialization time
    **********************************************************************/
    
    void _objc_init(void)
    {
        static bool initialized = false;
        if (initialized) return;
        initialized = true;
        
        // fixme defer initialization until an objc-using image is found?
    
        // 环境变量初始化 
        environ_init();
        // 线程处理
        tls_init();
        // 运行C++静态构造函数。
        static_init();
        // runtime运行时初始化
        runtime_init();
        // objc异常处理系统初始化
        exception_init();
    #if __OBJC2__
        // 缓存初始化
        cache_t::init();
    #endif
        // 启动回调机制
        _imp_implementationWithBlock_init();
       // dyld通知注册
        _dyld_objc_notify_register(&map_images, load_images, unmap_image);
    
    #if __OBJC2__
        didCallDyldNotifyRegister = true;
    #endif
    }
    
    _objc_init

    以及_read_images的示意图. _dyld_objc_notify_register(&map_images, load_images, unmap_image); 第一个参数: &map_imagesmap_images_nolock_read_images

    read_image

    _read_images

    read_image做了:

    • 条件控制进行一次加载
    • 修复预编译阶段的@selector混乱问题
    • 错误混乱的类处理
    • 修复重新映射的类
    • 修复消息
    • 协议读取
    • 分类处理
    • 类的加载处理
    • 优化类
    _read_images

    [修复重新映射的类]

    修复重新映射的类

    其中

    // 类
    GETSECT(_getObjc2ClassRefs,           Class,           "__objc_classrefs");  
    // 父类
    GETSECT(_getObjc2SuperRefs,           Class,           "__objc_superrefs");
    
    remapClassRef remapClass image.png

    循环判断, 如果找到未映射当前类 &父类, 进行修复。

    [修复消息]

    修复消息

    一些特殊的消息处理, 正常消息已经被llvm给处理了, 这里主要修复一些特殊消息。例如: alloc的消息改为直接调用objc_alloc (而不是走alloc的实现) 这种。

    fixupMessageRef

    [协议读取]

    协议读取
    GETSECT(_getObjc2ProtocolList,        protocol_t * const,    "__objc_protolist");
    
    • 创建协议表 NXMapTable *protocol_map = protocols()
    • Mach-O中获取__objc_protolist中协议
    • 遍历读取插入表, 方法: readProtocol
    readProtocol

    稍微打印一下, 看一下:

    printf("%s - 协议- %s\n",__func__,newproto->mangledName);
    
    image.png

    可发现系统的代理还是蛮多的 -_- !!!, 接下来我们自定义一个代理走下流程

    // 自定义代理
    @protocol SRTestDelegate <NSObject>
    
    - (void)SRTestAction;
    
    @end
    

    很遗憾发现并没有出现


    错误例子

    为什么呢? 因为我并未对代理进行调用, 这里也体现了苹果严谨性。每调用还想让我给你个空床位, 做你大头梦 :)

    正确例子

    加个代理调用就行, 实现方法可以不写。, 接下来我们跟一下源码看下流程。

    可看到, 直接走到这里


    绑定isa指针
    • 先通过initIsa创建isa指针,与mach-o里面的协议相关结构体进行绑定
    • 把绑定协议名, 协议结构体, 插入新建的协议表里面

    当然如果协议出现没加载情况, 也会有一步修复

    修复协议

    [分类处理]

    分类处理
    • 只有在初始类完成后才能执行此操作。
    • 对于启动时存在的分类,需要推迟到+ load之后调用。

    [类的加载处理]

    [非懒加载类]
    非懒加载类

    只有非懒加载类才走, 正常添加的类不会走这里, 比如我加一个打印

                const char *mangledName = cls->nonlazyMangledName();
                
                if (strcmp(mangledName, "SRTest") == 0) {
                    printf("%s 测试 %s\n",__func__,mangledName);
                }
    
    正常类

    可发现并没走。其中classref_t const *classlist = hi->nlclslist(&count);是从非懒加载类找数据。

    nlclslist

    其中_getObjc2NonlazyClassList是获取Mach-O的静态段__objc_nlclslist非懒加载类表

    GETSECT(_getObjc2NonlazyClassList,    classref_t const,      "__objc_nlclslist");
    

    这个里稍微提一下懒加载类与非懒加载类


    懒加载类与非懒加载类

    • 懒加载:
      • 并未实现+load方法
      • 数据加载推迟到第一次消息发送objc_msgSend, lookUpImpOrForward消息慢速查找的时候进行初始化的。
    • 非懒加载:
      • 自己实现+load方法 (会出现在__objc_nlclslist中)

      • 子类实现了+ load方法。(父类不会出现在__objc_nlclslist中, 但是子类在)

      • 分类实现了+load方法。(自己以及子类的分类,不是在map_images而是在prepare_load_methods中实例化类的,自己类/子类/分类 都不会出现在__objc_nlclslist中)

      • map_images的时候加载所有类数据(提前加载),为+ load的调用做准备。

    两者调用流程也有区别

    懒加载类:
    懒加载类

    执行顺序:

    • dyld start
    • main
    • objc_alloc
    • objc_alloc
    • lookUpImpOrForward → realizeAndInitializeIfNeeded_locked → realizeClassMaybeSwiftAndLeaveLocked
    • realizeClassWithoutSwift
    • methodizeClass
      ......
    非懒加载类:
    非懒加载类

    执行顺序:

    • dyld`start
    • dyld4::prepare
    • dyld4::Loader::findAndRunAllInitializers
    • _objc_init
    • dyld4::APIs::_dyld_objc_notify_register
    • dyld4::RuntimeState::setObjCNotifiers
    • map_images
    • _read_images
    • realizeClassWithoutSwift
    • methodizeClass
      ...
    [子类 +load]

    子类的+load方法稍微看下, SRTestSubSRTest子类, 子类有load方法, 父类没有, 运行看一下

    子类+load方法
    可验证: 父类不会出现在__objc_nlclslist中, 但是子类在__objc_nlclslist
    [分类 +load]

    看下分类的+load, 分类如果忘记创建参考: OC分类创建

    分类添加+load方法

    分类添加+load方法 非懒加载并没有断住 分类

    运行, 可发现

    • 非懒加载并没有断住, 所以不在__objc_nlclslist,
    • 在main中可看出分类的+load` 在加载当前类之前就运行了
        // Category discovery MUST BE Late to avoid potential races
        // when other threads call the new category code before
        // this thread finishes its fixups.
    
        // +load handled by prepare_load_methods()
    

    再回头看下官方注释, 可发现分类 +load也是在prepare_load_methods()执行的

    prepare load methods

    回到之前看一下, 其实我们是想看下非懒加载类的执行顺序, 那么我们在普通类写一个+load方法, 把它变成非懒加载类看源码流程.

    • addClassTableEntry: 将非懒加载类插入类表,存储到内存,如果已经添加就不会载添加,需要确保整个结构都被添加.
    addClassTableEntry

    接着往下走了 realizeClassWithoutSwift

    realizeClassWithoutSwift

    类的初始化核心方法: realizeClassWithoutSwift

    realizeClassWithoutSwift

    实现类方法realizeClassWithoutSwift

    /***********************************************************************
    * realizeClassWithoutSwift
    * Performs first-time initialization on class cls, 
    * including allocating its read-write data.
    * Does not perform any Swift-side initialization.
    * Returns the real class structure for the class. 
    * Locking: runtimeLock must be write-locked by the caller
    **********************************************************************/
    static Class realizeClassWithoutSwift(Class cls, Class previously)
    {
        runtimeLock.assertLocked();
    
        class_rw_t *rw;
        Class supercls;
        Class metacls;
    
        if (!cls) return nil;
        if (cls->isRealized()) {
            validateAlreadyRealizedClass(cls);
            return cls;
        }
        ASSERT(cls == remapClass(cls));
    
        // fixme verify class is not in an un-dlopened part of the shared cache?
    
        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 {
            // 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);
        }
    
        cls->cache.initializeToEmptyOrPreoptimizedInDisguise();
    
    #if FAST_CACHE_META
        if (isMeta) cls->cache.setBit(FAST_CACHE_META);
    #endif
    
        // Choose an index for this class.
        // Sets cls->instancesRequireRawIsa if indexes no more indexes are available
        cls->chooseClassArrayIndex();
    
        if (PrintConnecting) {
            _objc_inform("CLASS: realizing class '%s'%s %p %p #%u %s%s",
                         cls->nameForLogging(), isMeta ? " (meta)" : "", 
                         (void*)cls, ro, cls->classArrayIndex(),
                         cls->isSwiftStable() ? "(swift)" : "",
                         cls->isSwiftLegacy() ? "(pre-stable swift)" : "");
        }
    
        // Realize superclass and metaclass, if they aren't already.
        // This needs to be done after RW_REALIZED is set above, for root classes.
        // This needs to be done after class index is chosen, for root metaclasses.
        // This assumes that none of those classes have Swift contents,
        //   or that Swift's initializers have already been called.
        //   fixme that assumption will be wrong if we add support
        //   for ObjC subclasses of Swift classes.
        supercls = realizeClassWithoutSwift(remapClass(cls->getSuperclass()), nil);
        metacls = realizeClassWithoutSwift(remapClass(cls->ISA()), nil);
    
    #if SUPPORT_NONPOINTER_ISA
        if (isMeta) {
            // Metaclasses do not need any features from non pointer ISA
            // This allows for a faspath for classes in objc_retain/objc_release.
            cls->setInstancesRequireRawIsa();
        } else {
            // Disable non-pointer isa for some classes and/or platforms.
            // Set instancesRequireRawIsa.
            bool instancesRequireRawIsa = cls->instancesRequireRawIsa();
            bool rawIsaIsInherited = false;
            static bool hackedDispatch = false;
    
            if (DisableNonpointerIsa) {
                // Non-pointer isa disabled by environment or app SDK version
                instancesRequireRawIsa = true;
            }
            else if (!hackedDispatch  &&  0 == strcmp(ro->getName(), "OS_object"))
            {
                // hack for libdispatch et al - isa also acts as vtable pointer
                hackedDispatch = true;
                instancesRequireRawIsa = true;
            }
            else if (supercls  &&  supercls->getSuperclass()  &&
                     supercls->instancesRequireRawIsa())
            {
                // This is also propagated by addSubclass()
                // but nonpointer isa setup needs it earlier.
                // Special case: instancesRequireRawIsa does not propagate
                // from root class to root metaclass
                instancesRequireRawIsa = true;
                rawIsaIsInherited = true;
            }
    
            if (instancesRequireRawIsa) {
                cls->setInstancesRequireRawIsaRecursively(rawIsaIsInherited);
            }
        }
    // SUPPORT_NONPOINTER_ISA
    #endif
    
        // Update superclass and metaclass in case of remapping
        cls->setSuperclass(supercls);
        cls->initClassIsa(metacls);
    
        // Reconcile instance variable offsets / layout.
        // This may reallocate class_ro_t, updating our ro variable.
        if (supercls  &&  !isMeta) reconcileInstanceVariables(cls, supercls, ro);
    
        // Set fastInstanceSize if it wasn't set already.
        cls->setInstanceSize(ro->instanceSize);
    
        // Copy some flags from ro to rw
        if (ro->flags & RO_HAS_CXX_STRUCTORS) {
            cls->setHasCxxDtor();
            if (! (ro->flags & RO_HAS_CXX_DTOR_ONLY)) {
                cls->setHasCxxCtor();
            }
        }
        
        // Propagate the associated objects forbidden flag from ro or from
        // the superclass.
        if ((ro->flags & RO_FORBIDS_ASSOCIATED_OBJECTS) ||
            (supercls && supercls->forbidsAssociatedObjects()))
        {
            rw->flags |= RW_FORBIDS_ASSOCIATED_OBJECTS;
        }
    
        // Connect this class to its superclass's subclass lists
        if (supercls) {
            addSubclass(supercls, cls);
        } else {
            addRootClass(cls);
        }
    
        // Attach categories
        methodizeClass(cls, previously);
    
        return cls;
    }
    
    

    继续探索前, 先了解下2个概念


    clean memory 和 dirty memory

    • clean memory: 加载后不会发生更改的内存, 例如: class_ro_t, 因为它是只读的
    • dirty memory: 进程运行时会发生更改的内存,

    类结构一经使用就会变成dirty memory, 因为runtime会向它写入新的数据。例如: 创建一个新的方法缓存并从类中指向它。dirty memoryclean memory 要"昂贵"的多, 只要进程运行, 它就必须在。比如手机64G, 升级到128G是不是要添"马内", 侧面也表示了有多昂贵 (^ - ^)。同时clean memory可以进行移除从而节省更多的内存空间。因为如果你需要clean memory, 系统可以从磁盘从新加载。同时因为IOS不使用swap, 所以dirty memory在ios中代价是很大的。苹果更建议保持"清洁"的数据越多越好, 通过分类出那些永远不会更改的数据, 可以把大部分数据储存为clean memory.


    realizeClassWithoutSwift会操作ro, rw信息。其中

    • ro : Read Only只读 -- clean memory -- 结构体, 包含类的名称, 方法, 协议, 实例变量等编译期的信息。
    • rw: Read Write读写 -- dirty memory-- 结构体, 复制ro, 目的是对于方法, 属性等进行操作。
    • rwe: Read Write Extra, 额外读写 -- dirty memory-- 结构体, ro部分复制, 只操作需要改变的信息。

    class_ro_t, class_rw_t , class_rw_ext_t 结构体详细内容看末尾附录 (class_rw_ext_t 真是真滴少 :) )

    因为ro 是沙盒路径加载, 只读不能操作, 那么当需要操作的时候会将ro 复制一份存放到rw上。

    rw

    这也体现了 Objective-C 是一门动态语言, 可以在运行时更改方法, 属性等。分类可以在不改变类设计的前提下, 将新方法添加到类中。

    但是对于一个类, 我们需要大部分改变的就是methods, properties, protocols等 , 其他的例如父类关系, Flags等不会进行更改, 所以这部分"dirty memory" 很浪费的。由此苹果对class_rw_t进行进一步拆分出class_rw_ext_t用来存储这部分可能被修改的methods, properties, protocols等,class_rw_ext_t只有在需要的时候,才被创建, 相对于旧版runtime可以节省下的内存空间。

    rwe

    熟悉完下一章继续看 realizeClassWithoutSwift

    附: class_ro_t, class_rw_t , class_rw_ext_t 详细

    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        union {
            const uint8_t * ivarLayout;
            Class nonMetaclass;
        };
    
        explicit_atomic<const char *> name;
        WrappedPtr<method_list_t, method_list_t::Ptrauth> baseMethods;
        protocol_list_t * baseProtocols;
        const ivar_list_t * ivars;
    
        const uint8_t * weakIvarLayout;
        property_list_t *baseProperties;
    
        // This field exists only when RO_HAS_SWIFT_INITIALIZER is set.
        _objc_swiftMetadataInitializer __ptrauth_objc_method_list_imp _swiftMetadataInitializer_NEVER_USE[0];
    
        _objc_swiftMetadataInitializer swiftMetadataInitializer() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                return _swiftMetadataInitializer_NEVER_USE[0];
            } else {
                return nil;
            }
        }
    
        const char *getName() const {
            return name.load(std::memory_order_acquire);
        }
    
        class_ro_t *duplicate() const {
            bool hasSwiftInitializer = flags & RO_HAS_SWIFT_INITIALIZER;
    
            size_t size = sizeof(*this);
            if (hasSwiftInitializer)
                size += sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
    
            class_ro_t *ro = (class_ro_t *)memdup(this, size);
    
            if (hasSwiftInitializer)
                ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
    
    #if __has_feature(ptrauth_calls)
            // Re-sign the method list pointer.
            ro->baseMethods = baseMethods;
    #endif
    
            return ro;
        }
    
        Class getNonMetaclass() const {
            ASSERT(flags & RO_META);
            return nonMetaclass;
        }
    
        const uint8_t *getIvarLayout() const {
            if (flags & RO_META)
                return nullptr;
            return ivarLayout;
        }
    };
    

    [class_rw_ext_t]

    struct class_rw_t {
        // Be warned that Symbolication knows the layout of this structure.
        uint32_t flags;
        uint16_t witness;
    #if SUPPORT_INDEXED_ISA
        uint16_t index;
    #endif
    
        explicit_atomic<uintptr_t> ro_or_rw_ext;
    
        Class firstSubclass;
        Class nextSiblingClass;
    
    private:
        using ro_or_rw_ext_t = objc::PointerUnion<const class_ro_t, class_rw_ext_t, PTRAUTH_STR("class_ro_t"), PTRAUTH_STR("class_rw_ext_t")>;
    
        const ro_or_rw_ext_t get_ro_or_rwe() const {
            return ro_or_rw_ext_t{ro_or_rw_ext};
        }
    
        void set_ro_or_rwe(const class_ro_t *ro) {
            ro_or_rw_ext_t{ro, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_relaxed);
        }
    
        void set_ro_or_rwe(class_rw_ext_t *rwe, const class_ro_t *ro) {
            // the release barrier is so that the class_rw_ext_t::ro initialization
            // is visible to lockless readers
            rwe->ro = ro;
            ro_or_rw_ext_t{rwe, &ro_or_rw_ext}.storeAt(ro_or_rw_ext, memory_order_release);
        }
    
        class_rw_ext_t *extAlloc(const class_ro_t *ro, bool deep = false);
    
    public:
        void setFlags(uint32_t set)
        {
            __c11_atomic_fetch_or((_Atomic(uint32_t) *)&flags, set, __ATOMIC_RELAXED);
        }
    
        void clearFlags(uint32_t clear) 
        {
            __c11_atomic_fetch_and((_Atomic(uint32_t) *)&flags, ~clear, __ATOMIC_RELAXED);
        }
    
        // set and clear must not overlap
        void changeFlags(uint32_t set, uint32_t clear) 
        {
            ASSERT((set & clear) == 0);
    
            uint32_t oldf, newf;
            do {
                oldf = flags;
                newf = (oldf | set) & ~clear;
            } while (!OSAtomicCompareAndSwap32Barrier(oldf, newf, (volatile int32_t *)&flags));
        }
    
        class_rw_ext_t *ext() const {
            return get_ro_or_rwe().dyn_cast<class_rw_ext_t *>(&ro_or_rw_ext);
        }
    
        class_rw_ext_t *extAllocIfNeeded() {
            auto v = get_ro_or_rwe();
            if (fastpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext);
            } else {
                return extAlloc(v.get<const class_ro_t *>(&ro_or_rw_ext));
            }
        }
    
        class_rw_ext_t *deepCopy(const class_ro_t *ro) {
            return extAlloc(ro, true);
        }
    
        const class_ro_t *ro() const {
            auto v = get_ro_or_rwe();
            if (slowpath(v.is<class_rw_ext_t *>())) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro;
            }
            return v.get<const class_ro_t *>(&ro_or_rw_ext);
        }
    
        void set_ro(const class_ro_t *ro) {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                v.get<class_rw_ext_t *>(&ro_or_rw_ext)->ro = ro;
            } else {
                set_ro_or_rwe(ro);
            }
        }
    
        const method_array_t methods() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->methods;
            } else {
                return method_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseMethods};
            }
        }
    
        const property_array_t properties() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->properties;
            } else {
                return property_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProperties};
            }
        }
    
        const protocol_array_t protocols() const {
            auto v = get_ro_or_rwe();
            if (v.is<class_rw_ext_t *>()) {
                return v.get<class_rw_ext_t *>(&ro_or_rw_ext)->protocols;
            } else {
                return protocol_array_t{v.get<const class_ro_t *>(&ro_or_rw_ext)->baseProtocols};
            }
        }
    };
    

    [class_rw_ext_t]

    struct class_rw_ext_t {
        DECLARE_AUTHED_PTR_TEMPLATE(class_ro_t)
        class_ro_t_authed_ptr<const class_ro_t> ro;
        method_array_t methods;
        property_array_t properties;
        protocol_array_t protocols;
        char *demangledName;
        uint32_t version;
    };
    
    

    相关文章

      网友评论

          本文标题:OC底层探索(十四): 类的加载(二)

          本文链接:https://www.haomeiwen.com/subject/yykjurtx.html