美文网首页工作博客iOS
【iOS】weak & strong 及自动引用计数机制

【iOS】weak & strong 及自动引用计数机制

作者: abs_ | 来源:发表于2019-02-21 16:25 被阅读17次

【iOS】weak实现细节学习

1. weak & Strong的基本用法和异同比较

weak 和 strong 是ARC才会用到的两个关键词

以weak为例,一般用到的地方有两处:

  1. 用于修饰属性常见于协议对象,此时weak被称为属性修饰符

    @property (nonatomic, weak) UIView *someView;
    
  2. 用作变量的修饰符,常用于打破循环引用,避免内存泄漏,此时, ___weak称为“所有权修饰符”,

    __weak PINCache *weakSelf = self;
    

二者的区别和联系:首先应该明白,属性是什么? 一个属性一般包含3个东西:实例变量、getter 、setter。 当声明一个属性时,编译器在为我们生成一个实例变量时,会使用与其相对应的所有权修饰符来修饰该实例变量,例如被weak修饰的属性,它对应实例变量的所有权修饰符为__weak。

weak基本特征:

  • 对应__weak修饰符
  • 引用计数不加1
  • 指向的对象销毁,指针会自动置为nil
  • 定义了一种“非拥有关系” (nonowning relationship),为weak属性设置新值时,设置方法既不保留新值,也不释放旧值

assign基本特征

  • 对应__unsafe_unretained所属权修饰符

  • 不会让引用计数器+1

  • 如果指向对象被销毁,指针不会清空,仍旧指向对象销毁后所处的内存区域,导致程序崩溃,野指针

  • 定义了一种“非拥有关系” (nonowning relationship),设置新值时,设置方法既不保留新值,也不释放旧值

    关于关于第4点,可以结合在MRC环境下setter方法的实现来理解

    // assign 
    -(void)setName:(NSString *)name{
    
        _name = name;
    }
    // retain
    -(void)setName:(NSString *)name{
        
        if (_name != name) {
            [_name release];
            _name = [name retain];
        }
    }
    // copy 
    -(void)setName:(NSString *)name{
    
        if (_name != name) {
            [_name release];
            _name = [name copy];
        }
    }
    

strong基本特征:

  • 对应__strong修饰符
  • 引用计数加1
  • 指向的对象销毁,指针会自动置为nil

有人可能没有思考过strong指向的对象销毁后,strong类型指针会不会变为nil,其实这个仔细想一下就明白,平常我们调试的时候,判断一个对象存不存在不就是通过看它的指针是不是nil吗?

2. weak底层实现原理

2.1 weak表的数据结构

  1. runtime中维护了一张hash表。key是对象的值(结构体地址) value是指向结构体指针的一组指针变量

    散列表(Hash table,也叫哈希表),是根据关键码值(Key value)而直接进行访问的数据结构也就是说,它通过把关键码值映射到表中一个位置来访问记录,以加快查找的速度。这个映射函数叫做散列函数,存放记录的数组叫做散列表 给定表M,存在函数f(key),对任意给定的关键字值key,代入函数后若能得到包含该关键字的记录在表中的地址,则称表M为哈希(Hash)表,函数f(key)为哈希(Hash) 函数。

    从上可知,hash表的基本要素:

    1. 散列表(存放记录的数组)

    2. 散列函数(从key到表中位置的映射关系)

      1. 散列函数的通常要求是防止碰撞,(碰撞的意思是不同的key值通过映射关系后得到的表的位置相同)

      2. 可以看得出其实散列函数与MD5加密算法是同一类算法(md5可以将长度不等的字符串编码为长度相等且唯一的字符串)我想这也是md5被称为哈希算法的原因。

        以上是本人自己的思考,如果要求证请查阅相关资料

weak表结构如下:

/**
 * 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;                   // weak对象的存储空间
    uintptr_t mask;                          //参与判断引用计数辅助量
    uintptr_t max_hash_displacement;             //hash key 最大偏移值
};

uintptr_t(即 unsigned long) size_t(即 unsigned )

typedef objc_object ** weak_referrer_t;
struct weak_entry_t {
    DisguisedPtr<objc_object> referent;  
    union {
        struct {
            weak_referrer_t *referrers;
            uintptr_t        out_of_line : 1;
            uintptr_t        num_refs : PTR_MINUS_1;
            uintptr_t        mask;
            uintptr_t        max_hash_displacement;
        };
        struct {
            // out_of_line=0 is LSB of one of these (don't care which)
            weak_referrer_t  inline_referrers[WEAK_INLINE_COUNT];
        };
    }
}


DisguisedPtr<T> acts like pointer type T*

根据注释可知,可以将 DisguisedPtr<T> 当成 T*指针类型即可,指向objc_object的指针,referent记录了weak对象的地址

weak_referrer_tobjc_object 的二阶指针,我推测应该是用于指向referent这个指针变量。

共用体表示几个变量共用一个内存位置,在不同的时间保存不同的数据类型和不同长度的变量。在union中,所有的共用体成员共用一个空间,并且同一时间只能储存其中一个成员变量的值

union内部有两个结构体,但是两个结构体只会存储一个。weak_referrer_t *referrersweak_referrer_t inline_referrers[WEAK_INLINE_COUNT]都可以作为数组来存储weak_referrer_t类型的数据(也就是指向referent变量的指针变量)

接着看它的注释:

// out_of_line=0 is LSB of one of these (don't care which)

我推测应该是通过out_of_line这样一个标志位来判断,要么使用第一个结构存储,要么使用第二个结构存储。WEAK_INLINE_COUNT 是一个常量,所以inline_referrers可以存储的数据是有限的。我认为它的逻辑应该是,当需要存储的数量大于WEAK_INLINE_COUNT时,使用第一个结构体存储,否则使用第二个结构体存储。

写到这里之后引发了我对于对象、类、结构体、指针的混乱,我觉得自己的脑子已经乱了,然后我又把这些东西整理了一下,没兴趣的看,可以略过。。。

首先第一个 什么是对象?什么是类?

首先摆出几个结构体的定义:

struct objc_object {
    Class isa  OBJC_ISA_AVAILABILITY;
}; 
struct objc_class { 
    Class isa  OBJC_ISA_AVAILABILITY;

#if !__OBJC2__
    Class super_class                                        OBJC2_UNAVAILABLE;
    const char *name                                         OBJC2_UNAVAILABLE;
    long version                                             OBJC2_UNAVAILABLE;
    long info                                                OBJC2_UNAVAILABLE;
    long instance_size                                       OBJC2_UNAVAILABLE;
    struct objc_ivar_list *ivars                             OBJC2_UNAVAILABLE;
    struct objc_method_list **methodLists                    OBJC2_UNAVAILABLE;
    struct objc_cache *cache                                 OBJC2_UNAVAILABLE;
    struct objc_protocol_list *protocols                     OBJC2_UNAVAILABLE;
#endif
} OBJC2_UNAVAILABLE;
/// An opaque type that represents an Objective-C class.
typedef struct objc_class *Class;

那我们平常用的例如:

NSString *string = @"Hello world";

前面为什么要用用一个*号呢?

回忆一下C语言类型中类似的场景

typedef struct node {
   char *name;
}Node;

Node *node = (Node *)malloc(sizeof(Node));

objc是运行时语言,所以NSString *编译期完全可以看成 NSObject类型,NSObject里面可以找到NSObject的定义:

@interface NSObject <NSObject> {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wobjc-interface-ivars"
    Class isa  OBJC_ISA_AVAILABILITY;
#pragma clang diagnostic pop
}

obje_object结构体是相同的。

回到刚刚C语言结构体的定义的地方,首先node是一个指向Node结构体的指针变量,在栈空间分配内存,假设它的值是0x7f7efb48fe20 它指向了一片堆空间内存,这片内存的地址是0x7f7efb48fe20 这片内存存储的结构是什么呢?就是一个Node类型的结构体!

再回到NSString *string = @"Hello world";

string是什么呢? 它也是一个结构体指针,它指向了一片堆空间的内存,这片内存存储的结构是是objc_object!

结论: 所以,如果一个对象是一个objc_object结构体,那么我们平常使用的只是指向该对象的结构体指针!

2.2 weak表的初始化过程

2.2.1 weak表的初始化过程

//初始化weak表
/** 
 * 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;
 * 
 * @param addr Address of __weak ptr. 
 * @param val Object ptr. 
 */
id objc_initWeak(id *addr, id val)
{
    *addr = 0;
    if (!val) return nil;
    return objc_storeWeak(addr, val); // 存储weak对象
}

阅读注释可以知道,该方法触发时机

  1. 声明一个空的__weak变量,
  2. 声明一个空的__weak变量然后赋值的时候

2.2.2 存储weak对象的方法

/** 
 * 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)
{
    id oldObj;
    SideTable *oldTable;
    SideTable *newTable;
    spinlock_t *lock1;
#if SIDE_TABLE_STRIPE > 1
    spinlock_t *lock2;
#endif

    // 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:
    oldObj = *location;
    
    oldTable = SideTable::tableForPointer(oldObj);
    newTable = SideTable::tableForPointer(newObj);
    
    lock1 = &newTable->slock;
#if SIDE_TABLE_STRIPE > 1
    lock2 = &oldTable->slock;
    if (lock1 > lock2) {
        spinlock_t *temp = lock1;
        lock1 = lock2;
        lock2 = temp;
    }
    if (lock1 != lock2) spinlock_lock(lock2);
#endif
    spinlock_lock(lock1);

    if (*location != oldObj) {
        spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
        if (lock1 != lock2) spinlock_unlock(lock2);
#endif
        goto retry;
    }

    weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
    // 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 = newObj;
    
    spinlock_unlock(lock1);
#if SIDE_TABLE_STRIPE > 1
    if (lock1 != lock2) spinlock_unlock(lock2);
#endif

    return newObj;
}

objc_initWeak内调用了objc_storeWeak方法

* ```
   /** 
    * 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)
   {
       id oldObj;
       SideTable *oldTable;
       SideTable *newTable;
       spinlock_t *lock1;
   #if SIDE_TABLE_STRIPE > 1
       spinlock_t *lock2;
   #endif
   
       // 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:
       oldObj = *location;
       
       oldTable = SideTable::tableForPointer(oldObj);
       newTable = SideTable::tableForPointer(newObj);
       
       lock1 = &newTable->slock;
   #if SIDE_TABLE_STRIPE > 1
       lock2 = &oldTable->slock;
       if (lock1 > lock2) {
           spinlock_t *temp = lock1;
           lock1 = lock2;
           lock2 = temp;
       }
       if (lock1 != lock2) spinlock_lock(lock2);
   #endif
       spinlock_lock(lock1);
   
       if (*location != oldObj) {
           spinlock_unlock(lock1);
   #if SIDE_TABLE_STRIPE > 1
           if (lock1 != lock2) spinlock_unlock(lock2);
   #endif
           goto retry;
       }
   
       weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
       newObj = weak_register_no_lock(&newTable->weak_table, newObj, location);
       // 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 = newObj;
       
       spinlock_unlock(lock1);
   #if SIDE_TABLE_STRIPE > 1
       if (lock1 != lock2) spinlock_unlock(lock2);
   #endif
   
       return newObj;
   }
   ```

相关文章

网友评论

    本文标题:【iOS】weak & strong 及自动引用计数机制

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