iOS底层原理之OC类的扩展与关联对象

前言

前文iOS底层原理之OC类的加载原理(下)已经分析了分类加载的流程,以及主类和分类搭配加载的情况。本文就来分析下特殊的分类--类扩展和分类属性的存取--关联对象

准备工作

一: list_array_tt数据结构分析

开始今天主要内容之前,先补充一点小内容。

根据前文的分析,我们知道方法列表在底层的数据结构是method_array_t,下面就来探究下这个数据结构以及lldb调试时使用的函数的由来。

method_array_t继承自模板类list_array_tt

// Element = method_t, List = method_list_t, Ptr = method_list_t_authed_ptr
template <typename Element, typename List, template<typename> class Ptr>
class list_array_tt {
    struct array_t {
        uint32_t count;
        Ptr<List> lists[0];

        static size_t byteSize(uint32_t count) {
            return sizeof(array_t) + count*sizeof(lists[0]);
        }
        size_t byteSize() {
            return byteSize(count);
        }
    };
    
    ...
    
public:
    union { // 互斥
        Ptr<List> list;
        uintptr_t arrayAndFlag;
    };

    bool hasArray() const {
        return arrayAndFlag & 1;
    }

    array_t *array() const {
        return (array_t *)(arrayAndFlag & ~1);
    }

    void setArray(array_t *array) {
        arrayAndFlag = (uintptr_t)array | 1;
    }
    
    ...
    
    uint32_t count() const {
        uint32_t result = 0;
        for (auto lists = beginLists(), end = endLists(); 
             lists != end;
             ++lists)
        {
            result += (*lists)->count;
        }
        return result;
    }
    
    ...
    
    const Ptr<List>* beginLists() const {
        if (hasArray()) {
            return array()->lists;
        } else {
            return &list;
        }
    }

    const Ptr<List>* endLists() const {
        if (hasArray()) {
            return array()->lists + array()->count;
        } else if (list) {
            return &list + 1;
        } else {
            return &list;
        }
    }
    
    // attachLists函数也在这里,此处省略,感兴趣的朋友可以看上一篇文章
    void attachLists(List* const * addedLists, uint32_t addedCount) {
        ...
    }
  • list_array_tt类通过传入模板可以生成method_array_tproperty_array_tprotocol_array_t等。

根据传入的模板Element = method_t, List = method_list_t, Ptr = method_list_t_authed_ptr,先来查看method_list_t

image.png

method_list_t继承自entsize_list_tt,根据传入模板不同可以生成method_list_tproperty_list_tprotocol_list_t等。

image.png
  • 根据get(i)函数获取i位置的method_t *

method_t::pointer_modifier的实现。

struct method_t {
    ...
    struct pointer_modifier {
        template <typename ListType>
        static method_t *modify(const ListType &list, method_t *ptr) {
            if (list.flags() & smallMethodListFlag)
                return (method_t *)((uintptr_t)ptr | 1);
            return ptr;
        }
    };
    ...
}

PointerModifierNop的实现。

struct PointerModifierNop {
    template <typename ListType, typename T>
    static T *modify(__unused const ListType &list, T *ptr) { return ptr; }
};

再来看看method_t的数据结构。

struct method_t {
    static const uint32_t smallMethodListFlag = 0x80000000;

    struct big {
        SEL name;
        const char *types;
        MethodListIMP imp;
    };

private:
    // arm64架构(包括M1版iMac),小端模式
    bool isSmall() const {
        return ((uintptr_t)this & 1) == 1;
    }
    // small里面存的是三个相对偏移值
    struct small {
        RelativePointer<const void *> name;
        RelativePointer<const char *> types;
        RelativePointer<IMP> imp;
        
        bool inSharedCache() const {
            return (CONFIG_SHARED_CACHE_RELATIVE_DIRECT_SELECTORS &&
                    objc::inSharedCache((uintptr_t)this));
        }
    };
    
    // 获取small信息
    small &small() const {
        ASSERT(isSmall());
        return *(struct small *)((uintptr_t)this & ~(uintptr_t)1);
    }
    
    ...
    
    // 获取big信息
    big &big() const {
        ASSERT(!isSmall());
        return *(struct big *)this;
    }
    
    SEL name() const {
        if (isSmall()) {
            return (small().inSharedCache()
                    ? (SEL)small().name.get()
                    : *(SEL *)small().name.get());
        } else {
            return big().name;
        }
    }
    
    ...
}
  • method_t里面有我们经常使用的big()small()name()等函数。

到这里list_array_tt的数据结构和我们lldb调试时使用的函数由来就介绍完了。

二: 类扩展

1.1: category VS extension

category(分类/类别)

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

extension(类扩展)

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

1.2: extension格式

1.2.1: .m文件中

对于我们iOS开发者来说,最常见的就是这种定义在.m文件中的extension了:

image.png

⚠️extension必须写在类的声明之后,实现之前。.h文件中的声明部分在编译时也会被展开放到.m文件中。

1.2.2: 单独创建文件

当然也可以单独创建extension文件:

image.png
image.png

这种方式就只会生成.h文件,因为类扩展的实现也是需要在类的.m文件中的,而且这个.h文件必须导入类的.m文件,不然的话类无法访问extension定义的属性和成员变量(也不会生成属性的gettersetter),编译的时候这个extension也不会合并到类的ro中(方法因为实现就在类里面,就算不导入也不影响)。

这种方式其实只是把.m文件里的extension写成了一个单独的头文件而已。

1.3: extension底层原理分析

main.m创建XJBoy类,添加extension

@interface XJBoy : NSObject

@property (nonatomic, copy) NSString *name;

@property (nonatomic, assign) int age;

- (void)instanceMethod;

+ (void)classMethod;

@end

@interface XJBoy ()

@property (nonatomic, copy) NSString *ext_name;

@property (nonatomic, assign) int ext_age;

- (void)ext_instanceMethod;

+ (void)ext_classMethod;

@end

@implementation XJBoy

- (void)instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)classMethod{
    NSLog(@"%s",__func__);
}

- (void)ext_instanceMethod{
    NSLog(@"%s",__func__);
}

+ (void)ext_classMethod{
    NSLog(@"%s",__func__);
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        XJPerson * person = [XJPerson alloc];
        [person saySomething];
    }
    return 0;
}

通过clangmain.m生成main.cpp文件探索。

XJBoy类实现部分:

image.png

XJBoy成员变量、属性、方法列表:

image.png

可以看出extension的数据在编译时就是存储在类中的。

XJBoy类添加+load方法,并在realizeClassWithoutSwift函数添加断点调试:

image.png

从调试结果可以看出extension的数据也是在ro中的,而ro在编译时就已确定。

  • Category会影响类的加载,因为它有自己的.m文件,可以实现自己的+load方法。

  • Extension不会影响类的加载,因为它没有自己的.m文件,不管有几个Extension,所有的实现都是在类的.m文件中的。

三: 关联对象

分类添加属性后,会报相应的警告:

image.png
  • 提示开发者自己实现属性的getter & setter方法。

这是因为分类用@property声明的属性,只会生成getter & setter方法的声明,不会生成方法的实现和带下划线(_)的成员变量。而getter & setter方法是通过成员变量的偏移值,对成员变量进行存值和取值的,连成员变量都没有,所以也就没有getter & setter方法的实现。

这种情况下就需要通过关联对象来给分类属性添加getter & setter方法了。

- (void)setXja_name:(NSString *)xja_name
{
    objc_setAssociatedObject(self, "xja_name", xja_name, OBJC_ASSOCIATION_COPY_NONATOMIC);
}

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

3.1: 关联对象setter

3.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函数。

objc4-781及之前的版本中是通过SetAssocHook.get()函数获取到_base_objc_setAssociatedObject函数,然后再调用_object_set_associative_reference函数。

image.png

3.1.2: _object_set_associative_reference

// 关联对象 存储 self  xja_name  value  OBJC_ASSOCIATION_COPY_NONATOMIC
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 进行操作
    // retain 或 copy value
    association.acquireValue();

    bool isFirstAssociation = false;
    {
        // manager 不是单例,调用构造函数创建 manager,内部进行了加锁
        AssociationsManager manager;
        // AssociationsHashMap 是单例,通过 AssociationsManager 获取 AssociationsHashMap
        // 它是在`map_images`的时候初始化。
        AssociationsHashMap &associations(manager.get());

        if (value) {//有值
            // 在全局关联对象哈希表查找或创建插入object对应的关联对象哈希表迭代器
            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 */
            // 获取`object`的`关联对象`哈希表
            auto &refs = refs_result.first->second;
            // 这时候的key就是成员变量的key,将 association 插入object的表中
            // 有值的情况下没有插入,没有值的情况下才插入
            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 {// 没有值,进行清空处理。
            // 找到object的关联对象哈希表的迭代器
            auto refs_it = associations.find(disguised);
            // 不是 end 标记
            if (refs_it != associations.end()) {
                // 获取object的关联对象哈希表
                auto &refs = refs_it->second;
                // 找到对应key的 association 的迭代器
                auto it = refs.find(key);
                // 不是 end 标记
                if (it != refs.end()) {
                    // it->second为表中的association
                    // 将表中value修改为nil,association修改为之前的值
                    association.swap(it->second);
                    // 擦除对应key的 association 的迭代器
                    refs.erase(it);
                    if (refs.size() == 0) {
                        // 如果`object`对应的关联对象哈希表空了,就擦除此表
                        associations.erase(refs_it);

                    }
                }
            }
        }
    // 出了作用域,会调用AssociationsManager的析构函数
    // 进行解锁
    }
    // 只在第一次标记对象的isa是否有关联对象
    if (isFirstAssociation)
        object->setHasAssociatedObjects();

    // 释放`association`中交换得来的旧值
    association.releaseHeldValue();
}
  • 对象和值都为空就直接返回。

  • 对象的类是否禁止关联对象。

  • 将对象统一包装成DisguisedPtr类型。

  • policyvalue统一包装成ObjcAssociation,并根据policy对值进行retaincopy操作。

  • AssociationsManager manager调用AssociationsManager类的构造函数创建manager,构造函数和析构函数内部只是进行了加锁和解锁的处理。manager不是单例(后文具体分析)。

  • AssociationsHashMap &associations(manager.get())通过manager获取AssociationsHashMap类的全局关联对象总哈希表associations。此表是一张单例表,在map_images流程初始化(后文具体分析)。

  • value有值的情况下:

    • 调用associations.try_emplace函数在全局关联对象总哈希表查找或创建插入object对应的关联对象哈希表的迭代器,返回迭代器与是否第一次添加关联对象组成的pair,即refs_result
    • 如果当前object的关联对象哈希表第一次插入,refs_result.second就为true,将isFirstAssociation标记为true
    • refs_result.first->second获取object的关联对象哈希表refs
    • 调用refs.try_emplacepolicyvalue包装成的association插入当前object的关联对象哈希表,返回当前object的关联对象哈希表的迭代器和是否已经存在组成的pair,即result
    • 如果之前已经存在,result.second就为false,通过result.first->second获取之前的ObjcAssociation类对象,调用association.swap,将policyvalue的新值存储进去,旧值交换出来,为后续进行释放做准备。
  • value为空的情况下:

    • 调用associations.find函数在全局关联对象哈希表中查找object对应的关联对象哈希表的迭代器refs_it
    • 返回的迭代器不为end标记,则通过refs_it->second获取object对应的关联对象哈希表refs
    • 调用refs.findobject对应的关联对象哈希表中查找key对应的ObjcAssociation类的迭代器it
    • 返回的迭代器不为end标记,通过it->second获取之前的ObjcAssociation类对象,调用association.swap,将{policy, nil}存进去,旧值交换保存到association中,为后续进行释放做准备。
    • 调用refs.erase擦除object对应的关联对象哈希表中key对应的ObjcAssociation类的迭代器。
    • 如果object对应的关联对象哈希表空了,就擦除此表。
  • 根据isFirstAssociation,设置objectisa是否有关联对象。

  • 释放association中交换得来的旧值。

3.1.3: lldb查看disguisedassociation

image.png

3.1.4: acquireValue

    inline void acquireValue() {
        if (_value) {
            // 0xFF = 0B 1111 1111
            switch (_policy & 0xFF) {
            // 0B 0000 0001 & 0B 1111 1111 = 0B 0000 0001
            case OBJC_ASSOCIATION_SETTER_RETAIN:
                _value = objc_retain(_value);
                break;
            // 0B 0000 0011 & 0B 1111 1111 = 0B 0000 0011
            case OBJC_ASSOCIATION_SETTER_COPY:
                _value = ((id(*)(id, SEL))objc_msgSend)(_value, @selector(copy));
                break;
            }
        }
    }
  • 根据policy对值进行retaincopy操作。

3.1.5: AssociationsManagerAssociationsHashMap

// 对象的关联对象哈希表类型
typedef DenseMap<const void *, ObjcAssociation> ObjectAssociationMap;
// 关联对象总哈希表类型,两层哈希表结构,里面为 object, ObjectAssociationMap
typedef DenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap> AssociationsHashMap;

// class AssociationsManager manages a lock / hash table singleton pair.
// Allocating an instance acquires the lock

class AssociationsManager {
    using Storage = ExplicitInitDenseMap<DisguisedPtr<objc_object>, ObjectAssociationMap>;
    // 关联对象总哈希表
    // 静态变量,声明在AssociationsManager中,只能AssociationsManager类的实例调用
    // 相当于单例
    static Storage _mapStorage;

public:
    // 构造函数,加锁
    AssociationsManager()   { AssociationsManagerLock.lock(); }
    // 析构函数,解锁
    ~AssociationsManager()  { AssociationsManagerLock.unlock(); }
    
    // 获取关联对象总哈希表,全局单例
    // _mapStorage是静态变量,相当于单例
    AssociationsHashMap &get() {
        return _mapStorage.get();
    }
    // static表示类方法,在map_images流程调用
    static void init() {
        _mapStorage.init();
    }
};

AssociationsManager::Storage AssociationsManager::_mapStorage;
  • _mapStorage是一个静态变量,声明在AssociationsManager类中,只能AssociationsManager类的实例调用,相当于单例。
  • AssociationsManager构造函数和析构函数进行加解锁操作。
  • AssociationsHashMap类的关联对象总哈希表,通过AssociationsManager类的get方法获取。
  • 关联对象总哈希表是在AssociationsManager类的类方法init中初始化的,initmap_images流程调用。

3.1.5.1: AssociationsManager构造、析构仿照验证

image.png
  • 出了作用域就调用了析构函数。

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

image.png

3.1.5.2: AssociationsHashMap初始化流程

AssociationsManager类的init函数加上断点,查看函数调用栈:

image.png

关联对象总哈希表初始化流程:

  • map_images -> map_images_nolock -> arr_init -> _objc_associations_init -> AssociationsManager::init() -> _mapStorage.init()

map_images

image.png

map_images_nolock

image.png

arr_init

image.png

_objc_associations_init

image.png

AssociationsManager::init()

image.png

3.1.5.3: AssociationsHashMap单例验证

实例化多个实例,lldb输出地址验证。

image.png
  • AssociationsManager不是单例,AssociationsHashMap才是单例(全局关联对象总哈希表,所有对象的关联对象哈希表都存在里面)。

验证需要暂时注释AssociationsManager类构造和析构函数里的加解锁操作,否则会造成死锁:

image.png

3.1.6: try_emplace

// 外层哈希表 Key = disguised(object)   Args = ObjectAssociationMap{}
// 内存哈希表 key = key                 Args = association
  template <typename... Ts>
  std::pair<iterator, bool> try_emplace(const KeyT &Key, Ts &&... Args) {
    BucketT *TheBucket;
    // 根据key去查找对应的Bucket
    // TheBucket指针传递,会随着里面的值改变而改变
    if (LookupBucketFor(Key, TheBucket)) // 查询到,已经存在
      // 通过make_pair生成相应的pair
      // second 为 false
      return std::make_pair(
               makeIterator(TheBucket, getBucketsEnd(), true),
               false); // Already in map.

    // 如果没有查询到 将数据插入bucket中,返回bucket
    // TheBucket的first为key,second为Args
    // 所以外面可以通过 xxx.first->second 来获取 Args
    TheBucket = InsertIntoBucket(TheBucket, Key, std::forward<Ts>(Args)...);
    // 通过make_pair生成相应的pair
    // second 为 true
    return std::make_pair(
             makeIterator(TheBucket, getBucketsEnd(), true),
             true);
  }
  • try_emplacevalue有值的情况下会调用两次(关联对象总哈希表是双层哈希表结构):
    • 第一次是全局关联对象总哈希表调用,keyobject包装成的DisguisedPtr类对象,ArgsDisguisedPtr类对象对应的关联对象哈希表。
    • 第二次是DisguisedPtr类对象对应的关联对象哈希表调用,key是关联对象的keyArgs是{policy, value}包装成的ObjcAssociation类对象。
  • 调用LookupBucketFor查找key对应的TheBucket,第一次为对象对应的关联对象哈希表,第二次为关联对象key对应的ObjcAssociation类对象(都进行了相应的包装)。
  • 找到则生成迭代器与false组队(make_pair)返回。
  • 没找到就调用InsertIntoBucketkey & Args插入TheBucket,并生成迭代器与true组队返回(有旧值的情况下,不替换直接返回,以便后面交换之后释放旧值)。

全局关联对象总哈希表调用try_emplacelldb调试(第一次):

image.png

image.png

object包装成的DisguisedPtr类对象对应的关联对象哈希表调用try_emplacelldb调试(第二次):

image.png

image.png

3.1.6.1: LookupBucketFor

LookupBucketFor函数在源码里有两个,BucketT参数不同,一个有const修饰,一个没有。try_emplace传入的BucketT类型的TheBucket是没有const修饰的,所以我们先查看它:

image.png

很明显BucketT类型不带const的函数调用了带const的函数,BucketT类型参数同样是指针传递。

  • 根据key获取bucket地址存到ConstFoundBucket指针中。
  • ConstFoundBucket指针的地址赋值给&FoundBucket指针中,这样FoundBucket存放的数据就会实时更新。
  • 查询到Result返回true

接下来查看BucketT类型参数带constLookupBucketFor

  template<typename LookupKeyT>
  // BucketT 被 const 修饰了
  bool LookupBucketFor(const LookupKeyT &Val,
                       const BucketT *&FoundBucket) const {
    // 获取buckets的首地址
    const BucketT *BucketsPtr = getBuckets();
    // 获取buckets数量
    const unsigned NumBuckets = getNumBuckets();
    
    // 如果buckets数量为0,返回false,FoundBucket = nullptr
    if (NumBuckets == 0) {
      FoundBucket = nullptr;
      return false;
    }

    // FoundTombstone - Keep track of whether we find a tombstone while probing.
    const BucketT *FoundTombstone = nullptr;
    // 获取空bucket的key
    const KeyT EmptyKey = getEmptyKey();
    // 获取墓碑key
    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) {
      // 内存平移,ThisBucket = 首地址 + 第几个
      // 根据下标找到对应的 bucket
      const BucketT *ThisBucket = BucketsPtr + BucketNo;
      // Found Val's bucket?  If so, return it.
      // LLVM_LIKELY 就是 fastpath
      // 找到了bucket,也就是bucket已经存在了。
      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.
      // 没有查询到,就获取一个空的bucket,目的是可以向空的bucket插入数据
      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++;
      //再哈希,计算下标
      BucketNo &= (NumBuckets-1);
    }
  }
  • 此处逻辑与方法缓存的cache很像,首先key的哈希值 & (容量 - 1 )计算哈希下标。
  • 然后根据哈希下标查找bucket,有值就回传bucket指针,并返回true,没有就回传空的bucket墓碑标记,并返回false
  • 哈希冲突就再哈希,重新计算哈希下标。

lldb调试验证:

第一次查找因为没有数据,设置FoundBucket = nullptr,并返回false,然后走InsertIntoBucket流程。

image.png

第二次,在初次开辟空间或者扩容之后,获取空的bucket返回准备插入数据:

image.png

3.1.6.2: InsertIntoBucket

没找到相应的bucket,就会进入插入流程。

  Btemplate <typename KeyArg, typename... ValueArgs>
  BucketT *InsertIntoBucket(BucketT *TheBucket, KeyArg &&Key,
                            ValueArgs &&... Values) {
    // 获取空的`bucket`
    TheBucket =  InsertIntoBucketImpl(Key, Key, TheBucket);
    // TheBucket的first为Key,second为Values 
    // 所以外面可以通过 xxx.first->second 来获取 Values
    // values 第一次为对象对应的关联对象哈希表,
    // 第二次为{policy, value}包装成的`ObjcAssociation`类对象
    TheBucket->getFirst() = std::forward<KeyArg>(Key);
    ::new (&TheBucket->getSecond()) ValueT(std::forward<ValueArgs>(Values)...);
    return TheBucket;
  }
  • 获取空的bucket,容量不够的话就进行扩容。
  • 设置bucket对应的值,firstKeysecondValues
    • 第一次Keyobject包装成的DisguisedPtr类对象,ValuesDisguisedPtr类对象对应的关联对象哈希表。
    • 第二次Key是关联对象的keyValues是{policy, value}包装成的ObjcAssociation类对象。

lldb调试验证:

first赋值:

image.png

second赋值:

image.png
3.1.6.2.1: InsertIntoBucketImpl
  template <typename LookupKeyT>
  BucketT *InsertIntoBucketImpl(const KeyT &Key, const LookupKeyT &Lookup,
                                BucketT *TheBucket) {
    // NewNumEntries 表示将要插入一个 bucket
    unsigned NewNumEntries = getNumEntries() + 1;
    //获取bucket总个数
    unsigned NumBuckets = getNumBuckets();
    //如果当前要插入的个数 大于等于总个数的3/4 进行两倍扩容
    if (LLVM_UNLIKELY(NewNumEntries * 4 >= NumBuckets * 3)) {
      this->grow(NumBuckets * 2);// 进行两倍扩容,但是如果NumBuckets = 0 默认是开辟4个buckeet
      // 查找,实际是获取一个空的bucket
      LookupBucketFor(Lookup, TheBucket);
      NumBuckets = getNumBuckets();
    } else if (LLVM_UNLIKELY(NumBuckets-(NewNumEntries+getNumTombstones()) <=
                             NumBuckets/8)) {
      // 加上墓碑标记的bucket达到7/8时,重新开辟,将非墓碑标记换到新的bucket中
      this->grow(NumBuckets);
      LookupBucketFor(Lookup, TheBucket);
    }
    ASSERT(TheBucket);

    if (KeyInfoT::isEqual(TheBucket->getFirst(), getEmptyKey())) {
      // Replacing an empty bucket.
      // 找到一个empty bucket,被占用数量+1,返回这个bucket
      // 当前bucket被占用的数量 + 1
      incrementNumEntries();
      
    } else if (KeyInfoT::isEqual(TheBucket->getFirst(), getTombstoneKey())) {
      
      // Replacing a tombstone.
      // 找到一个墓碑标记的bucket,被占用数量+1,墓碑标记数量-1,返回这个bucket
      incrementNumEntries();
      decrementNumTombstones();
    } else {
      // we should be purging a zero. No accounting changes.
      ASSERT(ValueInfoT::isPurgeable(TheBucket->getSecond()));
      TheBucket->getSecond().~ValueT();
    }
    return TheBucket;
  }
  • 容量不够的情况下,调用grow函数进行开辟或扩容,首次开辟容量为4,扩容分为两种情况:
    • 负载因子达到3/4时,进行倍扩容。
    • 已被占用加上墓碑标记数量为7/8时,重新开辟空间,将非墓碑标记换到新的bucket中。
  • 调用LookupBucketFor查找bucket,获取到empty bucket情况下,被占用数量+1,返回这个bucket墓碑标记的bucket情况下,被占用数量+1,墓碑标记数量-1,返回这个bucket

lldb调试验证:

image.png
3.1.6.2.2: grow

空间不足的情况下,开辟空间和扩容会进入grow函数。

首先进入DenseMapBase类的grow函数。

void grow(unsigned AtLeast) {
    static_cast<DerivedT *>(this)->grow(AtLeast);
}

调试继续会进入DenseMap类的grow函数。

  void grow(unsigned AtLeast) {
    unsigned OldNumBuckets = NumBuckets;
    BucketT *OldBuckets = Buckets;
    // #define MIN_BUCKETS 4
    allocateBuckets(std::max<unsigned>(MIN_BUCKETS, static_cast<unsigned>(NextPowerOf2(AtLeast-1))));
    ASSERT(Buckets);
    if (!OldBuckets) {
      this->BaseT::initEmpty();
      return;
    }

    this->moveFromOldBuckets(OldBuckets, OldBuckets+OldNumBuckets);

    // Free the old table.
    operator delete(OldBuckets);
  } 
  • 通过max函数取MIN_BUCKETSNextPowerOf2(AtLeast-1)的最大值作为bucket的个数。MIN_BUCKETS4,首次开辟就是4
  • 调用allocateBuckets函数开辟上一步计算出的个数的bucket类型的内存空间。
  • OldBuckets为空,就遍历将bucket都初始化为空bucketempty bucketfirst1),然后返回。
  • 调用moveFromOldBuckets函数将旧的buckets移动到新的buckets中,这点和cache扩容是不一样。
  • 释放旧的buckets
3.1.6.2.3: NextPowerOf2
// 32位
/// NextPowerOf2 - 返回 2 的下一个幂(32 位)
/// 严格大于 A。溢出时返回零。
inline uint32_t NextPowerOf2(uint32_t A) {
  A |= (A >> 1);
  A |= (A >> 2);
  A |= (A >> 4);
  A |= (A >> 8);
  A |= (A >> 16);
  return A + 1;
}
// 64位
/// NextPowerOf2 - 返回 2 的下一个幂(64 位)
/// 严格大于 A。溢出时返回零。
inline uint64_t NextPowerOf2(uint64_t A) {
  A |= (A >> 1);
  A |= (A >> 2);
  A |= (A >> 4);
  A |= (A >> 8);
  A |= (A >> 16);
  A |= (A >> 32);
  return A + 1;
}
  • A是无符号整型,首次传进来是-1-1转换成1的二进制的规则是:1的原码是0b0000000000000001,取反是0b1111111111111110,再加10b1111111111111111(为了不那么长,都只以16位表示),再进行A |= (A >> n)运算,其实最后还是等于0b1111111111111111,然后A+1溢出等于0
  • 第二次传进来的是7(因为第一次开辟为4),二进制为0b0000000000000111,进行A |= (A >> n)运算,最好还是0b0000000000000111,然后A+1等于8

lldb调试验证:

image.png

image.png
3.1.6.2.4: initEmpty

调试继续进入initEmpty

 void initEmpty() {
    setNumEntries(0);
    setNumTombstones(0);

    ASSERT((getNumBuckets() & (getNumBuckets()-1)) == 0 &&
           "# initial buckets must be a power of two!");
    // 设置空的key
    const KeyT EmptyKey = getEmptyKey();
    for (BucketT *B = getBuckets(), *E = getBucketsEnd(); B != E; ++B)
      ::new (&B->getFirst()) KeyT(EmptyKey);
  }

继续调试进入getEmptyKey

首先进入llvm-DenseMap.hgetEmptyKey

  static const KeyT getEmptyKey() {
    static_assert(std::is_base_of<DenseMapBase, DerivedT>::value,
                  "Must pass the derived type to this template!");
    return KeyInfoT::getEmptyKey();
  }

继续调试进入llvm-DenseMapInfo.hstruct DenseMapInfo里的getEmptyKey

  static inline DisguisedPtr<T> getEmptyKey() {
    return DisguisedPtr<T>((T*)(uintptr_t)-1);
  }
  • DisguisedPtr大家应该很熟悉,object也是封装成DisguisedPtr类型。
   template** <typename T>
   class DisguisedPtr {
    uintptr_t value;
    static uintptr_t disguise(T* ptr) {
        return -(uintptr_t)ptr;
    }
    ...
}
  • ptr = (uintptr_t)-1,再次经过-(uintptr_t)ptr计算。就等于-(uintptr_t)((uintptr_t)-1) = 1,也就意味着空的bucketfirst1
3.1.6.2.5: moveFromOldBuckets
void moveFromOldBuckets(BucketT *OldBucketsBegin, BucketT *OldBucketsEnd) {
    initEmpty();

    // Insert all the old elements.
    const KeyT EmptyKey = getEmptyKey();
    const KeyT TombstoneKey = getTombstoneKey();
    for (BucketT *B = OldBucketsBegin, *E = OldBucketsEnd; B != E; ++B) {
      if (ValueInfoT::isPurgeable(B->getSecond())) {
        // Free the value.
        B->getSecond().~ValueT();
      } else if (!KeyInfoT::isEqual(B->getFirst(), EmptyKey) &&
          !KeyInfoT::isEqual(B->getFirst(), TombstoneKey)) {
        // Insert the key/value into the new table.
        BucketT *DestBucket;
        bool FoundVal = LookupBucketFor(B->getFirst(), DestBucket);
        (void)FoundVal; // silence warning.
        ASSERT(!FoundVal && "Key already in new map?");
        // bucket的first 非EmptyKey && 非墓碑(TombstoneKey),就插入新的bucket
        DestBucket->getFirst() = std::move(B->getFirst());
        ::new (&DestBucket->getSecond()) ValueT(std::move(B->getSecond()));
        // 已占用数量+1
        incrementNumEntries();

        // Free the value.
        B->getSecond().~ValueT();
      }
      B->getFirst().~KeyT();
    }
  }
  • 将新bucketsfirst都初始化为EmptyKey

  • 遍历OldBuckets中的OldBucket

    • OldBucketsecondfree:

      • 直接free
    • 不可free

      • OldBucketfirst不为EmptyKey,并且不为TombstoneKey,就插入到新的buckets中。
      • 已占用数量+1
      • free OldBucketsecond
  • free OldBucketfirst

3.1.7 setHasAssociatedObjects

associations.try_emplace(disguised, ObjectAssociationMap{})全局关联对象总哈希表associations调用try_emplace函数首次给object包装成的DisguisedPtr类型对象添加对应的关联对象哈希表时,组队(make_pair)返回了添加的对应的关联对象哈希表和true。此时isFirstAssociation就会被设置为true。后面就会标记对象是否有关联对象。

  ...
  if (isFirstAssociation)
        object->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函数,系统会判断是否实现了此函数。
  • isa非纯指针将has_assoc设置为true

_noteAssociatedObjects函数系统并没有实现,应该是提供给我们实现的。但是以_开头应该是私有方法。一般情况下我们是用不到的。

3.1.8: 关联对象设值流程

  1. object包装成DisguisedPtr类对象。

  2. 用策略和值生成ObjcAssociation类对象,并根据策略对ObjcAssociation中的值进行处理(retaincopy)。

  3. 根据AssociationsManager类拿到全局关联对象总哈希表AssociationsHashMap

  4. 判断需要设置的关联对象的值释放存在:

    • 4.1. 存在,走插入流程
      • 获取DisguisedPtr对应的关联对象哈希表ObjectAssociationMap,不存在就插入空的ObjectAssociationMap,并标记对象首次添加关联对象(isFirstAssociation)。
      • 查询ObjectAssociationMap中是否有key对应的ObjcAssociation,没有就插入,有就返回,跟2中的ObjcAssociation交互数据,第6步释放。
    • 4.2. 不存在,插入空值(插入空值,相当于清除)
      • 获取DisguisedPtr对应的关联对象哈希表ObjectAssociationMap
      • 获取key对应的ObjcAssociation
      • 将空值交换进去(即清除),然后擦除ObjcAssociation
      • 如果DisguisedPtr对应的ObjectAssociationMap空了,就擦除。
  5. 根据isFirstAssociation设置对象是否存在关联对象。

  6. 释放4.1交换出来的旧值。

3.2: 关联对象getter

关联对象取值调用的是objc_getAssociatedObject函数。

id
objc_getAssociatedObject(id object, const void *key)
{
    return _object_get_associative_reference(object, key);
}
  • 直接调用了_object_get_associative_reference函数。

3.2.1: _object_get_associative_reference

id
_object_get_associative_reference(id object, const void *key)
{   
    // 创建空的ObjcAssociation类型对象
    ObjcAssociation association{};

    {   
    // AssociationsManager manager调用构造函数加锁
        AssociationsManager manager;
        // 获取全局关联对象总哈希表
        AssociationsHashMap &associations(manager.get());
        // 获取对象对应的关联对象哈希表的迭代器
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        // 不是 end 标记
        if (i != associations.end()) {
            // 获取对象对应的关联对象哈希表
            ObjectAssociationMap &refs = i->second;
            // 获取关联对象`key`对应的`ObjcAssociation`类对象的迭代器
            ObjectAssociationMap::iterator j = refs.find(key);
            // 不是 end 标记
            if (j != refs.end()) {
                // 获取 ObjcAssociation类型对象
                association = j->second; 
                // 根据策略需要retain value
                association.retainReturnedValue();
            }
        }
    // 出了作用域,会调用AssociationsManager的析构函数 
    // 进行解锁
    }
    // 根据策略需要将value设为autorelease
    return association.autoreleaseReturnedValue();
}
  • 获取全局关联对象总哈希表associations
  • 包装objectDisguisedPtr类型对象从associations获取对象对应的关联对象哈希表的迭代器。
  • 通过key获取关联对象key对应的ObjcAssociation类对象的迭代器。
  • ObjcAssociation类型对象中获取value返回。

3.2.2: find

iterator find(const_arg_type_t<KeyT> Val) {
    BucketT *TheBucket;
    if (LookupBucketFor(Val, TheBucket))
      return makeIterator(TheBucket, getBucketsEnd(), true);
    return end();
  }
  • 调用LookupBucketFor函数查找TheBucket,如果存在就返回相关迭代器,不存在就返回end标记。

lldb调试查看:

image.png

3.2.3: 关联对象取值流程

  1. 根据AssociationsManager类拿到全局关联对象总哈希表AssociationsHashMap
  2. 根据当前object封装的DisguisedPtrAssociationsHashMap中获取对应的迭代器。
  3. 此迭代器不是end标记,取出对象对应的关联对象哈希表ObjectAssociationMap
  4. 根据keyObjectAssociationMap中获取对应的迭代器。
  5. 此迭代器不是end标记,取出对应的ObjcAssociation{policy, value})。
  6. 根据策略需要处理ObjcAssociation中的value之后返回。

3.3: 关联对象remove

苹果提供objc_removeAssociatedObjects函数以供我们移除关联对象。

void objc_removeAssociatedObjects(id object) 
{
    if (object && object->hasAssociatedObjects()) {
        _object_remove_assocations(object, /*deallocating*/false);
    }
}
  • 判断object是否有关联对象,如果有,就调用_object_remove_assocations函数移除关联对象。

3.3.1: hasAssociatedObjects

判断对象是否有关联对象:

inline bool
objc_object::hasAssociatedObjects()
{
    if (isTaggedPointer()) return true;
    if (isa.nonpointer) return isa.has_assoc;
    return true;
}
  • 默认返回true

3.3.2: _object_remove_assocations

void
_object_remove_assocations(id object, bool deallocating)
{
    ObjectAssociationMap refs{};

    {
    // AssociationsManager manager 调用AssociationsManager构造函数,加锁
        AssociationsManager manager;
        // 获取全局关联对象总哈希表
        AssociationsHashMap &associations(manager.get());
        // 获取对象对应的关联对象哈希表的迭代器
        AssociationsHashMap::iterator i = associations.find((objc_object *)object);
        // 不是 end 标记
        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) { // 对象非释放的情况下
                // 遍历对象对应的关联对象哈希表中所有的`ObjcAssociation`类对象的迭代器
                for (auto &ref: refs) {
                    // ref.second是ObjcAssociation类型对象{policy, value}
                    if (ref.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
                        // 重新将OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象插入
                        i->second.insert(ref);
                        didReInsert = true;
                    }
                }
            }
            if (!didReInsert)
                // 没有需要重新插入的,则擦除对象对应的关联对象哈希表的迭代器
                associations.erase(i);
        }
    }

    // Associations to be released after the normal ones.
    SmallVector<ObjcAssociation *, 4> laterRefs;

    // release everything (outside of the lock).
    // 遍历对象对应的关联对象哈希表中所有的`ObjcAssociation`类对象的迭代器
    for (auto &i: refs) {
        // i.second是ObjcAssociation类型对象{policy, value}
        if (i.second.policy() & OBJC_ASSOCIATION_SYSTEM_OBJECT) {
            // If we are not deallocating, then RELEASE_LATER associations don't get released.
            if (deallocating)
                //dealloc的时候,OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象,先放入laterRefs 稍后释放,否则不处理。
                laterRefs.append(&i.second);
        } else {
            //释放非OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象
            i.second.releaseHeldValue();
        }
    // 出了作用域,调用AssociationsManager析构函数,解锁
    }
    for (auto *later: laterRefs) {
        // dealloc 的情况下释放OBJC_ASSOCIATION_SYSTEM_OBJECT的关联对象
        later->releaseHeldValue();
    }
}
  • 根据第二个参数deallocating判断是不是dealloc的时候调用的。
  • 创建一个临时的ObjectAssociationMap对象refs
  • 获取全局关联对象总哈希表associations
  • 获取对象对应的关联对象哈希表(ObjectAssociationMap)的迭代器I
  • 将对象对应ObjectAssociationMap数据交互到refs中,原来的i置空。
  • deallocating情况下(也就是自己调用),将系统的关联对象(通过policy & OBJC_ASSOCIATION_SYSTEM_OBJECT判断)重新插入i,并标记didReInserttrue
  • 没有需要重新插入的情况下,则擦除对象对应ObjectAssociationMap的迭代器i
  • 创建laterRefs,记录dealloc情况下需要是否的系统ObjcAssociation
  • 循环refs非系统的关联对象直接释放,系统的关联对象判断是否deallocatingdeallocating的情况下加入laterRefs
  • 循环释放laterRefs里的ObjcAssociation,也就是系统的关联对象(deallocating的情况下才有值)。

3.3.3: 关联对象移除流程

  1. 创建一个临时的ObjectAssociationMap对象refs
    1. 根据AssociationsManager类拿到全局关联对象总哈希表AssociationsHashMap
  2. 根据当前object封装的DisguisedPtrAssociationsHashMap中获取对应的迭代器。
  3. 此迭代器不是end标记,将对象对应ObjectAssociationMap数据交互到refs中,原来迭代器置空,用于后续存放需要重新插入的系统关联对象。
  4. deallocating情况下,循环获取ObjcAssociation,将系统的关联对象重新插入,没有需要重新插入的,就擦除被置空的迭代器。
  5. 循环获取ObjcAssociation,如果是系统关联对象,deallocating情况下就加入临时Vector中,非系统的就直接释放。
  6. 如果系统关联对象Vector有数据,就遍历释放(系统的关联对象在deallocating情况下才释放)。

四: dealloc

实际开发过程中,我们一般不会主动调用移除关联对象的API,那么对象想要释放,必然需要移除关联对象,下面就查看下dealloc流程,看看里面是否有相应的操作。

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

调用流程dealloc -> _objc_rootDealloc -> obj->rootDeallocrootDealloc函数里就有关联对象相关流程。

4.1: rootDealloc

inline void
objc_object::rootDealloc()
{
    // Tagged Pointer
    if (isTaggedPointer()) return;  // fixme necessary?
    
    // isa非纯指针
    // 没有弱引用
    // 没有关联对象
    // 没有c++析构函数
    // 没有引用计数表
    // 同时满足这5个条件直接free,否则调用object_dispose
    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++析构函数 && 没有引用计数表,同时满足这5个条件直接free,否则调用object_dispose函数。

4.2: object_dispose

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

    return nil;
}
  • 调用objc_destructInstance函数破坏对象,然后free

很显然移除关联对象的核心逻辑就在objc_destructInstance函数中。

4.3: 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);
        // 清除`object`对应的弱引用表和引用计数表
        obj->clearDeallocating();
    }

    return obj;
}
  • 如果有c++析构函数,调用。
  • 如果有关联对象,移除。
  • 清除object对应的弱引用表和引用计数表。

4.4: clearDeallocating

inline void 
objc_object::clearDeallocating()
{
    // 两个不调用通一个方法的原因是 
    // 纯指针需要 SIDE_TABLE_WEAKLY_REFERENCED 判断是否有弱引用,
    // nonpointer 可以通过isa字段判断
    if (slowpath(!isa.nonpointer)) {//纯指针
        // Slow path for raw pointer 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());
}
  • 根据isa是否为纯指针分为两个逻辑,纯指针调用sidetable_clearDeallocating函数,非纯指针调用clearDeallocating_slow函数。

4.4.1: sidetable_clearDeallocatingclearDeallocating_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);
    // 不为 end 标记
    if (it != table.refcnts.end()) {
        // 判断是否有弱引用
        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判断是否有引用计数表,有则擦除。

4.4.2: 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
    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从弱引用表中删除。

4.5: dealloc调用流程

  • 能直接释放的情况(isa非纯指针 && 没有弱引用 && 没有关联对象 && 没有c++析构函数 && 没有引用计数表):
    • 直接free
  • 不能直接释放的情况:
    • 如果有c++析构函数,调用.
    • 如果有关联对象,移除。
    • 清空弱引用表(遍历弱引用指针数组,全部置为nil)。
    • 从引用计数表中擦除自己的数据。
    • free

已知在ARC模式下dealloc方法不再需要显式调用[super dealloc]了,分析dealloc流程时在objc源码里也并没有看到[super dealloc]的调用(objc部分已经是运行时了),那么相关处理肯定在编译时了。

使用hopper反汇编查看伪代码:

image.png
  • 编译时就已经添加了[super dealloc]相关处理。

代码编译相关内容,以后有机会单独发文分析。

五: 关联对象实现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属性的关联对象,但是weakassign是有相关差别的:

weak:修饰OBJC对象,不会持有指向修饰对象,同样指向的对象引用计数就不会增加,当指向的对象被释放的时候,weak修饰的对象会被置为nil

因为堆内存是动态的,所以当某个地址的对象被释放的时候,所有指向他的指针都应该被置为空。weak就是为了满足避免循环引用,同时在对象被释放的时候可以被置为空的情况而存在的。

assign:修饰栈内存中的基本数据类型,当使用assign修饰了一个OBJC对象的时候,当指向的对象被释放的时候,assign修饰的对象不会主动被置为nil,可能造成野指针。

为了避免野指针的情况出现,只能另辟蹊径:强引用一个中间者,让中间者持有这个weak属性。主流的实现方案就是使用block:强引用blockblock持有weak属性。

@property (nonatomic, weak) id weak_obj;

- (void)setWeak_obj:(id)weak_obj {
    id __weak weakObject = weak_obj;
    // 声明实现block,执行block就会返回weakObject
    id (^block)(void) = ^{ return weakObject; };
    // 存储block,copy策略
    return objc_setAssociatedObject(self, "weak_obj", block, OBJC_ASSOCIATION_COPY);
}

- (id)weak_obj {
    // 获取block
    id (^block)(void) = objc_getAssociatedObject(self, "weak_obj");
    // 执行block,取到block返回的weakObject
    id weakObject = (block ? block() : nil);
    return weakObject;
}

关联对象时仍然是强引用,但是block内部持有了属性的弱引用。在weak_obj释放后,block也就获取不到weak_obj了。

关联对象在什么情况下会造成内存泄漏?

关联对象可以理解就是持有了一个对象,如果是强持有,而该对象也强持有了本类,那就会导致循环引用。

总结

关联对象的存储结构是两层哈希map,存、取、移除都需要进行两层处理。

关联对象两层哈希结构示意图

关联对象哈希结构.jpg

关联对象设值流程示意图

关联对象设值流程.jpg

关联对象取值流程示意图

关联对象取值流程.jpg

关联对象移除流程示意图

关联对象移除流程.jpg
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容