美文网首页IOS知识积累
类加载原理补充-关联对象底层原理

类加载原理补充-关联对象底层原理

作者: 浅墨入画 | 来源:发表于2021-08-29 13:45 被阅读0次

    分类加载的补充

    objc4-818.2源码中创建LGPerson类以及LGPerson+LGALGPerson+LGB分类,其中LGPerson类中未实现+ (void)load{}方法,分类中实现了load方法。源码load_categories_nolockattachCategories方法中分别添加了如下代码

    const char *mangledName = cls->nonlazyMangledName();
    if (strcmp(mangledName, "LGPerson") == 0)
    {
         printf("%s -LGPerson....\n",__func__);
    }
    
    • 在添加的代码printf("%s -LGPerson....\n",__func__);处添加断点
    • main方法中添加如下代码,运行源码进行调试
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // class_data_bits_t
            LGPerson * person = [LGPerson alloc];
            [person saySomething];
        }
        return 0;
    }
    
    <!-- attachCategories方法内进行调试 -->
    (lldb) p ro.baseMethods()
    (method_list_t *) $0 = 0x00000001000044e8
      Fix-it applied, fixed expression was: 
        ro->baseMethods()
    (lldb) p *$0 
    (method_list_t) $1 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 10)
    }
    (lldb) p $1.get(0)   // 指针地址指向的就是method_t
    (method_t) $2 = {}
    
    • 由源码可知method_list_t继承自entsize_list_tt,查看其源码
    struct entsize_list_tt {
        uint32_t entsizeAndFlags;
        uint32_t count;
    
        uint32_t entsize() const {
            return entsizeAndFlags & ~FlagMask;
        }
        uint32_t flags() const {
            return entsizeAndFlags & FlagMask;
        }
    
        Element& getOrEnd(uint32_t i) const { 
            ASSERT(i <= count);
            return *PointerModifier::modify(*this, (Element *)((uint8_t *)this + sizeof(*this) + i*entsize()));
        }
        Element& get(uint32_t i) const { 
            ASSERT(i < count);
            // 获取方法实际上调用的getOrEnd
            return getOrEnd(i);
        }
    
        size_t byteSize() const {
            return byteSize(entsize(), count);
        }
        
        static size_t byteSize(uint32_t entsize, uint32_t count) {
            return sizeof(entsize_list_tt) + count*entsize;
        }
      ......
    
    • 由上面工程调试可知执行了源码attachCategories方法

    如果我们把分类LGPerson+LGA中的load方法屏蔽掉,再运行工程,发现没有执行attachCategories方法,打印信息如下

    readClass LGPerson....
    _read_images LGPerson....
    realizeClassWithoutSwift LGPerson....
    prepareMethodLists - LGPerson....
    realizeClassWithoutSwift LGPerson....
    methodizeClass -LGPerson....
    prepareMethodLists - LGPerson....
    attachToClass -LGPerson....
    getMethodNoSuper_nolock -LGPerson....
    2021-08-28 17:27:22.788465+0800 KCObjcBuild[79133:10109465] -[LGPerson(LGB) saySomething]
    

    如果再创建分类LGPerson+LGCLGPerson+LGD,其中分类LGPerson+LGC实现load方法,运行工程发现执行了attachCategories方法,打印信息如下

    readClass LGPerson....
    operator() -LGPerson....
    operator() -LGPerson....
    operator() -LGPerson....
    operator() -LGPerson....
    realizeClassWithoutSwift LGPerson....
    prepareMethodLists - LGPerson....
    prepareMethodLists - LGPerson....
    realizeClassWithoutSwift LGPerson....
    methodizeClass -LGPerson....
    prepareMethodLists - LGPerson....
    attachToClass -LGPerson....
    attachToClass -LGPerson....
    attachCategories - LGPerson....
    prepareMethodLists - LGPerson....
    getMethodNoSuper_nolock -LGPerson....
    2021-08-28 17:41:17.662930+0800 KCObjcBuild[79229:10118616] -[LGPerson(LGB) saySomething]
    

    得出结论:

    • 超过一个分类实现了load方法,就会执行attachCategories方法

    load_images方法添加断点,重新运行工程进行调试

    image.png

    主类中没有实现load方法,迫使执行prepare_load_methods方法,继续调试执行到realizeClassWithoutSwift -> attachCategories

    image.png

    疑问:明明有四个分类,这里为什么之后两个?

    // lldb进行调试查看
    (lldb) p cats_list[0]
    (const locstamped_category_t) $0 = {
      cat = 0x0000000100004438
      hi = 0x0000000100936c90
    }
    (lldb) p $0.cat 
    (category_t *const) $1 = 0x0000000100004438
    (lldb) p *$1 
    (category_t) $2 = {
      name = 0x0000000100003af6 "LGA"
      cls = 0x0000000100004988
      instanceMethods = {
        ptr = 0x0000000100004388
      }
      classMethods = {
        ptr = 0x00000001000043d8
      }
      protocols = 0x0000000000000000
      instanceProperties = 0x0000000100004410
      _classProperties = 0x0000000000000000
    }
    (lldb) p cats_list[1]
    (const locstamped_category_t) $3 = {
      cat = 0x0000000100004830
      hi = 0x0000000100936c90
    }
    (lldb) p $3.cat 
    (category_t *const) $4 = 0x0000000100004830
    (lldb) p *$4
    (category_t) $5 = {
      name = 0x0000000100003b07 "LGB"
      cls = 0x0000000100004988
      instanceMethods = {
        ptr = 0x0000000100004768
      }
      classMethods = {
        ptr = 0x00000001000047b8
      }
      protocols = 0x0000000000000000
      instanceProperties = 0x0000000100004808
      _classProperties = 0x0000000000000000
    }
    

    通过上面调试发现只有LGA、LGB,因为创建的LGC、LGD没有添加任何属性与方法,所以只有两个

    LLVM读取class_ro和分类

    分类的加载需要排序。由attachList可知methodList中存储的是LGA的数组指针(排序) 、LGB的数组指针(排序)、主类的数组指针(排序)

    • 查看attachToClass源码,最终执行到attachCategories
    static void
    attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
                     int flags)
    {
        if (slowpath(PrintReplacedMethods)) {
            printReplacements(cls, cats_list, cats_count);
        }
        if (slowpath(PrintConnecting)) {
            _objc_inform("CLASS: attaching %d categories to%s class '%s'%s",
                         cats_count, (flags & ATTACH_EXISTING) ? " existing" : "",
                         cls->nameForLogging(), (flags & ATTACH_METACLASS) ? " (meta)" : "");
        }
    
        constexpr uint32_t ATTACH_BUFSIZ = 64;
        method_list_t   *mlists[ATTACH_BUFSIZ];
        property_list_t *proplists[ATTACH_BUFSIZ];
        protocol_list_t *protolists[ATTACH_BUFSIZ];
    
        uint32_t mcount = 0;
        uint32_t propcount = 0;
        uint32_t protocount = 0;
        bool fromBundle = NO;
        bool isMeta = (flags & ATTACH_METACLASS);
        auto rwe = cls->data()->extAllocIfNeeded();
        
        auto rw = cls->data();
        auto ro = rw->ro();
        const char *mangledName = cls->nonlazyMangledName();
        if (strcmp(mangledName, "LGPerson") == 0)
        {
            if (!isMeta) {
                printf("%s - LGPerson....\n",__func__);
            }
        }
    
        for (uint32_t i = 0; i < cats_count; i++) {
            // 分类列表cats_list
            auto& entry = cats_list[i];
            // 读取分类中的方法
            method_list_t *mlist = entry.cat->methodsForMeta(isMeta);
            if (mlist) {
                if (mcount == ATTACH_BUFSIZ) {
                    prepareMethodLists(cls, mlists, mcount, NO, fromBundle, __func__);
                    rwe->methods.attachLists(mlists, mcount);
                    mcount = 0;
                }
                mlists[ATTACH_BUFSIZ - ++mcount] = mlist;
                fromBundle |= entry.hi->isBundle();
            }
    
            property_list_t *proplist =
                entry.cat->propertiesForMeta(isMeta, entry.hi);
            if (proplist) {
                if (propcount == ATTACH_BUFSIZ) {
                    rwe->properties.attachLists(proplists, propcount);
                    propcount = 0;
                }
                proplists[ATTACH_BUFSIZ - ++propcount] = proplist;
            }
    
            protocol_list_t *protolist = entry.cat->protocolsForMeta(isMeta);
            if (protolist) {
                if (protocount == ATTACH_BUFSIZ) {
                    rwe->protocols.attachLists(protolists, protocount);
                    protocount = 0;
                }
                protolists[ATTACH_BUFSIZ - ++protocount] = protolist;
            }
        }
    
        if (mcount > 0) {
            // 对methodList进行排序
            prepareMethodLists(cls, mlists + ATTACH_BUFSIZ - mcount, mcount,
                               NO, fromBundle, __func__);
            rwe->methods.attachLists(mlists + ATTACH_BUFSIZ - mcount, mcount);
            if (flags & ATTACH_EXISTING) {
                flushCaches(cls, __func__, [](Class c){
                    // constant caches have been dealt with in prepareMethodLists
                    // if the class still is constant here, it's fine to keep
                    return !c->cache.isConstantOptimizedCache();
                });
            }
        }
    
        rwe->properties.attachLists(proplists + ATTACH_BUFSIZ - propcount, propcount);
        rwe->protocols.attachLists(protolists + ATTACH_BUFSIZ - protocount, protocount);
    }
    
    • 主类LGPerson、分类LGALGB里面都有saySomething方法,按照之前二分查找逻辑查找saySomething方法,如果没有找到,执行--(减减操作)。疑问?LGA、LGB会--几次?
    1. main函数[person saySomething];处添加断点
    2. 查看lookUpImpOrForward源码
    // 慢速查找
    // 方法调用 objc_msgSend
    // 1 —— 发送消息objc_msgSend 缓存快速查找(cache_t)
    // 2 —— 没有命中,lookUpImpOrForward慢速查找
    NEVER_INLINE
    IMP lookUpImpOrForward(id inst, SEL sel, Class cls, int behavior)
    {
        // 省略代码
     ......
        //    加锁,目的是保证读取的线程安全
        runtimeLock.lock();
    
        //  是否是已知类:判断当前类是否是已经被认可的类,即已经加载的类
        checkIsKnownClass(cls);
    
        // 判断类是否实现,如果没有,需要先实现
        cls = realizeAndInitializeIfNeeded_locked(inst, cls, behavior & LOOKUP_INITIALIZE);
        // runtimeLock may have been dropped but is now locked again
        runtimeLock.assertLocked();
        curClass = cls;
    
        //    递归
        for (unsigned attempts = unreasonableClassCount();;) {
            if (curClass->cache.isConstantOptimizedCache(/* strict */true)) {
    #if CONFIG_USE_PREOPT_CACHES
                imp = cache_getImp(curClass, sel);
                if (imp) goto done_unlock;
                curClass = curClass->cache.preoptFallbackClass();
    #endif
            } else {
                // curClass method list.
                // 在当前的类的方法列表中查找方法(采用二分查找算法),如果找到,则返回,将方法缓存到cache中
                Method meth = getMethodNoSuper_nolock(curClass, sel);
                if (meth) {
                    imp = meth->imp(false);
                    goto done;
                }
    
                // 未找到,superclass找到父类或者父元类继续查找,如果父类是nil,默认赋值forward_imp
                if (slowpath((curClass = curClass->getSuperclass()) == nil)) {
                    // No implementation found, and method resolver didn't help.
                    // Use forwarding.
                    // 父类为nil,即继承链都未找到方法实现,跳出循环
                    imp = forward_imp;
                    break;
                }
            }
    
            // Halt if there is a cycle in the superclass chain.
            if (slowpath(--attempts == 0)) {
                _objc_fatal("Memory corruption in class list.");
            }
    
            // Superclass找到父类,在父类的cache中查找.
            // 从父类缓存中查找 - 再次进入汇变查找
            // - 如果查找到done
            // - 查找不到,循环superclass,再查找父类
            // Superclass cache.
            imp = cache_getImp(curClass, sel);
            if (slowpath(imp == forward_imp)) {
                break;
            }
            if (fastpath(imp)) {
                // Found the method in a superclass. Cache it in this class.
                goto done;
            }
        }
    ...... //省略代码
    }
    
    1. 查看getMethodNoSuper_nolock源码,在printf("%s -LGPerson....\n",__func__);添加断点
    2. 运行工程,断点执行到printf("%s -LGPerson....\n",__func__);,执行调试
    image.png
    // lldb调试信息
    (lldb) p methods.beginLists()
    (const method_list_t_authed_ptr<method_list_t> *) $0 = 0x00007ffeefbfeea0
    (lldb) p *$0 
    (const method_list_t_authed_ptr<method_list_t>) $1 = {
      ptr = 0x00000001000041c8
    }
    (lldb) p $1.ptr 
    (method_list_t *const) $2 = 0x00000001000041c8
    (lldb) p *$2 
    (method_list_t) $3 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 27, count = 16)
    }
    
    1. 查看search_method_list_inline方法源码 -> findMethodInUnsortedMethodList方法源码
    ALWAYS_INLINE static method_t *
    findMethodInUnsortedMethodList(SEL key, const method_list_t *list)
    {
        if (list->isSmallList()) {
            if (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS && objc::inSharedCache((uintptr_t)list)) {
                // 二分查找
                return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSEL(); });
            } else {
                return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.getSmallNameAsSELRef(); });
            }
        } else {
            return findMethodInUnsortedMethodList(key, list, [](method_t &m) { return m.big().name; });
        }
    }
    
    /***********************************************************************
     * search_method_list_inline
     **********************************************************************/
    template<class getNameFunc>
    ALWAYS_INLINE static method_t *
    findMethodInSortedMethodList(SEL key, const method_list_t *list, const getNameFunc &getName)
    {
        ASSERT(list);
    
        auto first = list->begin();
        auto base = first;
        decltype(first) probe;
    
        uintptr_t keyValue = (uintptr_t)key;
        uint32_t count;
        // base相当于low地址,count是max地址,probe是middle地址
        for (count = list->count; count != 0; count >>= 1) {
            // 指针平移至中间位置
            // 从首地址 + 下标 --> 移动到中间位置(count >> 1)
            probe = base + (count >> 1);
            // 获取该位置的sel名称
            uintptr_t probeValue = (uintptr_t)getName(probe);
            // 如果查找的key的keyvalue等于中间位置(probe)的probeValue,则直接返回中间位置
            if (keyValue == probeValue) {
                // `probe` is a match.
                // Rewind looking for the *first* occurrence of this value.
                // This is required for correct category overrides.
                // 分类方法同名- while 平移 -- 向前在查找,判断是否存在相同的方法,保证调用的是分类的
                while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) {
                    probe--;
                }
                return &*probe;
            }
            // 如果keyValue 大于 probeValue,就往probe即中间位置的右边查找,即中间位置再右移
            if (keyValue > probeValue) {
                base = probe + 1;
                count--;
            }
        }
        
        return nil;
    }
    
    1. 上面代码while (probe > first && keyValue == (uintptr_t)getName((probe - 1))) { probe--; }为什么要执行probe--
      原因是本类方法分类方法都整合在一块了,这里一定找的是最前面(最先加载)的分类方法saySomething

    class_ro_t的数据结构是怎么通过一个指针得到的?

    • 打开llvm查看class_ro_t源码
      struct class_ro_t {
        uint32_t m_flags;
        uint32_t m_instanceStart;
        uint32_t m_instanceSize;
        uint32_t m_reserved;
    
        lldb::addr_t m_ivarLayout_ptr;
        lldb::addr_t m_name_ptr;
        lldb::addr_t m_baseMethods_ptr;
        lldb::addr_t m_baseProtocols_ptr;
        lldb::addr_t m_ivars_ptr;
    
        lldb::addr_t m_weakIvarLayout_ptr;
        lldb::addr_t m_baseProperties_ptr;
    
        std::string m_name;
    
        bool Read(Process *process, lldb::addr_t addr);
      };
    
    // 读取当前address
    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;
      // 对class_ro_t中相应的成员变量进行赋值
      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, (char *)name_buf.GetBytes(),
                                     name_buf.GetByteSize(), error);
    
      if (error.Fail()) {
        return false;
      }
    
      m_name.assign((char *)name_buf.GetBytes());
    
      return true;
    }
    
    • llvm中寻找哪里调用了Read方法?最终查找到在Read_class_row方法中调用
    bool ClassDescriptorV2::Read_class_row(
        Process *process, const objc_class_t &objc_class,
        std::unique_ptr<class_ro_t> &class_ro,
        std::unique_ptr<class_rw_t> &class_rw) const {
      class_ro.reset();
      class_rw.reset();
    
      Status error;
      uint32_t class_row_t_flags = process->ReadUnsignedIntegerFromMemory(
          objc_class.m_data_ptr, sizeof(uint32_t), 0, error);
      if (!error.Success())
        return false;
    
      if (class_row_t_flags & RW_REALIZED) {
        class_rw = std::make_unique<class_rw_t>();
    
        if (!class_rw->Read(process, objc_class.m_data_ptr)) {
          class_rw.reset();
          return false;
        }
    
        class_ro = std::make_unique<class_ro_t>();
    
        if (!class_ro->Read(process, class_rw->m_ro_ptr)) {
          class_rw.reset();
          class_ro.reset();
          return false;
        }
      } else {
        class_ro = std::make_unique<class_ro_t>();
    
        if (!class_ro->Read(process, objc_class.m_data_ptr)) {
          class_ro.reset();
          return false;
        }
      }
      return true;
    }
    

    类扩展分析

    面试题:类扩展分类的区别
    1、category 类别、分类

    • 专门用来给类添加新的方法
    • 不能给类添加成员属性,添加了成员属性,也无法取到
    • 注意:其实可以通过runtime 给分类添加属性,即属性关联,重写setter、getter方法
    • 分类中用@property定义变量,只会生成变量的setter、getter方法的声明,不能生成方法实现带下划线的成员变量

    2、extension 类扩展

    • 可以说成是特殊的分类 ,也可称作匿名分类
    • 可以给类添加成员属性,但是是私有变量
    • 可以给类添加方法,也是私有方法

    类的扩展有两种创建方式

    • 直接在类中书写:永远在声明之后,在实现之前(需要在.m文件中书写)
    • 通过 command+N 新建 -> Objective-C File -> 选择Extension

    下面写一个类扩展

    @interface LGStudent : NSObject
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, assign) int age;
    - (void)instanceMethod;
    + (void)classMethod;
    @end
    
    <!-- 以下就是LGStudent的类扩展 -->
    @interface LGStudent ()
    @property (nonatomic, copy) NSString *ext_name;
    @property (nonatomic, assign) int ext_age;
    - (void)ext_instanceMethod;
    + (void)ext_classMethod;
    @end
    
    • 通过clang -rewrite-objc main.mm -o main.cpp命令生成cpp文件,打开cpp文件,搜索ext_name属性
    struct LGStudent_IMPL {
        struct NSObject_IMPL NSObject_IVARS;
        int _age;
        int _ext_age;
        NSString *_name;
        NSString *_ext_name;
    };
    
    • 查看成员变量列表_ivar_list_t,发现有两个成员变量_name_ext_name
    static struct /*_ivar_list_t*/ {
        unsigned int entsize;  // sizeof(struct _prop_t)
        unsigned int count;
        struct _ivar_t ivar_list[4];
    } _OBJC_$_INSTANCE_VARIABLES_LGStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_ivar_t),
        4,
        {{(unsigned long int *)&OBJC_IVAR_$_LGStudent$_age, "_age", "i", 2, 4},
         {(unsigned long int *)&OBJC_IVAR_$_LGStudent$_ext_age, "_ext_age", "i", 2, 4},
         {(unsigned long int *)&OBJC_IVAR_$_LGStudent$_name, "_name", "@\"NSString\"", 3, 8},
         {(unsigned long int *)&OBJC_IVAR_$_LGStudent$_ext_name, "_ext_name", "@\"NSString\"", 3, 8}}
    };
    
    • 查看对象方法列表
    static struct /*_method_list_t*/ {
        unsigned int entsize;  // sizeof(struct _objc_method)
        unsigned int method_count;
        struct _objc_method method_list[18];
    } _OBJC_$_INSTANCE_METHODS_LGStudent __attribute__ ((used, section ("__DATA,__objc_const"))) = {
        sizeof(_objc_method),
        18,
        {{(struct objc_selector *)"instanceMethod", "v16@0:8", (void *)_I_LGStudent_instanceMethod},
        {(struct objc_selector *)"ext_instanceMethod", "v16@0:8", (void *)_I_LGStudent_ext_instanceMethod},
        {(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGStudent_name},
        {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGStudent_setName_},
        {(struct objc_selector *)"age", "i16@0:8", (void *)_I_LGStudent_age},
        {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LGStudent_setAge_},
        {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LGStudent_ext_name},
        {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LGStudent_setExt_name_},
        {(struct objc_selector *)"ext_age", "i16@0:8", (void *)_I_LGStudent_ext_age},
        {(struct objc_selector *)"setExt_age:", "v20@0:8i16", (void *)_I_LGStudent_setExt_age_},
        {(struct objc_selector *)"name", "@16@0:8", (void *)_I_LGStudent_name},
        {(struct objc_selector *)"setName:", "v24@0:8@16", (void *)_I_LGStudent_setName_},
        {(struct objc_selector *)"age", "i16@0:8", (void *)_I_LGStudent_age},
        {(struct objc_selector *)"setAge:", "v20@0:8i16", (void *)_I_LGStudent_setAge_},
        {(struct objc_selector *)"ext_name", "@16@0:8", (void *)_I_LGStudent_ext_name},
        {(struct objc_selector *)"setExt_name:", "v24@0:8@16", (void *)_I_LGStudent_setExt_name_},
        {(struct objc_selector *)"ext_age", "i16@0:8", (void *)_I_LGStudent_ext_age},
        {(struct objc_selector *)"setExt_age:", "v20@0:8i16", (void *)_I_LGStudent_setExt_age_}}
    };
    

    查看 LGTeacher 类拓展的方法,在编译过程中,方法就直接添加到了methodlist中,作为类的一部分,即编译时期直接添加到本类里面

    通过源码调试探索

    • 创建LGPerson+LG.h即类的扩展,并声明两个方法
    @interface LGPerson ()
    - (void)ext_instanceMethod;
    + (void)ext_classMethod;
    @end
    
    • LGPeron.m中实现这两个方法
    - (void)ext_instanceMethod{
        NSLog(@"%s",__func__);
    }
    
    + (void)ext_classMethod{
        NSLog(@"%s",__func__);
    }
    
    • 运行objc源码程序,在realizeClassWithoutSwift中断住
    image.png
    (lldb) p ro.baseMethods()
    (method_list_t *) $0 = 0x00000001000046f0
      Fix-it applied, fixed expression was: 
        ro->baseMethods()
    (lldb) p *$0 
    (method_list_t) $1 = {
      entsize_list_tt<method_t, method_list_t, 4294901763, method_t::pointer_modifier> = (entsizeAndFlags = 24, count = 11)
    }
    (lldb) p $1.get(0).big()
    (method_t::big) $2 = {
      name = "saySomething"
      types = 0x0000000100003c65 "v16@0:8"
      imp = 0x0000000100003870 (KCObjcBuild`-[LGPerson saySomething])
    }
    (lldb) p $1.get(1).big()
    (method_t::big) $3 = {
      name = "sayHello1"
      types = 0x0000000100003c65 "v16@0:8"
      imp = 0x00000001000038a0 (KCObjcBuild`-[LGPerson sayHello1])
    }
    (lldb) p $1.get(2).big()
    (method_t::big) $4 = {
      name = "ext_instanceMethod"
      types = 0x0000000100003c65 "v16@0:8"
      imp = 0x00000001000038d0 (KCObjcBuild`-[LGPerson ext_instanceMethod])
    }
    

    得出结论:

    • 类的扩展在编译时会作为类的一部分,和类一起编译进来
    • 类的扩展只是声明,依赖于当前的主类,没有.m文件,可以理解为一个·h文件

    关联对象初探

    关联对象的底层原理的实现,主要分为三部分:

    • 通过objc_setAssociatedObject设值流程
    • 通过objc_getAssociatedObject取值流程
    • 通过objc_removeAssociatedObjects移除关联对象
    // 取值流程
    id
    objc_getAssociatedObject(id object, const void *key)
    {
        return _object_get_associative_reference(object, key);
    }
    
    // 设值流程
    void
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    {
        _object_set_associative_reference(object, key, value, policy);
    }
    
    // 移除关联对象
    void objc_removeAssociatedObjects(id object) 
    {
        if (object && object->hasAssociatedObjects()) {
            _object_remove_assocations(object, /*deallocating*/false);
        }
    }
    
    关联对象

    关联对象底层分析

    关联对象-设值流程
    • 分类LGA中重写属性cate_nameset、get方法,通过runtime的属性关联方法实现
    // 分类添加cate_name、cate_age属性
    @interface LGPerson (LGA)
    @property (nonatomic, copy) NSString *cate_name;
    @property (nonatomic, copy) NSString *cate_age;
    
    - (void)saySomething;
    - (void)cateA_instanceMethod1;
    - (void)cateA_instanceMethod2;
    + (void)cateA_classMethod1;
    + (void)cateA_classMethod2;
    @end
    
    // LGPerson+LGA.m文件
    - (void)setCate_name:(NSString *)cate_name{
        /**
         1: 对象 2: 标识符 3: value 4: 策略
         */
        objc_setAssociatedObject(self, "cate_name", cate_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)cate_name{
        return  objc_getAssociatedObject(self, "cate_name");
    }
    
    - (void)setCate_age:(NSString *)cate_age{
        objc_setAssociatedObject(self, "cate_age", cate_age, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)cate_age{
        return objc_getAssociatedObject(self, "cate_age");
    }
    
    • 运行程序,断点断在main中person.cate_name = @"KC";赋值处
    image.png
    • 进入_object_set_associative_reference源码实现,关于关联对象底层原理的探索主要是看value存到了哪里, 以及如何取出value ,以下是源码
    // 重磅提示  重点
    /**
     关联对象 : 存储 object - cate_name -> value - policy
     */
    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));
        //object封装成一个数组结构类型,类型为DisguisedPtr
        DisguisedPtr<objc_object> disguised{(objc_object *)object};//相当于包装了一下 对象object,便于使用
        // 包装一下 policy - value
        ObjcAssociation association{policy, value};
    
        // retain the new value (if any) outside the lock.
        association.acquireValue();
    
        bool isFirstAssociation = false;
        {
            // 初始化manager变量,相当于自动调用AssociationsManager的析构函数进行初始化
            AssociationsManager manager;//并不是全场唯一,构造函数中加锁只是为了避免重复创建,在这里是可以初始化多个AssociationsManager变量的
            AssociationsHashMap &associations(manager.get());//AssociationsHashMap 全场唯一
    
            if (value) {
                auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});//返回的结果是一个类对
                if (refs_result.second) {//判断第二个存不存在,即bool值是否为true
                    /* it's the first association we make  第一次建立关联*/
                    isFirstAssociation = true;//标记位true
                }
    
                /* establish or replace the association  建立或者替换关联*/
                auto &refs = refs_result.first->second;//得到一个空的桶子,找到引用对象类型,即第一个元素的second值
                auto result = refs.try_emplace(key, std::move(association));//查找当前的key是否有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);
    
                        }
                    }
                }
            }
        }
    
        if (isFirstAssociation)
            object->setHasAssociatedObjects();
    
        // release the old value (outside of the lock).
        association.releaseHeldValue();//释放
    }
    
    • _object_set_associative_reference源码中添加断点进行调试
    image.png
    // lldb调试信息
    (lldb) p disguised
    (DisguisedPtr<objc_object>) $0 = (value = 18446744069394321632)
    (lldb) p association
    (objc::ObjcAssociation) $1 = {
      _policy = 3
      _value = 0x0000000100004080 "KC"
    }
    
    • 注意AssociationsManager manager;是个析构函数,并不是单例。
    // main.m文件添加如下结构体,进行验证是析构函数
    struct LGObjc {
        LGObjc()   { printf("KC 来了 \n");}  //构造函数
        ~LGObjc()  {  printf("大师班 NB \n"); } //析构函数
    };
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            LGObjc kc;
        }
        return 0;
    }
    
    // 运行工程打印如下
    KC 来了 
    大师班 NB 
    
    • 查看AssociationsManager源码,定义AssociationsManager类型的变量,相当于自动调用AssociationsManager的析构函数进行初始化。加锁lock,并不代表 唯一,只是为了避免多线程重复创建,其实在外面是可以定义多个AssociationsManager manager;
    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类型的哈希map是全场唯一的,从哪里可以体现呢?
      通过_mapStorage.get()生成哈希map,其中_mapStorage是一个静态变量,所以哈希map永远是通过静态变量获取出来的,是全场唯一的。AssociationsHashMap哈希表是一个单例

    • 继续上面lldb调试

    image.png
    (lldb) p associations
    (objc::AssociationsHashMap) $0 = {
      Buckets = 0x0000000000000000
      NumEntries = 0
      NumTombstones = 0
      NumBuckets = 0
    }
    
    • associations调用try_emplace方法,传入一个对象disguised和 一个空的关联mapObjectAssociationMap{}
    (lldb) p refs_result
    (std::pair<objc::DenseMapIterator<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >, objc::DenseMapValueInfo<objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, objc::DenseMapInfo<DisguisedPtr<objc_object> >, objc::detail::DenseMapPair<DisguisedPtr<objc_object>, objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> > >, false>, bool>) $1 = {
      first = {
        Ptr = 0x0000000100828a20
        End = 0x0000000100828aa0
      }
      second = true
    }
    
    • 查看try_emplace方法的源码实现
      // it is not moved. 如果key不在map中,则直接构造,否则它不会移动
      template <typename... Ts>
      std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
        BucketT *TheBucket;
        if (LookupBucketFor(Key, TheBucket))//LookupBucketFor找桶子,其中key是关联对象
          return std::make_pair(//如果桶子存在则返回,置为false的原因是哈希map中已经存在
                   makeIterator(TheBucket, getBucketsEnd(), true),
                   false); // Already in map. 已经存在直接返回false
    
        // Otherwise, insert the new element. 第一次来则插入 -- true
        TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...); //往桶里添加值
        return std::make_pair(
                 makeIterator(TheBucket, getBucketsEnd(), true),
                 true);//bool值置为true,表示第一次往哈希map中添加桶子
      }
    
    • 查看LookupBucketFor源码,有两个同名方法,其中第二个方法属于重载函数,区别于第一个的是第二个参数没有const修饰,通过调试可知,外部的调用是调用的第二个重载函数,而第二个LookupBucketFor方法内部的实现是调用第一个LookupBucketFor方法
    image.png
    • 断点运行至try_emplace方法中的获取bucket部分TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    image.png
    (lldb) p TheBucket
    (objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $2 = 0x0000000000000000
    
    • 进入查看InsertIntoBucket源码
    template <typename LookupKeyT>
      BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                    BucketT *TheBucket) {
        // If the load of the hash table is more than 3/4, or if fewer than 1/8 of
        // the buckets are empty (meaning that many are filled with tombstones),
        // grow the table.
        //
        // The later case is tricky.  For example, if we had one empty bucket with
        // tons of tombstones, failing lookups (e.g. for insertion) would have to
        // probe almost the entire table until it found the empty bucket.  If the
        // table completely filled with tombstones, no lookup would ever succeed,
        // causing infinite loops in lookup.
        unsigned NewNumEntries = getNumEntries() + 1;
        unsigned NumBuckets = getNumBuckets();
        // 3/4扩容
        if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
          this->grow(NumBuckets * 2);
          LookupBucketFor(Lookup, TheBucket);
          NumBuckets = getNumBuckets();
        } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                                 NumBuckets/8)) {
          this->grow(NumBuckets);
          LookupBucketFor(Lookup, TheBucket);
        }
        ASSERT(TheBucket);
    ......//省略代码
    
    • 调试断点回到_object_set_associative_reference方法
    image.png
    (lldb) p refs
    (objc::DenseMap<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> >) $3 = {
      Buckets = 0x0000000100706680
      NumEntries = 1
      NumTombstones = 0
      NumBuckets = 4
    }
    (lldb) p result
    (std::pair<objc::DenseMapIterator<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>, false>, bool>) $4 = {
      first = {
        Ptr = 0x00000001007066b0
        End = 0x00000001007066e0
      }
      second = true
    }
    (lldb) p $4.first 
    (objc::DenseMapIterator<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>, false>) $5 = {
      Ptr = 0x00000001007066b0
      End = 0x00000001007066e0
    }
    // 这就是存进去的bucket
    (lldb) p $5.Ptr 
    (objc::DenseMapIterator<const void *, objc::ObjcAssociation, objc::DenseMapValueInfo<objc::ObjcAssociation>, objc::DenseMapInfo<const void *>, objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>, false>::pointer) $6 = 0x00000001007066b0
    

    最终探索到bucket是通过InsertIntoBucket方法的这一段代码TheBucket->getFirst() = std::forward<KeyArg>(Key); ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);插进去的。

    第一次执行try_emplace插入的是一个空桶还没有值,第二次执行try_emplace才插入值,即往空桶中插入ObjectAssociationMap(value,policy),返回true
    所以关联对象的设值图示如下,有点类似于cache_t中的insert方法插入sel-imp的逻辑

    关联对象设值
    关联对象: 设值流程
    • 创建一个AssociationsManager管理类
    • 获取唯一的全局静态哈希Map
    • 判断是否插入的关联值是否存在:
      1: 存在走第4步
      2: 不存在就走 : 关联对象插入空流程
    • 创建一个空的ObjectAssociationMap去取查询的键值对
    • 如果发现没有这个 key 就插入一个 空的BucketT进去返回
    • 标记对象存在关联对象
    • 用当前 修饰策略 和 值 组成了一个ObjcAssociation 替换原来 BucketT 中的空
    • 标记一下 ObjectAssociationMap 的第一次为 false
    关联对象插入空流程
    • 根据DisguisedPtr找到 AssociationsHashMap 中的 iterator 迭代查询器
    • 清理迭代器
    • 其实如果插入空值 相当于清除
    关联对象的取值流程自己探索,这里简单讲述下取值流程
    关联对象: 取值流程
    • 创建一个 AssociationsManager 管理类
    • 获取唯一的全局静态哈希Map
    • 根据 DisguisedPtr 找到 AssociationsHashMap中的 iterator 迭代查询器
    • 如果这个迭代查询器不是最后一个 获取 : ObjectAssociationMap (这里有策略和value)
    • 找到ObjectAssociationMap的迭代查询器获取一个经过属性修饰符修饰的value
    • 返回_value
      总结: 其实就是两层哈希map , 存取的时候两层处理(类似二位数组)

    相关文章

      网友评论

        本文标题:类加载原理补充-关联对象底层原理

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