iOS底层原理05 - 属性关键字copy&weak&strong底层分析

上一篇: iOS底层原理04 - 类的结构


Student类中添加分别由strongcopyweak修饰的属性:

在通过Clang编译后的.cpp文件中,看到会生成三个对应的setter方法:

唯独在setAge的方法调用中,是通过objc_setProperty方法实现的。

1. copy

我们再objc-818.4源码中搜索,看到了如下几种方法:

objc_setProperty
  • setProperty

objc源码中,搜索不到关于setProperty相关的调用,那么针对不同的修饰符,是如何跳转到对应的setProperty方法的呢?我们打开llvm源码来查找:

在llvm层,其实对copy属性做了编译器优化,之后才会调用objc中的objc_setProperty_xxx方法。我们这里定义的age属性使用了nonatomiccopy修饰,故name = "objc_setProperty_nonatomic_copy".

最后进入reallySetProperty流程,这里可以通过断点调试来看:

reallySetProperty
  • reallySetProperty
static inline void reallySetProperty(id self, SEL _cmd, id newValue, ptrdiff_t offset, bool atomic, bool copy, bool mutableCopy)
{
    if (offset == 0) {
        object_setClass(self, newValue);
        return;
    }

    id oldValue;
    id *slot = (id*) ((char*)self + offset);

    if (copy) {
        newValue = [newValue copyWithZone:nil];
    } else if (mutableCopy) {
        newValue = [newValue mutableCopyWithZone:nil];
    } else {
        if (*slot == newValue) return;
        newValue = objc_retain(newValue);
    }

    if (!atomic) {
        oldValue = *slot;
        *slot = newValue;
    } else {
        spinlock_t& slotlock = PropertyLocks[slot];
        slotlock.lock();
        oldValue = *slot;
        *slot = newValue;        
        slotlock.unlock();
    }

    objc_release(oldValue);
}
  • 如果是copy或者mutableCopy并不会对新值进行objc_retain操作,而是copyWithZone,返回一个不可变的对象
  • 若为atomic原子性的属性,会在setter方法添加spinlock_t类型的自旋锁来保证多线程写入安全。

2. weak

.cpp中看到,使用weak修饰的对象,并不会走上面的objc_setProperty方法,而是走objc源码中的objc_storeWeak来更新弱引用指针的指向

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;
    
    // 用地址作为唯一标识来获取新值和旧值锁的位置
    // 通过地址来创建索引标志,防止桶的重复
 retry:
    if (haveOld) {
        oldObj = *location;
        // 获得以oldObj为索引存储的SideTable
        oldTable = &SideTables()[oldObj];
    } else {
        oldTable = nil;
    }
    if (haveNew) {
        // 获得以newObj为索引存储的SideTable
        newTable = &SideTables()[newObj];
    } else {
        newTable = nil;
    }
    // 加锁操作,防止多线程中竞争冲突 
    SideTable::lockTwo(oldTable, newTable);
    // 避免线程冲突重处理
    // location 应该与 oldObj 保持一致,如果不同说明当前的 location 已经处理过 oldObj 可是又被其他线程所修改  
    if (haveOld  &&  *location != oldObj) {
        SideTable::unlockTwo(oldTable, newTable);
        goto retry;
    }
    // 避免弱引用机制和 +initialize 之间的死锁
    if (haveNew  &&  newObj) {
        Class cls = newObj->getIsa();
        if (cls != previouslyInitializedClass  &&  
            !((objc_class *)cls)->isInitialized()) 
        {
            SideTable::unlockTwo(oldTable, newTable);
            class_initialize(cls, (id)newObj);

            previouslyInitializedClass = cls;

            goto retry;
        }
    }

    // 清除旧值
    if (haveOld) {
        weak_unregister_no_lock(&oldTable->weak_table, oldObj, location);
    }
    // 设置新值
    if (haveNew) {
        newObj = (objc_object *)
            weak_register_no_lock(&newTable->weak_table, (id)newObj, location, 
                                  crashIfDeallocating ? CrashIfDeallocating : ReturnNilIfDeallocating);
       
        if (!newObj->isTaggedPointerOrNil()) {
            // 弱引用位初始化操作      
            // 引用计数那张散列表的weak引用对象的引用计数中标识为weak引用
            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(oldTable, newTable);
    callSetWeaklyReferenced((id)newObj);
    return (id)newObj;
}
  • location是指向weak指针的指针,因为要修改weak指针
  • 上面storeWeak的过程,就是在解除与旧对象的关系,并与新对象建立联系

runtime维护了一张weak表,用来存储指向某个对象的所有weak指针,从源码中看到,这张表其实就是SideTables,这是一张hash表,key是对象的地址,value是weak指针的数组。

SideTables 散列表

SideTables结构
  • 使用对象来获取对应的SideTable: &SideTables()[newObj]

  • spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable

  • RefcountMap refcnts :以DisguisedPtr为key的hash表,用来存储OC对象的引用计数(仅在未开启isa优化 或 在isa优化情况下isa_t的引用计数溢出时才会用到)。

// RefcountMap disguises its pointers because we 
// don't want the table to act as a root for `leaks`.
typedef objc::DenseMap,size_t,RefcountMapValuePurgeable> RefcountMap;

RefcountMap是以DenseMap为模板创建的,三个参数分别为hash key类型,value类型,以及是否要在value==0时自动释放掉响应的hash节点,这里是true。

  • weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC weak功能实现的核心数据结构。
struct weak_table_t {
    weak_entry_t *weak_entries; // hash数组 存储弱引用对象的相关信息
    size_t    num_entries;      // hash数组的元素个数
    uintptr_t mask;             // hash数组长度-1,参与hash计算
    uintptr_t max_hash_displacement;    // 可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误
};

weak_table_t是全局的弱引用表,将对象id存储为键,将weak_entry_t存储为它们的值。

在我们的App中,多个对象会重用同一个SideTable节点,也就是说,weak_table会存储多个对象的弱引用信息。因此在一个SideTable中,又会通过weak_table作为hash表再次分散存储每一个对象的弱引用信息。

  • weak_entry_t
struct weak_entry_t {
    DisguisedPtr referent; // 被弱引用的对象
    // 两种形式的联合体 - 动态数组weak_referrer_t和定长数组weak_referrer_t
    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];
        };
    };
    ...
};
  • DisguisedPtr referent:弱引用对象的指针摘要
  • union:联合体,有两种形式 - 动态数组weak_referrer_t *referrers和定长数组weak_referrer_t inline_referrers[WEAK_INLINE_COUNT]。 用来存储弱引用该对象的指针的指针。当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT 4时,为定长数组;当超过时,会将定长数组中的元素转移到动态数组中。

clearDeallocating

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();
}

当一个对象被释放,调用clearDeallocating后会做如下操作:

  • 由当前对象作为key找到SideTable
  • 将当前对象转为objc_object类型并作为key,从SideTableweak_table_t中取出weak_entry_t
  • 遍历weak_referrer_t,将指向该对象的弱引用指针置为nil;
  • referrers数组移除,weak_table中hash数组的元素个数-1
  • table.refcnts.eraser() 从引用计数表中擦除该对象的引用计数

3. strong

objc_storeStrong

若以strong修饰的属性,会调用objc_storeStrong方法,进行新值retain旧值release

你可能感兴趣的:(iOS底层原理05 - 属性关键字copy&weak&strong底层分析)