美文网首页iOS学习
iOS-底层原理16-类扩展和关联对象底层原理

iOS-底层原理16-类扩展和关联对象底层原理

作者: 一亩三分甜 | 来源:发表于2020-12-25 02:28 被阅读0次

    《iOS底层原理文章汇总》

    上一篇文章《iOS-底层原理15-类的加载下》详细介绍了类和分类的懒加载和非懒加载搭配情况下,方法的加载流程,本文介绍类的扩展和关联对象底层原理

    LLVM源码下载地址

    1.方法排序中类中的方法的name的内存地址排序,内存地址从哪儿得来,有什么规则?

    我们都知道方法经过prepareMethodLists --> fixupMethodList 排序后变得有序,排序是通过name的内存地址进行排序的,name地址从哪儿取值呢?

    name赋值@2x.png
    static void 
    fixupMethodList(method_list_t *mlist, bool bundleCopy, bool sort)
    {
        runtimeLock.assertLocked();
        ASSERT(!mlist->isFixedUp());
    
        // fixme lock less in attachMethodLists ?
        // dyld3 may have already uniqued, but not sorted, the list
        if (!mlist->isUniqued()) {
            mutex_locker_t lock(selLock);
        
            // Unique selectors in list.
            for (auto& meth : *mlist) {
                const char *name = sel_cname(meth.name);
                meth.name = sel_registerNameNoLock(name, bundleCopy);
            }
        }
        // sel - imp
        // Sort by selector address.
        if (sort) {
            method_t::SortBySELAddress sorter;
            std::stable_sort(mlist->begin(), mlist->end(), sorter);
        }
        
        // Mark method list as uniqued and sorted
        mlist->setFixedUp();
    }
    
    SEL sel_registerNameNoLock(const char *name, bool copy) {
        return __sel_registerName(name, 0, copy);  // NO lock, maybe copy
    }
    
    static SEL __sel_registerName(const char *name, bool shouldLock, bool copy) 
    {
        SEL result = 0;
    
        if (shouldLock) selLock.assertUnlocked();
        else selLock.assertLocked();
    
        if (!name) return (SEL)0;
    
        result = search_builtins(name);
        if (result) return result;
        
        conditional_mutex_locker_t lock(selLock, shouldLock);
        auto it = namedSelectors.get().insert(name);
        if (it.second) {
            // No match. Insert.
            *it.first = (const char *)sel_alloc(name, copy);
        }
        return (SEL)*it.first;
    }
    
    static SEL search_builtins(const char *name) 
    {
    #if SUPPORT_PREOPT
      if (builtins) {
          SEL result = 0;
          if ((result = (SEL)builtins->get(name)))
              return result;
    
          if ((result = (SEL)_dyld_get_objc_selector(name)))
              return result;
      } else if (useDyldSelectorLookup) {
          if (SEL result = (SEL)_dyld_get_objc_selector(name))
              return result;
      }
    #endif
        return nil;
    }
    

    method.name从result = search_builtins(name)中取值,条件为useDyldSelectorLookup,查看条件的初始化发现map_images_nolock-->sel_init(selrefCount)-->useDyldSelectorLookup = true,程序进入(SEL)_dyld_get_objc_selector(name),走入dyld源码_dyld_get_objc_selector-->gAllImages.getObjCSelector(selName) --> _objcSelectorHashTable->getString(selName, _objcSelectorHashTableImages.array()),name的地址来源于段的基本地址+相应偏移量,name的内存地址是随机变化的,根据编译器调试处理的,新增库和方法会导致selectionBaseAddress地址变化和偏移量变化

    useDyldSelectorLookup@2x.png sel_init@2x.png _dyld_get_objc_selector@2x.png 自定义类走dyld3-_dyld_get_objc_selector@2x.png name地址动态变化@2x.png
    const char* _dyld_get_objc_selector(const char* selName)
    {
        // Check the shared cache table if it exists.
        if ( gObjCOpt != nullptr ) {
            if ( const objc_opt::objc_selopt_t* selopt = gObjCOpt->selopt() ) {
                const char* name = selopt->get(selName);
                if (name != nullptr)
                    return name;
            }
        }
    
        if ( gUseDyld3 )
            return dyld3::_dyld_get_objc_selector(selName);
    
        return nullptr;
    }
    
    const char* _dyld_get_objc_selector(const char* selName)
    {
        log_apis("dyld_get_objc_selector()\n");
        return gAllImages.getObjCSelector(selName);
    }
    

    2.MachO文件格式,读到相应的data(),read data()中的地址怎么直接变成class_ro_t格式的?那么什么时候编译成class_ro_t的格式编译到MachO中的呢?在MachO中是地址指针的形式存在

        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);
        }
    
    • 1.查看class_ro_t的数据结构
    struct class_ro_t {
        uint32_t flags;
        uint32_t instanceStart;
        uint32_t instanceSize;
    #ifdef __LP64__
        uint32_t reserved;
    #endif
    
        const uint8_t * ivarLayout;
        
        const char * name;
        method_list_t * baseMethodList;
        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;
            }
        }
    
        method_list_t *baseMethods() const {
            return baseMethodList;
        }
    
        class_ro_t *duplicate() const {
            if (flags & RO_HAS_SWIFT_INITIALIZER) {
                size_t size = sizeof(*this) + sizeof(_swiftMetadataInitializer_NEVER_USE[0]);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                ro->_swiftMetadataInitializer_NEVER_USE[0] = this->_swiftMetadataInitializer_NEVER_USE[0];
                return ro;
            } else {
                size_t size = sizeof(*this);
                class_ro_t *ro = (class_ro_t *)memdup(this, size);
                return ro;
            }
        }
    };
    

    MachO文件在编译期完成,肯定有一个方法在编译期能读取data()中内存地址为class_ro_t,查看llvm源码

    在llvm中查看 struct class_ro_t源码,llvm通过Read方法读取data()中的内存地址,给class_ro_t中的元素依次按顺序赋值,那么class_ro_t::Read方法在哪里调用的呢?

    llvm-class_ro_t@2x.png
    读取内存地址赋值class_ro_t@2x.png

    Read_class_row中会连续调用class_rw->Readclass_ro->Read进而给class_rw_tclass_ro_t赋值,那什么时候调用Read_class_row的呢?Read_class_rowRead_objc_class相生相随,Read_class_row属于模板类,调用Describe方法之前都会调用ClassDescriptorV2进行初始化,初始化后调用Describe,GetClassName,GetInstanceSize都会调用Read_class_row,从而触发class_ro_tclass_rw_t赋值

    Read_class_row@2x.png

    3.类的扩展在编译期就会作为类的一部分编译进去,和分类的加载过程不一样,分类是为了动态的开辟,类扩展作为类的一部分跟类一起伴随着永生下去

    A.category:类别,分类

    • I 专门用来给类添加新的方法
    • II 不能给类添加成员属性,添加了成员变量,也无法取到
    • III 可以通过runtime给分类添加属性
    • IV 分类中用@property定义变量,只会生成变量的setter、getter方法的声明,不能生成方法实现和带下划线的成员变量

    B.extension:类扩展

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

    给LGPerson+LGA中增加属性cate_name,在main函数中调用person.cate_name = @"KC"变色但运行没有实现会崩溃

    声明cate_name运行奔溃@2x.png ClassDescriptorV2@2x.png

    类扩展,类扩展必须放到类的声明之后,实现之前,即@interface和@implementation中间,否则编译报错,那么类扩展的本质是什么呢?我们通过clang查看类扩展的本质

    类扩展必须在@interface和@implementation中间@2x.png

    类扩展中声明的属性会自动生成带下划线的成员变量和setter、getter方法,类扩展中方法和类中方法一模一样,没有实现load方法会在第一次消息发送的时候将方法加载到类中,

    realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 LGTeacher 
    realizeClassMaybeSwiftMaybeRelock: 这个是我要研究的 LGTeacher 
    realizeClassWithoutSwift: 这个是我要研究的 LGTeacher 
    methodizeClass: 这个是我要研究的 LGTeacher 
    prepareMethodLists: 这个是我要研究的 LGTeacher 
    attachToClass: 这个是我要研究的 LGTeacher 
    

    实现了load方法,类的扩展在编译期就会作为类的一部分编译进MachO文件中,方法在编译时期就进入ro文件中了,直接从MachO文件的data()中读取出来,过程和上一篇文章《iOS-底层原理15-类的加载下》分析的一致,分类是为了动态的开辟,类的扩展不需要动态开辟,作为类的一部分跟类一起加载

    实现load方法加载到类中@2x.png _ext_name@2x.png _setExt_name@2x.png 类扩展中方法和类中方法一模一样@2x.png

    4.类的扩展可以填加load方法变为非懒加载类吗?不能,没有.m文件和@implementation实现类

    关联对象底层原理:分类无法添加属性,要添加属性的话,需要重写setter、getter方法添加关联对象,关联对象传入4个参数,对象,标识符,value值,关联策略

    关联对象@2x.png _base_objc_setAssociatedObject@2x.png

    objc_setAssociatedObject --> SetAssocHook.get()(object, key, value, policy) --> _base_objc_setAssociatedObject --> _object_set_associative_reference(object, key, value, policy)

    SetAssocHook.get()为一层接口模式,整个对外暴露的objc_setAssociatedObject永远不变,中间的这一层是有处理的,可以增加接口拦截之类做一些其他处理,类似于reallySetProperty(self, _cmd, newValue, offset, atomic, copy, mutableCopy)

    调用SetAssocHook.get()(object, key, value, policy)相当于_base_objc_setAssociatedObject(object, key, value, policy)从而进入_object_set_associative_reference(object, key, value, policy)方法

    _object_set_associative_referenece@2x.png

    acquireValue对value的值进行处理,传入的策略是retain或copy做相应的操作,其他策略不做处理

    acquireValue@2x.png

    程序继续往下之心,构造函数和析构函数

    构造函数和析构函数@2x.png
        AssociationsManager()   { AssociationsManagerLock.lock(); }
        ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    

    类似于在main.m中写

    struct LGObjc {
        LGObjc()   { printf("来了");}
        ~LGObjc()  {  printf("走了"); }
    };
    
    LGObjc构造函数和析构函数@2x.png

    关联对象存在一张大的哈希表,AssociationsHashMap里面存储LGPerson和LGTeacher等的,此哈希表唯一,方便查找,AssociationsHashMap是静态变量获取出来的,全场唯一,但是AssociationsManager不唯一,加锁代表防止多线程重复创建,并不是代表不能创建

    AssociationsHashMap唯一@2x.png 赋值前初始化@2x.png

    查看refs_result的格式存在五个键值对

    获取键值对@2x.png

    根据对象去AssociationsHashMap总表中查找关联对象LGPerson桶子,若找到,直接返回LGPerson桶子和bool值为false,代表桶子已经存在,不是第一次进入,若没找到,插入一个新的空的LGPerson桶子和bool值为true,如果第一次执行object->setHasAssociatedObjects(),标记为nonpointerisa

    setHasAssociatedObjects中非nonpointerisa@2x.png

    若value传值为nil,则从AssociationsHashMap中移除桶子,关联对象也消除

    value为nil@2x.png AssociationsHashMap@2x.png

    查看第一次时,TheBucket的值,和refs_result的键值对中最后一个键值对DenseMapPair完全一样, refs_result存在五个属性,TheBucket桶子藏在detail中

    TheBucket@2x.png

    首先去安放关联对象,查找关联对象作为key对应的桶子是否已经存在,若存在返回找到的关联对象LGPerson桶子,若不存在,返回一个空的关联对象LGPerson桶子并给默认值,空桶子赋值前后对比


    查找关联对象对应的桶子@2x.png 空桶子@2x.png 桶子关联对象@2x.png

    关联对象桶子和属性桶子赋值前后对比,属性桶子objc::detail::DenseMapPair<const void *, objc::ObjcAssociation>作为关联对象桶子(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> > > *)的最后一个属性存在于关联对象桶子中,知道属性桶子的结构为DenseMapPair后,通过方法获取存入的值

    关联对象桶子赋值前后对比@2x.png 属性桶子赋值前后对比@2x.png 属性桶子通过方法获取存入的值@2x.png

    取返回值的refs_result.first->second,此时安放key也就是"cate_name"作为key,association{policy, value}(3,"KC")作为Value到属性cate_name桶子中,若"cate_name"对应的桶子已经存在,则直接返回属性桶子,若不存在,再次返回一个以"cate_name"为key的空属性桶子

    second的值@2x.png 返回cate_name为key的空桶子@2x.png

    此时将key("cate_name"),policy(3),value("KC")插入到返回的以cate_name为key的属性桶子中,对象的属性就和对象产生了关联,返回std::make_pair( makeIterator(TheBucket, getBucketsEnd(), true), true),则result.second为true

    key、policy、value插入到对应桶子中@2x.png 属性关联对象结果result@2x.png

    关联对象

    关联对象AssociationsHashMap->Buckets->DenseMapPair@2x.png

    关联对象设值流程

    关联对象设值流程@2x.png

    关联对象取值流程

    关联对象取值流程@2x.png

    相关文章

      网友评论

        本文标题:iOS-底层原理16-类扩展和关联对象底层原理

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