上一篇: iOS底层原理04 - 类的结构
在Student
类中添加分别由strong
、copy
、weak
修饰的属性:
在通过Clang
编译后的.cpp
文件中,看到会生成三个对应的setter
方法:
唯独在setAge
的方法调用中,是通过objc_setProperty
方法实现的。
1. copy
我们再objc-818.4源码中搜索,看到了如下几种方法:
- setProperty
在objc
源码中,搜索不到关于setProperty
相关的调用,那么针对不同的修饰符,是如何跳转到对应的setProperty
方法的呢?我们打开llvm
源码来查找:
在llvm层,其实对copy
属性做了编译器优化,之后才会调用objc
中的objc_setProperty_xxx
方法。我们这里定义的age
属性使用了nonatomic
和copy
修饰,故name = "objc_setProperty_nonatomic_copy"
.
最后进入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 散列表
使用对象来获取对应的
SideTable
:&SideTables()[newObj]
spinlock_t slock
: 自旋锁,用于上锁/解锁 SideTableRefcountMap 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,从SideTable
的weak_table_t
中取出weak_entry_t
- 遍历
weak_referrer_t
,将指向该对象的弱引用指针置为nil; - 将
referrers
数组移除,weak_table
中hash数组的元素个数-1 -
table.refcnts.eraser()
从引用计数表中擦除该对象的引用计数
3. strong
若以strong
修饰的属性,会调用objc_storeStrong
方法,进行新值retain
和旧值release