美文网首页
Objective-C 扩展与关联对象

Objective-C 扩展与关联对象

作者: HotPotCat | 来源:发表于2021-07-23 18:09 被阅读0次

    一、类扩展分析

    1.1 category 与 extension

    categoryextension开发中经常遇到,他们的区别对比简单总结下:

    category(类别、分类)

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

    extension(扩展)

    • 可以称为特殊分类(匿名分类)。
    • 可以给类添加成员属性,但是是私有变量。
    • 可以给类添加方法,也是私有方法。

    1.2 extension 底层实现分析

    对于extension我们一般常用的方式是将extension与类的实现放在一起都放在.m中:

    image.png

    也可以创建单独的extension

    image.png
    这个时候就只生成了对应的.h文件。
    • extension要在类的声明之后,实现之前。
    @interface HPObject : NSObject
    
    @property (nonatomic, copy) NSString *name;
    @property (nonatomic, copy) NSString *age;
    
    - (void)instanceMethod;
    + (void)classMethod;
    
    @end
    
    @interface HPObject ()
    
    @property (nonatomic, copy) NSString *ext_name;
    @property (nonatomic, copy) NSString *ext_age;
    
    - (void)ext_instanceMethod;
    + (void)ext_classMethod;
    
    @end
    
    @implementation HPObject
    
    - (void)instanceMethod {
        NSLog(@"%s",__func__);
    }
    
    + (void)classMethod {
        NSLog(@"%s",__func__);
    }
    
    - (void)ext_instanceMethod {
        NSLog(@"%s",__func__);
    }
    
    + (void)ext_classMethod {
        NSLog(@"%s",__func__);
    }
    
    @end
    

    将上面的代码转成.cpp查看对应的实现:

    image.png
    可以看到extension的内容与类的内容合并到了一起。并且没有搜到与category_t类似的extension_t

    realizeClassWithoutSwift中条件断点源码验证:

    image.png
    可以看到ro中已经有了extension的方法了(包括setter & getter)。
    • 分类会影响到类的编译和加载。
    • 类扩展不会影响类的编译和加载。
    • 类扩展可以写多个,最后也都是合并进主类了,不影响。(写在同一个文件或者不同文件都可以)
    • 类扩展必须与主类在一起,最好定义在主类.m中,单独定义还是要导入主类.m中。(单独写意义不大)

    如果extension是单独的文件声明的,需要将.h文件导入主类的.m中(分类不行),否则不会将extension中定义的属性和成员变量加入类中(方法能加入是因为方法本身是实现在主类中的)。所以单独声明定义extension没有任何意义,只是能这样做而已。

    二、关联对象

    分类中添加属性后会报警告:

    Property 'cat_name' requires method 'cat_name' to be defined - use @dynamic or provide a method implementation in this category
    
    image.png
    本质上是因为分类中用@property定义变量,只会生成变量的geter & setter方法的声明,不能生成方法的实现和带下划线的成员变量。本质上是因为没有成员变量,getter & setter无法进行存值与取值。

    这个时候可以通过关联对象给类添加属性:

    - (void)setCat_name:(NSString *)cat_name {
        //对象、标识符、value、策略
        return objc_setAssociatedObject(self, "cat_name", cat_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
    }
    
    - (NSString *)cat_name {
        return objc_getAssociatedObject(self, "cat_name");
    }
    

    2.1 关联对象存储值

    那么关联对象是怎么实现的呢?

    2.1.1 objc_setAssociatedObject

    objc_setAssociatedObject的源码实现:

    void
    objc_setAssociatedObject(id object, const void *key, id value, objc_AssociationPolicy policy)
    {
        _object_set_associative_reference(object, key, value, policy);
    }
    

    内部直接调用了_object_set_associative_reference

    818版本的实现与779版本不同,779版本中 objc_setAssociatedObject 的实现调用的是 SetAssocHook.get()

    2.1.2 _object_set_associative_reference

    源码解读:

    //关联对象 存储 object -> cat_name -> value - policy
    void
    _object_set_associative_reference(id object, const void *key, id value, uintptr_t policy)
    {
        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};
        //包装 {policy, value} 为 ObjcAssociation
        ObjcAssociation association{policy, value};
    
        //根据 policy 对 value 进行操作
        association.acquireValue();
    
        bool isFirstAssociation = false;
        {
            //manager 不是单例,调用构造函数创建 manager,内部进行了加锁和解锁。
            AssociationsManager manager;
            //AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap。它是在`map_images`的时候初始化。
            AssociationsHashMap &associations(manager.get());
    
            if (value) {//有值
                //创建/插入bucket
                auto refs_result = associations.try_emplace(disguised, ObjectAssociationMap{});
                if (refs_result.second) {//第一次,也就是插入 bucket 的时候 second 为 true
                    /* it's the first association we make */
                    isFirstAssociation = true;
                }
                /* establish or replace the association */
                //这个时候 association 还没有存
                auto &refs = refs_result.first->second;
                //相当于第二层
                //这时候的key就是成员变量的key,将 association 插入桶中。有值的情况下没有插入,没有值的情况下才插入。
                auto result = refs.try_emplace(key, std::move(association));
                if (!result.second) {//second为false 证明 LookupBucketFor 找到了。
                    // result.first->second 为 association。将旧的值替换为新的。association 变为旧值。
                    association.swap(result.first->second);
                }
            } else {//没有值,进行清空处理。
                //找到对象的ref
                auto refs_it = associations.find(disguised);
                if (refs_it != associations.end()) {
                    auto &refs = refs_it->second;
                    //找到对应key的 association
                    auto it = refs.find(key);
                    if (it != refs.end()) {
                        //交换值,也就是内存中存储的it修改为nil,association修改为之前的值。
                        association.swap(it->second);
                        //擦除内存中it数据。
                        refs.erase(it);
                        if (refs.size() == 0) {
                            //如果对象的ref没有关联对象了,则整个对象擦除。
                            associations.erase(refs_it);
    
                        }
                    }
                }
            }
        }
        //只在第一次标记对象是否有关联对象
        if (isFirstAssociation)
            object->setHasAssociatedObjects();
    
        //释放旧值。
        association.releaseHeldValue();
    }
    
    • 对象,值以及类是否禁用关联对象的逻辑判断。
    • 将对象包装成DisguisedPtr统一类型。
    • 将策略和值包装为ObjcAssociation类型并且根据策略对值进行操作。
    inline void acquireValue() {
        if (_value) {
            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;
            }
        }
    }
    
    • AssociationsManager manager调用构造函数创建 manager,内部进行了加锁和解锁。本身不是单例。

    • AssociationsHashMap通过manager获取,本质上可以认为是一个单例。是在map_images的时候进行的初始化。

    • value有值的情况:

      • try_emplace创建插入bucket。在这一次将object对应的map插入单例map中。
      • 根据refs_result.second判断是否是第一次插入。如果原先已经存在则secondfalse,否则为trueisFirstAssociation进行第一次标记。
      • refs_result.first->second获取到的相当于是object对应的map,这个时候map中还没有存储构造的association,也就是value-policy
      • 再次try_emplacekeyassociation传入就相当于要存储关联对象到map了(内层)。
        • 值存在的时候不会存储进去与上面一样通过second返回结果。为false证明之前有值。会走到association.swap逻辑。
      • association.swap是为了将旧值交换出来,存储新值进去在后续做释放旧值的存在。
        可以通过赋值两次进行验证:
    • value没有值的情况:

      • associations中找到object对应的map存为refs_it
      • map不为结束标记则取refs_it->second,根据keyassociation存入it
      • 不为结束标记则对it进行交换,也就是map中替换为 {policy,nil},将原先的值存入association。然后擦除map中的值。
      • 如果该对象已经没有关联对象了,则擦出该对象的关联对象map。擦除相当于标记位墓碑(erase)。
    • 根据isFirstAssociation,标记对象是否有关联对象。

    • 通过association释放旧值。

    2.1.2.1 disguised 与 association 验证

    image.png

    2.1.2.2 refs_result 结构

    refs_result结构:

    image.png
    格式化后结构如下:
    类型:
    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
    >
    
    值:
    {
      first = {
        Ptr = 0x0000000101016c70
        End = 0x0000000101016cb0
      }
      second = true
    }
    

    first对应objc::DenseMapIteratorsecond对应最后一个bool值也就是true

    2.1.3 AssociationsManager

    class AssociationsManager {
        using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
        //静态变量,声明在 AssociationsManager 中是一个全局变量,只能 AssociationsManager 调用。 相当于单例。
        static Storage _mapStorage;
    
    public:
        //析构和构造函数,内部进行了加锁和解锁。
        AssociationsManager()   { AssociationsManagerLock.lock(); }
        ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
        //设置和获取 AssociationsHashMap,由于 _mapStorage 是静态的,所以 AssociationsHashMap 也就相当于单例
        AssociationsHashMap &get() {
            return _mapStorage.get();
        }
        //类方法,在 _objc_associations_init 调用。也就是`map_images`的时候。
        static void init() {
            _mapStorage.init();
        }
    };
    
    • _mapStorage是一个静态变量,声明在 AssociationsManager 中是一个全局变量,只能 AssociationsManager 调用。 相当于单例。
    • AssociationsManager在构造和析构函数中进行加解锁操作。
    • AssociationsHashMap通过AssociationsManagerget方法获取。
    • AssociationsHashMapAssociationsManager的类方法init中初始化。

    2.1.3.1 AssociationsManager 模仿验证:

    struct HPObjectS {
        HPObjectS() {
            printf("Creat HPObjectS\n");
        }
        ~HPObjectS() {
            printf("release HPObjectS\n");
        }
    };
    

    调用:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            HPObjectS s;
        }
        return 0;
    }
    

    输出:

    Creat HPObjectS
    release HPObjectS
    

    出了作用域就被释放了。所以AssociationsManager不是单例,内部进行了加解锁操作。

    验证c++的构造和析构函数需要修改.m文件为.mm或者修改typec++

    image.png

    2.1.4 AssociationsHashMap

    typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;
    
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }
    
    static void init() {
        _mapStorage.init();
    }
    
    • AssociationsHashMap是通过AssociationsManagerget()方法获取的。
    • 初始化时在AssociationsManager的类方法init()中。(这里相当于是总表的初始化)

    2.1.4.1 AssociationsHashMap 单例验证

    _object_set_associative_reference中添加如下测试代码(也可以多次调用进入进行测试):

    //manager 不是单例,调用构造函数创建 manager,内部进行了加锁和解锁。
    AssociationsManager manager;
    //AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap。它是在`map_images`的时候初始化。
    AssociationsHashMap &associations(manager.get());
    
    //测试代码
    printf("manager: %p, associations: %p\n",&manager,&associations);
    
    AssociationsManager manager1;
    AssociationsHashMap &associations1(manager1.get());
    
    printf("manager1: %p, associations1: %p\n",&manager1,&associations1);
    
    AssociationsManager manager2;
    AssociationsHashMap &associations2(manager2.get());
    
    printf("manager2: %p, associations2: %p\n",&manager2,&associations2);
    //end
    

    输出:

    manager: 0x7ffeefbff460, associations: 0x10035e188
    manager1: 0x7ffeefbff448, associations1: 0x10035e188
    manager2: 0x7ffeefbff438, associations2: 0x10035e188
    
    • AssociationsHashMap多次输出地址一致,为单例对象。
    • AssociationsManager多次输出地址不一致,普通对象,只是内部对静态变量有操作而已。

    由于AssociationsManager中有加锁解锁操作,先暂时注释掉加锁解锁操作,否则会造成死锁(锁递归):

    image.png

    2.1.4.2 AssociationsHashMap 初始化

    上面的代码分析可以看到在AssociationsManager的类方法init()中有对_mapStorage的初始化:

    static void init() {
        _mapStorage.init();
    }
    

    那么初始化是在什么时机调用的呢?既然是单例那么应该只调用一次,直接在init中打断点有如下调用堆栈:

    libobjc.A.dylib`objc::AssociationsManager::init() at objc-references.mm:124:21
    libobjc.A.dylib`_objc_associations_init at objc-references.mm:137:5
    libobjc.A.dylib`arr_init at NSObject.mm:2214:5
    libobjc.A.dylib`map_images_nolock(mhCount=261, mhPaths=0x00007ffeefbf4e90, mhdrs=0x00007ffeefbf40f0) at objc-os.mm:544:9
    libobjc.A.dylib`map_images(count=261, paths=0x00007ffeefbf4e90, mhdrs=0x00007ffeefbf40f0) at objc-runtime-new.mm:3259:12
    
    • 是在map_images中初始化的。

    调用的入口是map_images_nolock -> arr_init()arr_init的实现:

    void arr_init(void) 
    {
        //内存页初始化
        AutoreleasePoolPage::init();
        //散列表初始化,引用计数表和弱引用表
        SideTablesMap.init();
        //关联对象map初始化
        _objc_associations_init();
    }
    
    • AutoreleasePoolPage内存页初始化。
    • SideTablesMap散列表初始化,其中包含引用计数表和弱引用表。
    • _objc_associations_init关联对象表初始化,内部调用了AssociationsManager::init()

    2.1.5 try_emplace

      template <typename... Ts>
      std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
        //与类的cache的bucket_t好像
        BucketT *TheBucket;
        //通过key(key是包装的 DisguisedPtr,也就是要存储到的对象。) 找bucket
        //TheBucket 没有被const 修饰,走的第二个 LookupBucketFor。TheBucket指针传递,值会被带回来。
        //通过 LookupBucketFor TheBucket 要么有值要么为空桶
        if (LookupBucketFor(Key, TheBucket))
          //second value 为false
          return std::make_pair(
                   makeIterator(TheBucket, getBucketsEnd(), true),
                   false); // Already in map.
    
        // Otherwise, insert the new element.
        //走到这里证明,bucket为空桶。则将bucket插入桶中。 objc::detail::DenseMapPair
        TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
        //first value, second value 赋值为 true
        return std::make_pair(
                 makeIterator(TheBucket, getBucketsEnd(), true),
                 true);
      }
    
    • try_emplacevalue有值的情况下会调用两次,第一次key为包装的对象DisguisedPtr,第二次key为关联对象的key。这里也就说明整个关联对象表是一个双层结构。
    • LookupBucketFor查找key对应的bucket,也就是对象对应的map以及关联对象key对应的value(都是包装后的)。
    • 找到则包装后返回,没有找到就插入bucket。为了返回原始值进行释放,这也就是有旧值的情况下,值不被替换的原因。

    2.1.5.1 LookupBucketFor

    LookupBucketFor有两个,BucketT参数不同,一个用const修饰了:

    image.png
    两个,其中进行了内部调用:
      template <typename LookupKeyT>
      bool LookupBucketFor(const LookupKeyT &Val, BucketT *&FoundBucket) {
        const BucketT *ConstFoundBucket;
        //调用上面的LookupBucketFor,result 为 true 证明 Val 对应的bucket已经存在。否则bucket不存在,返回了一个空桶。
        bool Result = const_cast<const DenseMapBase *>(this)
          ->LookupBucketFor(Val, ConstFoundBucket);
        //赋值bucket给传进来的参数。
        FoundBucket = const_cast<BucketT *>(ConstFoundBucket);
        return Result;
      }
    
    • 这里调用了LookupBucketFor,两者参数不一样。返回值为result,找没有找到bucket,也就对应map以及value
    • FoundBucket是指针传值,内部修改后外部也就修改了。
      template<typename LookupKeyT>
      //BucketT 被 const 修饰了
      bool LookupBucketFor(const LookupKeyT &Val,
                           const BucketT *&FoundBucket) const {
        //获取buckets地址
        const BucketT *BucketsPtr = getBuckets();
        //获取buckets数量
        const unsigned NumBuckets = getNumBuckets();
    
        if (NumBuckets == 0) {
          FoundBucket = nullptr;
          return false;
        }
    
        // FoundTombstone - Keep track of whether we find a tombstone while probing.
        const BucketT *FoundTombstone = nullptr;
        const KeyT EmptyKey = getEmptyKey();
        const KeyT TombstoneKey = getTombstoneKey();
        assert(!KeyInfoT::isEqual(Val, EmptyKey) &&
               !KeyInfoT::isEqual(Val, TombstoneKey) &&
               "Empty/Tombstone value shouldn't be inserted into map!");
        //key & (容量 - 1 ),类似于cache的mask,也就是找到index
        unsigned BucketNo = getHashValue(Val) & (NumBuckets-1);
        unsigned ProbeAmt = 1;
        //与cache插入类似
        while (true) {
          const BucketT *ThisBucket = BucketsPtr + BucketNo;
          // Found Val's bucket?  If so, return it.
          //找到了bucket,也就是bucket已经存在了。LLVM_LIKELY 就是 fastpath
          if (LLVM_LIKELY(KeyInfoT::isEqual(Val, ThisBucket->getFirst()))) {
            FoundBucket = ThisBucket;
            return true;
          }
    
          // If we found an empty bucket, the key doesn't exist in the set.
          // Insert it and return the default value.
          // 找到了空桶
          if (LLVM_LIKELY(KeyInfoT::isEqual(ThisBucket->getFirst(), EmptyKey))) {
            // If we've already seen a tombstone while probing, fill it in instead
            // of the empty bucket we eventually probed to.
            FoundBucket = FoundTombstone ? FoundTombstone : ThisBucket;
            return false;
          }
    
          // If this is a tombstone, remember it.  If Val ends up not in the map, we
          // prefer to return it than something that would require more probing.
          // Ditto for zero values.
          if (KeyInfoT::isEqual(ThisBucket->getFirst(), TombstoneKey) &&
              !FoundTombstone)
            FoundTombstone = ThisBucket;  // Remember the first tombstone found.
          if (ValueInfoT::isPurgeable(ThisBucket->getSecond())  &&  !FoundTombstone)
            FoundTombstone = ThisBucket;
    
          // Otherwise, it's a hash collision or a tombstone, continue quadratic
          // probing.
          if (ProbeAmt > NumBuckets) {
            FatalCorruptHashTables(BucketsPtr, NumBuckets);
          }
          //重新计算下标
          BucketNo += ProbeAmt++;
          //再hash
          BucketNo &= (NumBuckets-1);
        }
      }
    
    • 这块逻辑与方法缓存很像,首先通过容量 -1(类似mask)计算BucketNo,也就是找到index
    • 根据index查找bucketbucket有值返回true,没有值返回false
    • 没有找到的情况下重新计算下标,再hash

    2.1.5.2 InsertIntoBucket

      template <typename KeyArg, typename... ValueArgs>
      BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                                ValueArgs &&... Values) {
        TheBucket = InsertIntoBucketImpl(Key, Key, TheBucket);
    
        TheBucket->getFirst() = std::forward<KeyArg>(Key);
        ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
        return TheBucket;
      }
    
    • 先进行扩容/找到bucket
    • 设置bucket对应的值。

    InsertIntoBucketImpl

    template <typename LookupKeyT>
      BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                    BucketT *TheBucket) {
        unsigned NewNumEntries = getNumEntries() + 1;
        unsigned NumBuckets = getNumBuckets();
        // 3/4 扩容
        if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
          //2倍扩容
          this->grow(NumBuckets * 2);
          LookupBucketFor(Lookup, TheBucket);
          NumBuckets = getNumBuckets();
        } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                                 NumBuckets/8)) {
          this->grow(NumBuckets);
          LookupBucketFor(Lookup, TheBucket);
        }
     ……
    
        return TheBucket;
      }
    

    内部进行了查找扩容,返回对应的bucket

    • 这里也遵循 负载因子(3/4 以及2倍扩容。
    • (总容量 - 已有元素)* 8 <= 总容量相当于7/8判断。

    2.1.6 setHasAssociatedObjects

    标记对象是否有关联对象

    inline void
    objc_object::setHasAssociatedObjects()
    {
        //Tagged Pointer 直接返回
        if (isTaggedPointer()) return;
        //纯指针 && 有默认的 release,retain等方法,非future类,非元类
        if (slowpath(!hasNonpointerIsa() && ISA()->hasCustomRR()) && !ISA()->isFuture() && !ISA()->isMetaClass()) {
            //获取_noteAssociatedObjects 方法
            void(*setAssoc)(id, SEL) = (void(*)(id, SEL)) object_getMethodImplementation((id)this, @selector(_noteAssociatedObjects));
            //不为消息转发,也就是找到了方法。
            if ((IMP)setAssoc != _objc_msgForward) {
                //调用 _noteAssociatedObjects
                (*setAssoc)((id)this, @selector(_noteAssociatedObjects));
            }
        }
        //设置新的isa
        isa_t newisa, oldisa = LoadExclusive(&isa.bits);
        do {
            newisa = oldisa;
            //纯指针/已经有关联对象标记
            if (!newisa.nonpointer  ||  newisa.has_assoc) {
                ClearExclusive(&isa.bits);
                return;
            }
            //isa关联对象标记
            newisa.has_assoc = true;
        } while (slowpath(!StoreExclusive(&isa.bits, &oldisa.bits, newisa.bits)));
    }
    
    • isa纯指针走_noteAssociatedObjects逻辑,系统会判断是否实现了_noteAssociatedObjects方法。
    • isa非纯指针直接设置has_assoc标记。
    • _noteAssociatedObjects系统并没有实现,应该是提供给我们实现的。但是以_开头应该是私有方法。一般情况下我们是用不到的。

    在判断是否有关联对象的方法中:

    inline bool
    objc_object::hasAssociatedObjects()
    {
        if (isTaggedPointer()) return true;
        if (isa.nonpointer) return isa.has_assoc;
        return true;
    }
    

    可以看到默认的情况下返回true

    ⚠️设值的时候是一个对象,那么可以设置类对象,元类对象,只不过释放就要自己操作了。否则就只能类销毁的时候释放了。也就是伴随着应用程序的整个生命周期了。

    2.2 关联对象读取值

    上面分析了关联对象的存储值,有存储就有对应的取值,那么应该也是按照两层AssociationsHashMap获取的。objc_getAssociatedObject内部直接调用了_object_get_associative_reference

    2.2.1 _object_get_associative_reference

    id
    _object_get_associative_reference(id object, const void *key)
    {
        ObjcAssociation association{};
    
        {
            AssociationsManager manager;
            //整体单例map
            AssociationsHashMap &associations(manager.get());
            //找到对象对应的map 参数是 DisguisedPtr
            AssociationsHashMap::iterator i = associations.find((objc_object *)object);
            if (i != associations.end()) {
                ObjectAssociationMap &refs = i->second;
                //找到key对应的 association
                ObjectAssociationMap::iterator j = refs.find(key);
                if (j != refs.end()) {
                    association = j->second;
                    association.retainReturnedValue();
                }
            }
        }
        //返回 association的 _value 也就是我们存储的 value。
        return association.autoreleaseReturnedValue();
    }
    
    • 获取整体单例associations (AssociationsHashMap)
    • 包装objectDisguisedPtrassociations获取对象对应的i(AssociationsHashMap)
    • 通过key从内层i(ObjectAssociationMap)获取ObjcAssociation
    • ObjcAssociation获取value返回。(ObjcAssociation{_policy,_value}

    这样就与存储的时候两层结构对应上了,整个结构图下:


    关联对象hashmap结构

    2.3 关联对象的释放

    官方提供了objc_removeAssociatedObjects供我们释放关联对象。

    //移除关联对象
    void objc_removeAssociatedObjects(id object) 
    {
        if (object && object->hasAssociatedObjects()) {
            _object_remove_assocations(object, /*deallocating*/false);
        }
    }
    

    内部实现是调用_object_remove_assocations

    2.3.1 _object_remove_assocations

    void
    _object_remove_assocations(id object, bool deallocating)
    {
        ObjectAssociationMap refs{};
    
        {
            AssociationsManager manager;
            AssociationsHashMap &associations(manager.get());
            AssociationsHashMap::iterator i = associations.find((objc_object *)object);
            if (i != associations.end()) {
                //将值全部存入 refs 临时空间中
                refs.swap(i->second);
                
                // If we are not deallocating, then SYSTEM_OBJECT associations are preserved.
                bool didReInsert = false;
                if (!deallocating) {//对象非释放的情况下
                    for (auto &ref: refs) {
                        if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                            //重新将系统的关联对象插入
                            i->second.insert(ref);
                            didReInsert = true;
                        }
                    }
                }
                if (!didReInsert)
                    //没有重新插入的话则擦除associations
                    associations.erase(i);
            }
        }
    
        // Associations to be released after the normal ones.
        SmallVector<ObjcAssociation *, 4> laterRefs;
    
        // release everything (outside of the lock).
        for (auto &i: refs) {
            if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                // If we are not deallocating, then RELEASE_LATER associations don't get released.
                if (deallocating)
                    //dealloc的时候系统关联对象,先放入laterRefs 稍后释放,否则不处理。
                    laterRefs.append(&i.second);
            } else {
                //释放非系统的关联对象
                i.second.releaseHeldValue();
            }
        }
        for (auto *later: laterRefs) {
            //dealloc 的情况下释放系统的关联对象
            later->releaseHeldValue();
        }
    }
    
    • 根据第二个参数deallocating判断是不是dealloc的时候调用的。
    • 将对象对应的ObjectAssociationMap存入refs临时空间,本来的空间i置空。
    • deallocating情况下(也就是自己调用),将系统的关联对象(通过policy & OBJC_ASSOCIATION_SYSTEM_OBJECT判断)重新插入i,并标记是否重新插入didReInsert
    • 没有重新插入的情况下擦除object对应的ObjectAssociationMap
    • 创建laterRefs记录稍后要释放的ObjcAssociation
    • 循环refs非系统的关联对象直接释放,系统的关联对象判断是否deallocatingdeallocating的情况下加入laterRefs
    • 循环释放laterRefs,也就是系统的关联对象(deallocating的情况下才有值)。

    关联对象流程

    • 设值流程:

      • 1.创建一个 AssociationsManager 管理类。
      • 2.获取唯一的全局静态哈希AssociationsHashMap
      • 3.判断是否插入的关联值是否存在:
        • 3.1存在走插入流程
          • 3.1.1 创建一个空的 ObjectAssociationMap 去取查询的键值对。
          • 3.1.2 如果发现没有这个 key 就插入一个 空的 BucketT进去并返回。
          • 3.1.3 标记对象存在关联对象(isFirstAssociation)。
          • 3.1.4 用当前策略和值组成一个 ObjcAssociation 替换原来 BucketT 中的空。(有旧值需要交换释放旧值)
        • 3.2不存在就走关联对象插入空流程 (插入空置,相当于清除)
          • 3.2.1 根据 DisguisedPtr 找到 ObjectAssociationMap 中的iterator 迭代查询器。
          • 3.2.2 根据key找到ObjcAssociation
          • 3.2.3 交换关联对象,擦除关联对象。
          • 3.2.4 如果object对应的ObjectAssociationMap没有值了,则擦除。
      • 4.根据是否第一个关联对象设置是否存在关联对象。
      • 5.释放旧值
    • 取值流程:

      • 1.创建一个AssociationsManager 管理类。
      • 2.获取唯一的全局静态哈希AssociationsHashMap
      • 3.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器。
      • 4.如果这个迭代查询器不是最后一个,获取ObjectAssociationMap (这里有策略和value)。
      • 5.根据key找到ObjectAssociationMap中对应的ObjcAssociation
      • 6.返回ObjcAssociation中的_value
    • 释放流程:

      • 1.创建一个 AssociationsManager 管理类。
      • 2.获取唯一的全局静态哈希AssociationsHashMap
      • 3.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器。
      • 3.根据 DisguisedPtr 找到 AssociationsHashMap 中的 iterator 迭代查询器。
      • 4.如果这个迭代查询器不是最后一个元素,获取 ObjectAssociationMap
      • 5.循环遍历ObjectAssociationMap在非deallocing的情况下将系统关联对象重新插入 。
      • 6.遍历循环如果是系统关联对象,deallocating的情况下将系统的加入临时数组,释放非系统的关联对象。
      • 7.如果6中的临时数组有数据,遍历释放。(也就是deallocating的情况下释放系统的关联对象)。

    总结: 关联对象的存储结构其实就是两层哈希map , 存取的时候进行两层处理(类似二维数组),删除的时候由于存在非dealloc的情况以及系统添加的关联对象,以及擦除值的操作,也需要两层操作。

    三、dealloc

    既然关联对象是和对象绑定在一起的,那么在对象释放的时候关联对象肯定也要释放,在平常的开发中我们一般不回去主动调用释放关联对象的API objc_removeAssociatedObjects,根据上面的分析在dealloc中肯定调用了_object_remove_assocations
    那么在dealloc中都做了什么呢?

    - (void)dealloc {
        _objc_rootDealloc(self);
    }
    
    _objc_rootDealloc(id obj)
    {
        ASSERT(obj);
    
        obj->rootDealloc();
    }
    

    dealloc直接调用了_objc_rootDealloc-> obj->rootDealloc,核心逻辑在rootDealloc中。

    3.1 rootDealloc

    inline void
    objc_object::rootDealloc()
    {
        //Tagged Pointer
        if (isTaggedPointer()) return;  // fixme necessary?
        //isa非纯指针
        //弱引用
        //关联对象
        //c++析构函数
        //引用计数表
        //有这五种情况则不能直接释放,否则可以直接free。
        if (fastpath(isa.nonpointer                     &&
                     !isa.weakly_referenced             &&
                     !isa.has_assoc                     &&
    #if ISA_HAS_CXX_DTOR_BIT
                     !isa.has_cxx_dtor                  &&
    #else
                     !isa.getClass(false)->hasCxxDtor() &&
    #endif
                     !isa.has_sidetable_rc))
        {
            assert(!sidetable_present());
            free(this);
        } 
        else {
            //释放对象
            object_dispose((id)this);
        }
    }
    
    • isa非纯指针,有弱引用,有关联对象,有c++析构函数,有引用计数表的情况下不能直接释放对象走object_dispose逻辑,否则直接释放。

    3.2 object_dispose

    id 
    object_dispose(id obj)
    {
        if (!obj) return nil;
        //毁坏对象
        objc_destructInstance(obj);    
        free(obj);
    
        return nil;
    }
    

    内部进行了对象的毁坏后释放对象,核心逻辑就在objc_destructInstance中了。

    3.2.1 objc_destructInstance

    void *objc_destructInstance(id obj) 
    {
        if (obj) {
            // Read all of the flags at once for performance.
            bool cxx = obj->hasCxxDtor();
            bool assoc = obj->hasAssociatedObjects();
    
            // This order is important.
            //调用C++析构函数
            if (cxx) object_cxxDestruct(obj);
            //移除关联对象
            if (assoc) _object_remove_assocations(obj, /*deallocating*/true);
            //清除其它逻辑
            obj->clearDeallocating();
        }
    
        return obj;
    }
    
    • 调用c++析构函数。
    • 移除关联对象。
    • clearDeallocating清空弱引用表以及引用计数表。

    3.2.2 clearDeallocating

    inline void 
    objc_object::clearDeallocating()
    {
        if (slowpath(!isa.nonpointer)) {//纯指针
            // Slow path for raw pointer isa.
            //清空散列表 两个不调用通一个方法的原因是 纯指针需要 SIDE_TABLE_WEAKLY_REFERENCED 判断是否有弱引用,nonpointer 可以通过isa字段判断
            sidetable_clearDeallocating();
        }
        else if (slowpath(isa.weakly_referenced  ||  isa.has_sidetable_rc)) {//非纯指针 有弱引用或者引用计数表
            // Slow path for non-pointer isa with weak refs and/or side table data.
            clearDeallocating_slow();
        }
    
        assert(!sidetable_present());
    }
    

    根据是否纯指针分为两个逻辑,纯指针调用sidetable_clearDeallocating清空散列表,非纯指针调用clearDeallocating_slow

    3.2.3 sidetable_clearDeallocating & clearDeallocating_slow

    sidetable_clearDeallocating

    void 
    objc_object::sidetable_clearDeallocating()
    {
        SideTable& table = SideTables()[this];
    
        // clear any weak table items
        // clear extra retain count and deallocating bit
        // (fixme warn or abort if extra retain count == 0 ?)
        table.lock();
        //在散列表中找到自身
        RefcountMap::iterator it = table.refcnts.find(this);
        if (it != table.refcnts.end()) {
            //通过it判断是否有弱引用
            if (it->second & SIDE_TABLE_WEAKLY_REFERENCED) {
                //清空自身弱引用
                weak_clear_no_lock(&table.weak_table, (id)this);
            }
            //擦除引用计数表
            table.refcnts.erase(it);
        }
        table.unlock();
    }
    
    • 在散列表中找到自己。
    • 清空自己的弱引用。根据SIDE_TABLE_WEAKLY_REFERENCED判断是否有弱引用。
    • 擦除自己的引用计数表。

    clearDeallocating_slow

    NEVER_INLINE void
    objc_object::clearDeallocating_slow()
    {
        ASSERT(isa.nonpointer  &&  (isa.weakly_referenced || isa.has_sidetable_rc));
    
        //散列表
        SideTable& table = SideTables()[this];
        table.lock();
        if (isa.weakly_referenced) {
            //清除弱引用表中自己
            weak_clear_no_lock(&table.weak_table, (id)this);
        }
        if (isa.has_sidetable_rc) {
            //擦除引用计数表中自己
            table.refcnts.erase(this);
        }
        table.unlock();    
    }
    
    • 获取散列表。
    • 通过isa.weakly_referenced判断是否有弱引用,有弱引用则清除弱引用。
    • 判断是否有弱引用计数表(isa.has_sidetable_rc),有则擦除弱引用表中自己的计数。

    weak_clear_no_lock

    void 
    weak_clear_no_lock(weak_table_t *weak_table, id referent_id) 
    {
        //要清除的对象
        objc_object *referent = (objc_object *)referent_id;
        //
        weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
     ……
    
        // zero out references weak对象引用表
        weak_referrer_t *referrers;
        size_t count;
        
        if (entry->out_of_line()) {
            referrers = entry->referrers;
            count = TABLE_SIZE(entry);
        } 
        else {
            referrers = entry->inline_referrers;
            count = WEAK_INLINE_COUNT;
        }
        
        for (size_t i = 0; i < count; ++i) {
            objc_object **referrer = referrers[i];
            if (referrer) {
                if (*referrer == referent) {
                    //weak引用置为nil
                    *referrer = nil;
                }
                else if (*referrer) {
       ……
                    objc_weak_error();
                }
            }
        }
        //移除弱引用表中的entry
        weak_entry_remove(weak_table, entry);
    }
    
    • 在若引用计数表中找到自己的weak_entry_t
    • 根据out_of_line获取指向对象的弱引用指针referrersinline_referrers
    • 循环弱引用指针数组将弱引用指针置为nil
    • 将弱引用weak_entry_t从弱引用表中删除。

    dealloc调用流程:

    • 不能直接释放(弱引用、关联对象、c++析构函数、引用计数表)
      • 1.调用c++析构函数。
      • 2.移除关联对象。
      • 3.清空弱引用表(弱引用指针数组全部指针置为nil)。
      • 4.擦除引用计数表中自己。
      • 5.free
    • 能直接释放,free

    已知的一个常识是在dealloc中不需要调用[super dealloc],但是在源码分析中并没有看到调用父类的dealloc方法,在llvm中发下如下代码:

    - (void)dealloc {
     [_myproperty release];
     [super dealloc];
    }
    

    可以看到应该在编译阶段llvm自动添加了super dealloc的调用。反汇编后也能看到在编译阶段就已经编译进去了:

    image.png

    四、案例分析

    关联对象怎么添加weak属性?
    在设置关联对象的时候,objc_AssociationPolicy的取值如下:

    typedef OBJC_ENUM(uintptr_t, objc_AssociationPolicy) {
        OBJC_ASSOCIATION_ASSIGN = 0,           /**< Specifies a weak reference to the associated object. */
        OBJC_ASSOCIATION_RETAIN_NONATOMIC = 1, /**< Specifies a strong reference to the associated object. 
                                                *   The association is not made atomically. */
        OBJC_ASSOCIATION_COPY_NONATOMIC = 3,   /**< Specifies that the associated object is copied. 
                                                *   The association is not made atomically. */
        OBJC_ASSOCIATION_RETAIN = 01401,       /**< Specifies a strong reference to the associated object.
                                                *   The association is made atomically. */
        OBJC_ASSOCIATION_COPY = 01403          /**< Specifies that the associated object is copied.
                                                *   The association is made atomically. */
    };
    

    在这个枚举中并没有发现weak,只发现OBJC_ASSOCIATION_ASSIGN的注释中有weak描述,但是搜索OBJC_ASSOCIATION_ASSIGN却没有调用的地方,那也就说明它不会主动置为nil
    既然这样只能另辟蹊径,存储的时候只能是强引用,但是释放后要置为nil。一种主流的实现方案是使用blockblock持有弱引用。

    示例代码如下:

    @property (nonatomic, weak) id weak_obj;
    
    - (void)setWeak_obj:(id)weak_obj {
        id __weak weakObject = weak_obj;
        //存储 weak 类型的 obj
        id (^block)(void) = ^{ return weakObject; };
        return objc_setAssociatedObject(self, "weak_obj", block, OBJC_ASSOCIATION_COPY);
    }
    
    - (id)weak_obj {
        //获取block
        id (^block)(void) = objc_getAssociatedObject(self, "weak_obj");
        //执行block,取到block返回的obj
        id weakObject = (block ? block() : nil);
        return weakObject;
    }
    

    实际上关联对象仍然是强引用,但是block内部持有了变量的弱引用。这样就相当于加了个中间层。在weak_obj释放后,block就获取不到weak_obj了。

    关联对象在什么情况下造成内存泄漏?
    关联对象可以理解就是持有了一个对象,如果是retain等方式的持有,而该对象也持有了本类,那就导致了循环引用。一般block造成循环引用的情况比较多。

    五、关联对象流程图

    关联对象-取值、设值、释放、初始化流程

    相关文章

      网友评论

          本文标题:Objective-C 扩展与关联对象

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