美文网首页
iOS底层之关联对象

iOS底层之关联对象

作者: 大橘猪猪侠 | 来源:发表于2020-10-21 17:54 被阅读0次

    首先我们来简单的描述一下分类的一些基本概念:
    1、用来给类添加新方法
    2、不能给类添加成员属性,添加了成员变量,也无法取到
    3、注意:其实可以通过runtime给分类添加属性
    4、分类中用@property定义变量,只会生成变量的getter,setter方法声明,不能生成方法实现和带下划线的成员变量。

    那么问题来了, 分类是不能直接添加属性的,那么要给分类添加属性,那就需添加关联对象或者重写,在.m文件添加setter方法的实现。

    请看下面代码,在main.m中添加了对Person类的分类,在分类中有两个属性,且在main函数中创建Person对象并对分类的属性赋值,其中,分类的属性利用runtime来实现了gettersetter方法。

    #import <Foundation/Foundation.h>
    #import "Person.h"
    #import <objc/runtime.h>
    #import <malloc/malloc.h>
    #import "Person+LGEXT.h"
    
    @interface Person (LG)
    
    @property (nonatomic, copy) NSString *cate_name;
    @property (nonatomic, assign) int cate_age;
    
    - (void)cate_instanceMethod1;
    - (void)cate_instanceMethod3;
    - (void)cate_instanceMethod2;
    + (void)cate_sayClassMethod;
    @end
    
    @implementation Person (LG)
    - (void)cate_instanceMethod1{
        NSLog(@"%s",__func__);
    }
    - (void)cate_instanceMethod3{
        NSLog(@"%s",__func__);
    }
    - (void)cate_instanceMethod2{
        NSLog(@"%s",__func__);
    }
    + (void)cate_sayClassMethod{
        NSLog(@"%s",__func__);
    }
    
    - (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");
    }
    @end
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            Person *person = [Person alloc];
            person.cate_name = @"OC";
            NSLog(@"%p",person);
        }
        return 0;
    }
    

    我们首先在setter方法出打上断点,来看看它是否会执行这个方法:

    iShot2020-10-21 16.00.06.png

    可以看到,会执行这个方法,而当我们点击方法进去之后,你会发现,他们的整个方法实现都在一块,首先SetAssocHook,然后_base_objc_setAssociatedObject,之后就会执行_object_set_associative_reference方法。

    iShot2020-10-21 16.02.12.png

    _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};
        // 包装一下 policy - value
        ObjcAssociation association{policy, value};
        // retain the new value (if any) outside the lock.
        association.acquireValue();
        {
            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 */
                    object->setHasAssociatedObjects();
                }
                /* 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);
                        }
                    }
                }
            }
        }
        // release the old value (outside of the lock).
        association.releaseHeldValue();
    }
    
    

    上面的代码很多,将部分代码收起来,可以看到一共就这么几行代码:


    iShot2020-10-21 16.06.38.png

    从下图可以看到,167行代码中的DisguisedPtr是对对象进行包装,

    iShot2020-10-21 16.17.14.png

    ObjcAssociation的作用同样是对policyvalue进行包装,其中value的值是上面代码对cate_name赋的值OC

    iShot2020-10-21 16.18.48.png

    看下图,acquireValue()的作用其实是对_valuepolicy的判断;

    iShot2020-10-21 16.21.47.png

    接下来就是172-201行的局部作用空间代码,如下图:


    iShot2020-10-21 16.23.30.png iShot2020-10-21 16.24.16.png

    首先看到AssociationsManager,看上图,它的重要的地方其实是方框中的c++代码。至于它的作用,下面我用一个例子来表示:
    main.m改成main.mm,在main.m中写一个结构体:

    struct Objc {
        Objc()   { printf("来了");}
        ~Objc()  {  printf("走了"); }
    };
    

    然后在函数中调用:

    iShot2020-10-21 16.27.50.png

    看最终执行完毕的结果:


    iShot2020-10-21 16.28.09.png

    这个函数的作用其实就是在程序开始时执行一次,在函数结束完毕后,再执行一次。而程序中是对代码进行加锁和解锁;它可以有多个这样的AssociationsManager

    AssociationsHashMap从名字看,像一个哈希表,里面有很多关联对象,里买呢的类型类似于key-value健值对的形式存在;继续执行内部代码块:
    当执行到if (refs_result.second) {行时,我们在控制台来看一下一些属性的值:

    (lldb) p disguised
    (DisguisedPtr<objc_object>) $0 = (value = 18446744069408092480)
    (lldb) p association
    (objc::ObjcAssociation) $1 = {
      _policy = 3
      _value = 0x0000000100004038 "OC"
    }
    (lldb) p manager
    (objc::AssociationsManager) $2 = {}
    (lldb) p associations
    (objc::AssociationsHashMap) $3 = {
      Buckets = 0x0000000000000000
      NumEntries = 0
      NumTombstones = 0
      NumBuckets = 0
    }
    (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>) $4 = {
      first = {
        Ptr = 0x0000000101006810
        End = 0x0000000101006850
      }
      second = true
    }
    (lldb) 
    

    看到refs_result的时候,可以看到$4的类型很长,我们不用去管它,去看看给它赋值的函数try_emplace
    看下图,它其实是分两部分,一部分是false,一部分是true,从判断中可以看出来,当TheBucket为空时,就会执行InsertIntoBucket,并返回true,当TheBucket有值之后,就会返回false

    iShot2020-10-21 16.42.19.png

    下图是当value有值的时候,程序执行的代码块:

    iShot2020-10-21 17.17.46.png

    根据代码执行的流程分析的结果,程序第一次进入try_emplace走的是InsertIntoBucket,而在第二次执行try_emplace走的同样是InsertIntoBucket方法:
    也就是说,第一次是从哈希表读取refs_result,而disguised是传入的值,接着判断refs_result.second是不是第一次来,当结果为真,调用setHasAssociatedObjects()函数。
    接着引用refs_result.firstsecond值,对引用的refs进行第二次调用try_emplace,接着插入ObjcAssociation的值。

    下面是分别进入两次后的TheBucket的值:

    (lldb) p TheBucket
    (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> > > *) $0 = 0x000000010068b5b0
    (lldb) p TheBucket
    (objc::detail::DenseMapPair<const void *, objc::ObjcAssociation> *) $1 = 0x0000000102014cd8
    (lldb) 
    

    接下来看一下当value为空时,程序执行else部分:

    iShot2020-10-21 16.50.53.png

    上图部分代码的作用其实就是将关联对象进行消除;

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

    相关文章

      网友评论

          本文标题:iOS底层之关联对象

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