OC对象之旅 weak弱引用实现分析

2024-04-21 09:36:01

Runtime源码分析带你了解OC实现过程。其中参考了大量的大神的代码以及文献里面也有个人的见解欢迎拍砖欢迎交流。

两种常见使用场景

/// weak属性@interface XX : XX@property(nonatomic,weak) Type* weakPtr;@end/// 代码块中使用{    /// 使用__weak__weak Type* weakPtr = [[SomeObject alloc] init];
}

根据调试信息发现两者的区别是

  • 第一种进入到 id objc_storeWeak(id *location, id newObj)方法
    ```
    /**

  • This function stores a new value into a __weak variable. It would

  • be used anywhere a __weak variable is the target of an assignment.

  • @param location The address of the weak pointer itself

  • @param newObj The new object this weak ptr should now point to

  • @return \e newObj
    /
    id
    objc_storeWeak(id 
    location, id newObj)
    {
    return storeWeak

    (location, (objc_object *)newObj);
    }
    ```,>

  • 第二种绕一个远路先初始化 id objc_initWeak(id *location, id newObj)
    ``` Objective-C
    /**

  • Initialize a fresh weak pointer to some object location.

  • It would be used for code like:

  • (The nil case)

  • __weak id weakPtr;

  • (The non-nil case)

  • NSObject *o = ...;

  • __weak id weakPtr = o;

  • This function IS NOT thread-safe with respect to concurrent

  • modifications to the weak variable. (Concurrent weak clear is safe.)

  • @param location Address of __weak ptr.

  • @param newObj Object ptr.
    /
    id objc_initWeak(id 
    location, id newObj)
    {
    if (!newObj) {
    *location = nil;
    return nil;
    }

    return storeWeak

    (location, (objc_object*)newObj);
    }
    ```,>

  • 两者最终进入到如下方法

template <HaveOld haveOld, HaveNew haveNew,CrashIfDeallocating crashIfDeallocating>static idstoreWeak(id *location, objc_object *newObj)
{    ///略去下面会进行分析 ...    return (id)newObj;
}

所以重点就在 storeWeak这个方法中let's do it

分析源码

storeWeak源码的如下

template <HaveOld haveOld, HaveNew haveNew,CrashIfDeallocating crashIfDeallocating>static id storeWeak(id *location, objc_object *newObj)
{assert(haveOld  ||  haveNew);    if (!haveNew) assert(newObj == nil);Class previouslyInitializedClass = nil;    id oldObj;SideTable *oldTable;SideTable *newTable;    // Acquire locks for old and new values.// Order by lock address to prevent lock ordering problems. // Retry if the old value changes underneath us.retry:    if (haveOld) {oldObj = *location;oldTable = &SideTables()[oldObj];} else {oldTable = nil;}    if (haveNew) {newTable = &SideTables()[newObj];} else {newTable = nil;}SideTable::lockTwo<haveOld, haveNew>(oldTable, newTable);    if (haveOld  &&  *location != oldObj) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);        goto retry;}    // Prevent a deadlock between the weak reference machinery// and the +initialize machinery by ensuring that no // weakly-referenced object has an un-+initialized isa./// 注释大意是通过下面操作保证所有的弱引用对象的isa都被初始化这样可以防止死锁PS,这里我不是太明白求指教if (haveNew  &&  newObj) {        /// 下面的操作是初始化isaClass cls = newObj->getIsa();        if (cls != previouslyInitializedClass  &&  !((objc_class *)cls)->isInitialized()) {SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);_class_initialize(_class_getNonMetaClass(cls, (id)newObj));            // If this class is finished with +initialize then we're good.// If this class is still running +initialize on this thread // (i.e. +initialize called storeWeak on an instance of itself)// then we may proceed but it will appear initializing and // not yet initialized to the check above.// Instead set previouslyInitializedClass to recognize it on retry.previouslyInitializedClass = cls;            goto retry;}}    // Clean up old value, if any.if (haveOld) {weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);}    // Assign new value, if any.if (haveNew) {newObj = (objc_object *)weak_register_no_lock(&newTable->weak_table, (id)newObj, location, crashIfDeallocating);        // weak_register_no_lock returns nil if weak store should be rejected// Set is-weakly-referenced bit in refcount table.if (newObj  &&  !newObj->isTaggedPointer()) {newObj->setWeaklyReferenced_nolock();}        // Do not set *location anywhere else. That would introduce a race.*location = (id)newObj;}    else {        // No new value. The storage is not changed.}SideTable::unlockTwo<haveOld, haveNew>(oldTable, newTable);    return (id)newObj;
}
  • template

    是C++的一种泛型实现相当于这里申明了变量或者类型可以在代码块中使用用于处理不同的未知类型&枚举。

  • haveOld 弱引用是否已经有所指向

  • haveNew 是否有新的指向

  • CrashIfDeallocating 执行方法时发生Deallocate是否Crash

PS:初始化ISA那部分为何能阻止死锁我没有看懂
该函数流程如下


重点来了

/// SideTablesoldTable = &SideTables()[oldObj];
newTable = &SideTables()[newObj];/// taggedPointer是什么鬼isTaggedPointer/// 注册弱引用weak_register_no_lock(&newTable->weak_table, (id)newObj, location,crashIfDeallocating);/// 消除弱引用weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);

SideTable

SideTable是一个结构体定义如下

struct SideTable {    spinlock_t slock;RefcountMap refcnts;    weak_table_t weak_table;SideTable() {        memset(&weak_table, 0, sizeof(weak_table));}~SideTable() {_objc_fatal("Do not delete SideTable.");}    ///锁....
};
  • spinlock_t solck 锁

  • RefcountMap refcnts 强引用使用略过

  • weak_table_t weak_table 弱引用表
    SideTable是存放引用关系的对象通过Hash值操作在SideTableBuf 中寻找与之对应的SideTableSideTableBuf初始化过程如下

    alignas(StripedMap<SideTable>) static uint8_t SideTableBuf[sizeof(StripedMap<SideTable>)];/// 会在Objc_init中调用该方法static void SideTableInit() {/// 这句话貌似没什么卵用求指教new (SideTableBuf) StripedMap<SideTable>();
    }/// 寻找SideTablestatic StripedMap<SideTable>& SideTables() {return *reinterpret_cast<StripedMap<SideTable>*>(SideTableBuf);
    }

    StripedMap是一个泛型类并重写了[]运算符通过对象的地址运算出Hash值通过该hash值找到对象的SideTable
    ```
    template
    class StripedMap {
    enum { CacheLineSize = 64 };

    if TARGET_OS_EMBEDDED

    enum { StripeCount = 8 };

    else

    enum { StripeCount = 64 };

    endif

    struct PaddedT {
    T value alignas(CacheLineSize);
    };
    PaddedT array[StripeCount];
    /// 运算
    static unsigned int indexForPointer(const void *p) {
    uintptr_t addr = reinterpret_cast(p);
    /// 位运算可以控制返回值在0-63之间
    return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
    }

public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
/// 下面略去
...
}

### taggedPointer简单的说这是一种优化手段即将对象的值存入对象的地址中这些工程师简直丧心病狂就为了省一点内存嘛### 进入正题看看怎么实现弱引用的先看看注册的过程吧

/**

  • Registers a new (object, weak pointer) pair. Creates a new weak

  • object entry if it does not exist.

  • @param weak_table The global weak table.

  • @param referent The object pointed to by the weak reference.

  • @param referrer The weak pointer address.
    /
    id weak_register_no_lock(weak_table_t 
    weak_table, id referent_id,
    id referrer_id, bool crashIfDeallocating)
    {
    /// 转化为object
    objc_object 
    referent = (objc_object *)referent_id;
    objc_object referrer = (objc_object )referrer_id;
    /// 如果是taggedPointer,就没有引用的过程了
    if (!referent || referent->isTaggedPointer()) return referent_id;

    // ensure that the referenced object is viable
    bool deallocating;
    if (!referent->ISA()->hasCustomRR()) {
    deallocating = referent->rootIsDeallocating();
    }
    else {
    BOOL (allowsWeakReference)(objc_object , SEL) =
    (BOOL()(objc_object , SEL))
    object_getMethodImplementation((id)referent,
    SEL_allowsWeakReference);
    if ((IMP)allowsWeakReference == _objc_msgForward) {
    return nil;
    }
    deallocating =
    ! (*allowsWeakReference)(referent, SEL_allowsWeakReference);
    }
    /// 如果正在被销毁
    if (deallocating) {
    if (crashIfDeallocating) {
    _objc_fatal("Cannot form weak reference to instance (%p) of "
    "class %s. It is possible that this object was "
    "over-released, or is in the process of deallocation.",
    (void*)referent, object_getClassName((id)referent));
    } else {
    return nil;
    }
    }

    // now remember it and where it is being stored
    weak_entry_t *entry;
    if ((entry = weak_entry_for_referent(weak_table, referent))) {
    append_referrer(entry, referrer);
    }
    else {
    weak_entry_t new_entry(referent, referrer);
    weak_grow_maybe(weak_table);
    weak_entry_insert(weak_table, &new_entry);
    }

    // Do not set *referrer. objc_storeWeak() requires that the
    // value not change.

    return referent_id;
    }
    ```
    先从这行数的参数说起参数有4个

  • weak_table_t *weak_table hash表

  • id referent_id, 弱引用对象

  • id *referrer_id, 弱引用指针

  • bool crashIfDeallocating 如果正在Deallocate是否crash

后三个参数不用解释主要解释第一个参数weak_table_t,定义如下

/*** The global weak references table. Stores object ids as keys,* and weak_entry_t structs as their values.*/struct weak_table_t {    weak_entry_t *weak_entries; ///数组用于存储引用对象集合size_t    num_entries;  /// 存储数目uintptr_t mask; /// 当前分配容量uintptr_t max_hash_displacement; /// 已使用容量};

没错weak_table_t就是寄存在SideTable

  • weak_entry_t *weak_entries; ///数组用于存储引用对象集合

  • size_t num_entries; /// 存储数目

  • uintptr_t mask; /// 当前分配容量

  • uintptr_t max_hash_displacement; /// 已使用容量

定义中我们重点关注weak_entry_t

struct weak_entry_t {DisguisedPtr<objc_object> referent;    union {        struct {            weak_referrer_t *referrers;            uintptr_t        out_of_line_ness : 2;            uintptr_t        num_refs : PTR_MINUS_2;            uintptr_t        mask;            uintptr_t        max_hash_displacement;};        struct {            // out_of_line_ness field is low bits of inline_referrers[1]weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];};};    bool out_of_line() {        return (out_of_line_ness == REFERRERS_OUT_OF_LINE);}    weak_entry_t& operator=(const weak_entry_t& other) {        memcpy(this, &other, sizeof(other));        return *this;}    weak_entry_t(objc_object *newReferent, objc_object **newReferrer): referent(newReferent){inline_referrers[0] = newReferrer;        for (int i = 1; i < WEAK_INLINE_COUNT; i++) {inline_referrers[i] = nil;}}
};

weak_entry_t是最终存放对象和引用指针的地方referent是被引用的对象联合体union释义如下

  • weak_referrer_t *referrers; 存放引用指针

  • uintptr_t out_of_line_ness : 2 标识当前存储是否在初始WEAK_INLINE_COUNT个数之内

  • uintptr_t num_refs : PTR_MINUS_2 引用的个数

  • uintptr_t mask; 实际分配容量

  • uintptr_t max_hash_displacement; 实际使用容量包括已经被释放的每次调整容量时会更新重置

  • weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]; 当引用个数小于WEAK_INLINE_COUNT时使用该数组存放。

注册引用过程中重点关注下面代码

{
weak_entry_t *entry;/// 查找是否已经注册过了if ((entry = weak_entry_for_referent(weak_table, referent))) {/// 加上去就可以了append_referrer(entry, referrer);} else {/// 新建一个weak_entry_t new_entry(referent, referrer);/// 调整weak_table_t 的容量大小weak_grow_maybe(weak_table);/// 插入一个weak_entry_insert(weak_table, &new_entry);}
}

新建

通过weak_entry_t的源码可以看到新建一个weak_entry_t的过程是

  • 将被引用对象赋予referent

  • 将引用指针放入到inline_referrers因为此时数目还很少

调整weak_table_t的容量大小

static void weak_resize(weak_table_t *weak_table, size_t new_size){    size_t old_size = TABLE_SIZE(weak_table);    weak_entry_t *old_entries = weak_table->weak_entries;    weak_entry_t *new_entries = (weak_entry_t *)        calloc(new_size, sizeof(weak_entry_t));weak_table->mask = new_size - 1;weak_table->weak_entries = new_entries;    /// 重置weak_table->max_hash_displacement = 0;weak_table->num_entries = 0;  // restored by weak_entry_insert belowif (old_entries) {        weak_entry_t *entry;        weak_entry_t *end = old_entries + old_size;        for (entry = old_entries; entry < end; entry++) {            if (entry->referent) {weak_entry_insert(weak_table, entry);}}        free(old_entries);}
}// Grow the given zone's table of weak references if it is full.static void weak_grow_maybe(weak_table_t *weak_table){    size_t old_size = TABLE_SIZE(weak_table);    // Grow if at least 3/4 full.if (weak_table->num_entries >= old_size * 3 / 4) {weak_resize(weak_table, old_size ? old_size*2 : 64);}
}

当实际的数目大于old_sizeold_size就是mask的大小+1)就去调整大小同时重置max_hash_displacement为0通过calloc函数动态分配mask个的内存然后通过循环将原有的weak_entry_t插入到新的容器中在插入的过程中更新max_hash_displacement.

weak_table_t插入weak_entry_t

static void weak_entry_insert(weak_table_t *weak_table, weak_entry_t *new_entry)
{weak_entry_t *weak_entries = weak_table->weak_entries;assert(weak_entries != nil);size_t begin = hash_pointer(new_entry->referent) & (weak_table->mask);size_t index = begin;size_t hash_displacement = 0;    while (weak_entries[index].referent != nil) {index = (index+1) & weak_table->mask;        if (index == begin) bad_weak_table(weak_entries);hash_displacement++;}    /// 把新的加进去weak_entries[index] = *new_entry;    /// 引用计数+1weak_table->num_entries++;    /// 扩容前最大占位if (hash_displacement > weak_table->max_hash_displacement) {weak_table->max_hash_displacement = hash_displacement;}
}

过程比较简单也是利用hash处理方便后面查找。

weak_table_t查找对象是通过循环遍历的方式过程如下

static weak_entry_t *
weak_entry_for_referent(weak_table_t *weak_table, objc_object *referent)
{assert(referent);weak_entry_t *weak_entries = weak_table->weak_entries;    if (!weak_entries) return nil;size_t begin = hash_pointer(referent) & weak_table->mask; /// 获取hash值size_t index = begin;size_t hash_displacement = 0;    /// 循环遍历查找while (weak_table->weak_entries[index].referent != referent) {index = (index+1) & weak_table->mask;        if (index == begin) bad_weak_table(weak_table->weak_entries);        // 查找到最大的时候结束hash_displacement++;        if (hash_displacement > weak_table->max_hash_displacement) {            return nil;}}    return &weak_table->weak_entries[index];
}

在已有的weak_entry_t中加入引用

static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{    /// 如果是数组,即个数比较少if (! entry->out_of_line()) {        // Try to insert inline.for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {            if (entry->inline_referrers[i] == nil) {entry->inline_referrers[i] = new_referrer;                return;}}        // Couldn't insert inline. Allocate out of line.weak_referrer_t *new_referrers = (weak_referrer_t *)calloc(WEAK_INLINE_COUNT, sizeof(weak_referrer_t));        // This constructed table is invalid, but grow_refs_and_insert// will fix it and rehash it.for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {new_referrers[i] = entry->inline_referrers[i];}entry->referrers = new_referrers;entry->num_refs = WEAK_INLINE_COUNT;entry->out_of_line_ness = REFERRERS_OUT_OF_LINE;entry->mask = WEAK_INLINE_COUNT-1;entry->max_hash_displacement = 0;}assert(entry->out_of_line());    if (entry->num_refs >= TABLE_SIZE(entry) * 3/4) {        return grow_refs_and_insert(entry, new_referrer);}size_t begin = w_hash_pointer(new_referrer) & (entry->mask);size_t index = begin;size_t hash_displacement = 0;    while (entry->referrers[index] != nil) {hash_displacement++;index = (index+1) & entry->mask;        if (index == begin) bad_weak_table(entry);}    if (hash_displacement > entry->max_hash_displacement) {entry->max_hash_displacement = hash_displacement;}weak_referrer_t &ref = entry->referrers[index];ref = new_referrer;entry->num_refs++;
}

该过程同在weak_table_t中插入weak_entry_t如出一辙要注意的是需要判断引用的个数当引用个数大于WEAK_INLINE_COUNT时需要将原有的引用指针也移到referrers中同时更新相关计数器。
上面过程的流程如下

转载于:https://blog.51cto.com/12953214/1940624

OC对象之旅 weak弱引用实现分析相关推荐

  1. __attribute__((weak)):弱引用,可以不实现

    #include <stdio.h>#define __weak __attribute__((weak)) //变量加上weak时,是弱符号.函数加上weak时,是弱引用,可以不实现. ...

  2. 理解Java中的弱引用(Weak Reference)

    理解Java中的弱引用(Weak Reference) 本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限, ...

  3. java 对象引用 弱引用吗_Java对象的强引用、软引用、弱引用和虚引用 笔记

    从JDK1.2版本开始,把对象的引用分为四种级别,从而使程序能更加灵活的控制对象的生命周期.这四种级别由高到低依次为:强引用.软引用.弱引用和虚引用. 1.强引用 使用最普遍的引用.如果一个对象具有强 ...

  4. 弱引用的用途:在底层C++对象被上层python脚本对象使用时(转)

    在使用python脚本和底层C++对象进行交互的过程中发生了一个问题:由于底层C++对象的创建和删除决定权由底层决定,当底层决定删除这些对象而上层仍然在"强引用"这些对象的时候,就 ...

  5. python对象回收_python 引用,拷贝,对象回收,弱引用

    引用 python中,在对对象赋值,参数传递,函数返回等等, 都是引用传递的. 直接copy个例子来[1]: a = [1, 2, 3] b = a b.append(5) print a, b 输出 ...

  6. java的弱引用_理解Java中的弱引用(Weak Reference)

    本篇文章尝试从What.Why.How这三个角度来探索Java中的弱引用,理解Java中弱引用的定义.基本使用场景和使用方法.由于个人水平有限,叙述中难免存在不准确或是不清晰的地方,希望大家可以指出, ...

  7. java 软引用_Java中弱引用和软引用的区别以及虚引用和强引用介绍

    知道弱引用和软引用的概念与如何使用它们是两码事,引用类在垃圾回收工作的过程中有重要作用.我们都知道垃圾回收器会回收符合回收条件的对象的内存,但并不是所有的程序员都知道回收条件取决于指向该对象的引用类型 ...

  8. java强引用、软引用、弱引用、虚引用-Java的引用类型总共有四种,你都知道吗

    目录 谈引用 强引用(Strong Reference)--不回收 强引用例子 软引用(Soft Reference)--内存不足即回收 弱引用(Weak Reference)--发现即回收 面试题: ...

  9. JVM学习笔记之-垃圾回收相关概念 System.gc()的理解 内存溢出与内存泄漏 STW 垃圾回收的并行与并发 安全点与安全区域 再谈引用:强引用 软引用 弱引用 虚引用 终结器引用

    System.gc()的理解 在默认情况下,通过System.gc()或者Runtime. getRuntime ( ).gc ()的调用,会显式触发Full GC,同时对老年代和新生代进行回收,尝试 ...

  10. .NET中常见的内存泄露问题——GC、委托事件和弱引用

    其实吧,内存泄露一直是个令人头疼的问题,在带有GC的语言中这个情况得到了很大的好转,但是仍然可能会有问题. 一.什么是内存泄露(memory leak)? 内存泄露不是指内存坏了,也不是指内存没插稳漏 ...

最新文章

  1. Atcoder AGC031C Differ By 1 Bit (构造、二进制)
  2. Android开发之无bug滑动删除源码(非第三方库)
  3. html js停止计时,用 js 写的计时器,暂停计时无法生效
  4. 在linux设置回收站 - 防止失误操作造成数据清空,并定期清理
  5. Oracle 网络配置与管理
  6. NoSQL Manager for MongoDB 破解
  7. Android GSYVideoPlayer视频播放器
  8. QQ机器人制作教程,超详细
  9. Microsoft DirectX 8 开发人员常见问题
  10. 5步教你成功求职进入BAT
  11. 华为如何关闭系统更新提示
  12. 计算机论文的参考文献,应该怎么引用? - 易智编译EaseEditing
  13. 基于Trie树进行拆分字符串变成拼音音节(二):字符串拼音拆分
  14. 五个温度带的分界线_初中地理知识点:我国的温度带
  15. 网络层协议 ——— IP协议
  16. java 格式化日期到毫秒_关于日期:Java – SimpleDateFormat格式化程序,以毫秒为单位返回纪元时间...
  17. Vue中的methods配置项中的箭头函数this指向及相关源码分析
  18. 计算机技术在家庭方面的应用,物联网技术在家庭方面的应用
  19. 简单测试:慧荣SM2259XT2+闪迪BICS5,速度喜人
  20. 从零开始的计算机学习

热门文章

  1. Python XML解析(转载)
  2. SQL处理非常见空串
  3. 一步步学习SPD2010--附录A--SPD工作流条件和操作(4)--列表操作
  4. 【2017级面向对象程序设计】作业二
  5. XCode 遇到的问题
  6. HDOJ-1875-畅通工程再续 解题报告
  7. ddwrt开启USB硬盘
  8. 阿里云端安装mysql
  9. ubuntu 17.10 如何设置合盖不关机
  10. opencv_python学习笔记十三