17.iOS底层学习之关联对象

2021-10-20  本文已影响0人  牛牛大王奥利给

本篇文章主要研究关联对象,承接上一篇分类的加载,补充一部分关于分类的问题,主要分为以下几个部分:

分类加载是否需要排序

关联分类的关键方法是attachCategories,而该方法上面的注释有说明:

// Attach method lists and properties and protocols from categories to a class.
// Assumes the categories in cats are all loaded and sorted by load order,
// oldest categories first.
static void
attachCategories(Class cls, const locstamped_category_t *cats_list, uint32_t cats_count,
int flags)
翻译一下就是:从分类关联方法列表,协议,属性到一个类上,假如所有的类都被如期加载进来了那么分类的顺序就是加载的顺序,最旧的分类是第一个被加载的。

我们再来调试一下方法attachCategories:

image.png
先来到这个方法的是分类B。
分类A执行attachCategories
(lldb) p $25.cat
(category_t *const) $26 = 0x0000000100008090
(lldb) p *$26
(category_t) $27 = {
  name = 0x0000000100003e5a "B"
  cls = 0x00000001000084c8
  instanceMethods = {
    ptr = 0x0000000100008038
  }
  classMethods = {
    ptr = 0x0000000100008070
  }
  protocols = nil
  instanceProperties = nil
  _classProperties = nil
}

其实就是分类的结构体内容,通过打印的内容可以看到现在进来的是分类B,接着打印里面的instanceMethods的ptr,这个ptr的类型是method_list_t,进一步打印得到了对应的两个实例方法:AAAABBBB
打印classMethods中的ptr,拿到了一个+load方法:

image.png
以上的内容是把分类B关联到主类的过程!然后我们接着调试来看下A分类来到方法attachCategories的过程。
分类A执行attachCategories

现在的情况是A,B还有主类都是实现了load方法,所以是通过:attachCategories(cls, &lc, 1, ATTACH_EXISTING); 来到的attachCategories方法,那么此时cats_count固定是1。

image.png
这个调试的过程和B是一样的。
不一样的地方是此时B分类中有的方法AAAA已经存在了,我们来看一下A分类插入AAAA方法时的过程。我继续调试,方法通过attachCategories->prepareMethodLists->fixupMethodList->sel_registerName->_ _sel_registerName最终来到方法search_builtins(name)
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 (SEL result = (SEL)_dyld_get_objc_selector(name))
    return result;
#endif
    return nil;
}

这里方法search_builtins里有一个预编译,我这边跑的Mac 所以直接返回了nil,接下来走的是方法auto it = namedSelectors.get().insert(name),而这个insert方法如下

insert().png

这里边的关键方法是try_emplace

image.png
所以这个方法会去通过LookupBucketFor方法传进去的key去查找相应的bucket如果找到,那么返回对应的iterator,并且pair的第二个元素设置为false,返回pair。
根据源码的注释提示:这个要插入的方法已经存在map中了。
否则的话会继续往下走执行InsertIntoBucket,根据注释提示:插入新的元素,插入新的key到对应的map中并且返回pair,此时的pair的第二个元素是true。
pair的定义
这个pair是一个struct,并且带有模板template <class _T1, class _T2>
pair.png

所以此时我们再回到方法__sel_registerName,再来看:

auto it = namedSelectors.get().insert(name);
    if (it.second) {
        // No match. Insert.
        *it.first = (const char *)sel_alloc(name, copy);
    }
return (SEL)*it.first;

这一段就很清晰了,it的second就是上面pair过后的结果,为false的时候就是原来就有了,直接返回frist就行了因为前面通过pair处理过了,frist里面装的就是对应的value。
为ture的时候,结合注释,“未匹配到,插入”。


A分类有重名方法AAAA的时候.png

综上,这一段流程下来是没有对分类单独进行排序的,目前这种情况的cats_count都是1,是固定的,因为都是从方法 attachCategories(cls, &lc, 1, ATTACH_EXISTING);或者attachCategories(cls->ISA(), &lc, 1, ATTACH_EXISTING | ATTACH_METACLASS);这个进来的,这个cats_count是固定传的1。

然后我们再来看看从attachToClass-> attachCategories(cls, list.array(), list.count(), flags);这个地方进入的方法attachCategories,这个后面的流程是一样的,都是对对应方法的查询和插入,而进入到attachCategories之后的流程,也是去遍历cats_list每个分类里面的methodList,然后安卓流程attachList,就没有单独对分类排序的部分。

而分类的顺序之前源码的注释中也提到过,和加载顺序有关。

编译器分类的顺序.png
我的顺序是BCA,所以A是最后被load,注释说明,最旧的会被第一个加载。

这里我的理解是,分类最终要把方法合并到主类的相关关联ro或者是rwe中的方法列表里,每次操作都是对分类里的实质内容进行操作,比如分类里扩展了属性,方法,协议等等,核心操作是把这些相关的联系到主类里。所以分类不会再进行排序。(不管是Person的分类A,还是它的B,或者C,通过load最后关联的结构都是写入到Person类的rwe对应下面的methodlist,方法查找的时候也是从类的methodList找,也没有查找单独分类的方法列表,分类的设计是为了类服务的,为了更好的程序设计,提供了更好的动态性)(要是理解错了希望各位大佬帮忙纠正😊)。

methodList的数据结构

前面分析流程的时候,简单的说了下大概的类型和结构,这里总结下。

locstamped_category_t
struct locstamped_category_t {
    category_t *cat;
    struct header_info *hi;
};
category_t
struct category_t {
    const char *name;
    classref_t cls;
    WrappedPtr<method_list_t, PtrauthStrip> instanceMethods;
    WrappedPtr<method_list_t, PtrauthStrip> classMethods;
    struct protocol_list_t *protocols;
    struct property_list_t *instanceProperties;
    // Fields below this point are not always present on disk.
    struct property_list_t *_classProperties;

    method_list_t *methodsForMeta(bool isMeta) {
        if (isMeta) return classMethods;
        else return instanceMethods;
    }

    property_list_t *propertiesForMeta(bool isMeta, struct header_info *hi);
    
    protocol_list_t *protocolsForMeta(bool isMeta) {
        if (isMeta) return nullptr;
        else return protocols;
    }
};
method_list_t
struct method_list_t : entsize_list_tt<method_t, method_list_t, 0xffff0003, method_t::pointer_modifier> {
    bool isUniqued() const;
    bool isFixedUp() const;
    void setFixedUp();

    uint32_t indexOfMethod(const method_t *meth) const {
        uint32_t I = 
            (uint32_t)(((uintptr_t)meth - (uintptr_t)this) / entsize());
        ASSERT(i < count);
        return I;
    }

    bool isSmallList() const {
        return flags() & method_t::smallMethodListFlag;
    }

    bool isExpectedSize() const {
        if (isSmallList())
            return entsize() == method_t::smallSize;
        else
            return entsize() == method_t::bigSize;
    }

    method_list_t *duplicate() const {
        method_list_t *dup;
        if (isSmallList()) {
            dup = (method_list_t *)calloc(byteSize(method_t::bigSize, count), 1);
            dup->entsizeAndFlags = method_t::bigSize;
        } else {
            dup = (method_list_t *)calloc(this->byteSize(), 1);
            dup->entsizeAndFlags = this->entsizeAndFlags;
        }
        dup->count = this->count;
        std::copy(begin(), end(), dup->begin());
        return dup;
    }
};

所以通过cats_list去遍历locstamped_category_t,把locstamped_category_t中的category_t拿到,进而拿到category_t中的method_list_t。

分类和扩展

category(类别、分类)
extension(扩展)

通过xcrun查看extension的底层实现可以看到extension的内容与类的内容合并到了一起。通过调试去打印ro相关的方法,发现extension中的内容,早就被加到了ro中。在编译的时候就完成了。不会像分类那样在启动的时候再去添加到类的methodList了,所以extension不会影响类的加载。

关联对象

首先,我给分类A增加了一个叫aName的属性,然后在main函数中进行实例化主类LGPerson,然后通过实例对象L进行aName的赋值或者进行get的读取,嗯都不行,就报错了报错了报错了!

set.png get.png

说明通过分类添加的属性,没有set还有get方法!然后在分类A的实现中发现了这样的警告⚠️

image.png

Property 'aName' requires method 'setAName:' to be defined - use @dynamic or provide a method implementation in this category
aName需要一个方法'setAName:'定义,通过动态性或者在这个分类里提供一个方法实现。于是我就尝试像往常一样实现set方法,然后又报错了。

image.png
分类创建的属性没有带下划线的属性名
然后去查了下资料,这种可以用关联属性实现。
-(void)setAName:(NSString *)aName{
     objc_setAssociatedObject(self, "aName", aName, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

-(NSString *)aName{
    return objc_getAssociatedObject(self, "aName");
}

实现完毕这两个方法就可以正常给分类的属性赋值或者读取了。我们来看下关联属性的底层实现。

objc_setAssociatedObject

我们跟着这个方法点进去看一下,看到objc_setAssociatedObject调用的是_object_set_associative_reference。
_object_set_associative_reference源码解读

//  objc_setAssociatedObject(self, "aName", aName, OBJC_ASSOCIATION_COPY_NONATOMIC);
//object->self key->"aName" value->aName policy->OBJC_ASSOCIATION_COPY_NONATOMIC

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类的对象 disguised
    DisguisedPtr<objc_object> disguised{(objc_object *)object};
    //生成ObjcAssociation类的对象association
    ObjcAssociation association{policy, value};

    // retain the new value (if any) outside the lock.
    //根据_policy的值来确定value的处理方式
    /**acquireValue的实现
        switch (_policy & 0xFF) {
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value);
                break;
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                break;
     */
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        
        //类AssociationsManager管理锁/哈希表单例对。
        //分配实例将获得锁
        //这里在AssociationsManager创建对象的时候加了锁,在AssociationsManager的对象销毁的时候解锁
        AssociationsManager manager;
        //获取关联对象表
        AssociationsHashMap &associations(manager.get());
        if (value) {
            //然后来到try_emplace方法,这方法前面讲到了,前一个元素是从bucket中查到的结果,后面是查没查到 第二个值second为true的时候是新插入的
            auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
            if (refs_result.second) {
                /* it's the first association we make */
                //此时已经为这个key创建了对应bucket但是还没有存值,通过后面打印可以看到是空的
                isFirstAssociation = true;
            }

            /* establish or replace the association */
            //开始创建或者替换association 再拿refs_result的first的second
            auto &refs = refs_result.first->second;
            //往对应的bucket中存值
            auto result = refs.try_emplace(key, std::move(association));
            //因为前面已经执行过了try_emplace,如果这一次执行,返回的result.second为false说明这个已经存在了
            //所以要把存在的这个刚查到的result.first的值和内层的交换一下??(这个地方没有太看懂)
            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();
}
objc_getAssociatedObject

基于上面关于set的分析,_object_get_associative_reference就好看多了👌🏻!
_object_get_associative_reference源码解读:

id
_object_get_associative_reference(id object, const void *key)
{
    ObjcAssociation association{};
    {
        AssociationsManager manager;
        AssociationsHashMap &associations(manager.get());
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        if (i != associations.end()) {
            ObjectAssociationMap &refs = i->second;
            ObjectAssociationMap::iterator j = refs.find(key);
            if (j != refs.end()) {
                association = j->second;
                association.retainReturnedValue();
            }
        }
    }
    return association.autoreleaseReturnedValue();
}

通过get方法,可以了解到这是个HashMap的嵌套,第一层是AssociationsHashMap,第二层是ObjectAssociationMap。
最后的结构大致是这样的:


AssociationsHashMap结构.png

今天去打羽毛球,然后回来看到大爷发的羽毛球动态:
贵有恒,何必三更起五更眠;
最无益,只怕一日曝十日寒。
大爷永远是大爷🐂🍺😄

上一篇下一篇

猜你喜欢

热点阅读