上次ARC的博客里涉及了__weak关键字,也简单了解了weak原理。
这篇博客来具体探究weak的底层原理。
查看汇编代码
调用了initWeak、destroyWeak俩个方法
先看这个initWeak方法
*
* 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.
*/
翻译
初始化一个指向某个对象位置的新弱指针。
*它将用于以下代码:
*
* (nil个案)
*剩余的弱项;
*(非nil个案)
* NSObject *o =…;
*剩余弱id;
*
*这个函数对于并发来说不是线程安全的
修改弱变量。(同时弱清晰是安全的。)
*
* @param剩余弱ptr的位置地址。
* @param newObj对象ptr。
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak
(location, (objc_object*)newObj);
}
俩个参数是
location :__weak指针的地址,存储指针的地址,这样便可以在最后将其指向的对象置为nil。
newObj :所引用的对象。即例子中的obj 。
如果给__weak所指的对象为空了,就将这个对象置为nil。
再看看store这个方法
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];
//如果__weak指针之前弱引用过对象,就将这个对象对应的stideTable取出来,赋值给oldTable
} else {
oldTable = nil; //如果没有oldTable赋值为nil
}
if (haveNew) { //如果__weak指针要引用新对象,则将新对象的sideTable取出来赋值给newTable
newTable = &SideTables()[newObj];
} else {
newTable = nil; //如果__weak指针不需要引用新对象,则将newTable赋值为nil。
}
//加锁操作,防止多线程中竞争冲突。
SideTable::lockTwo(oldTable, newTable);
//location应与oldObj保持一致
//
//location这个参数是__weak指针,如果旧对象不为空且它不指向旧对象,说明已经被其他线程修改
if (haveOld && *location != oldObj) {
SideTable::unlockTwo(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.
if (haveNew && newObj) { //指向了新对象
Class cls = newObj->getIsa();
if (cls != previouslyInitializedClass &&
!((objc_class *)cls)->isInitialized()) //如果cls还没有初始化,先初始化,再尝试设置weak
{
SideTable::unlockTwo(oldTable, newTable);
class_initialize(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);
//如果__weak指针之前弱引用过别的对象oldObj,则调用weak_unregister_no_lock,在oldObj的weak_entry_t中移除该__weak指针的地址
}
// Assign new value, if any.
if (haveNew) { //如果__weak指针引用新对象
newObj = (objc_object *)
weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
将__weak指针记录到newobject对应的weak_table对应的weak_entry_t中
// weak_register_no_lock returns nil if weak store should be rejected
//更新newObj的isa的weakly_referenced bit标志位
// Set is-weakly-referenced bit in refcount table.
if (newObj && !newObj->isTaggedPointer()) {
newObj->setWeaklyReferenced_nolock();
}
//*location 赋值,也就是将__weak指针直接指向了newObj。可以看到,这里并没有将newObj的引用计数+1
// Do not set *location anywhere else. That would introduce a race.
*location = (id)newObj;
}
else {
// No new value. The storage is not changed.
}
// 解锁,其他线程可以访问oldTable, newTable了
SideTable::unlockTwo(oldTable, newTable);
// 返回newObj,此时的newObj与刚传入时相比,weakly-referenced bit位置1
return (id)newObj;
}
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
//省略方法
//spinlock_t slock : 自旋锁,用于上锁/解锁 SideTable。
RefcountMap refcnts :用来存储OC对象的引用计数的 hash表(仅在未开启isa优化或在isa优化情况下isa_t的引用计数溢出时才会用到)。
weak_table_t weak_table : 存储对象弱引用指针的hash表。是OC中weak功能实现的核心数据结构。
};
一个函数名之前加~,表示此函数是析构函数
关于c++的模版
c++模版
SideTables()
static objc::ExplicitInit> SideTablesMap;
//StripedMap 是一个模板类,根据传递的实际参数决定其中 array 成员存储的元素类型。
// 能通过对象的地址,运算出 Hash 值,通过该 hash 值找到对应的 value 。
static StripedMap& SideTables() {
return SideTablesMap.get();
}
这里的方法名是sideTables,返回值是StripedMap&
//这里面其实StripedMap里一共就一个PaddedT类型的数组,大小为StripeCount【64】
//可以大致理解为,该方法会返回一个StripedMap类型的数据结构,里面存储的类型是SideTable
来看看这个stripeMap
template
class StripedMap {
// 如果是手机而不是模拟器
#if TARGET_OS_IPHONE && !TARGET_OS_SIMULATOR
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);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
public:
T& operator[] (const void *p) {
return array[indexForPointer(p)].value;
}
const T& operator[] (const void *p) const {
return const_cast>(this)[p];
}
//省略其他方法
StripedMap 是一个模板类, 内部维护一个大小为 StripeCount 的数组, 数组的成员为结构体 PaddedT, PaddedT 结构体只有一个成员 value, value 的类型是 T.
这里涉及到泛型相关知识, 结合起来理解, 就是说 SideTables() 返回的 StripedMap, 是一个 value 为 SideTable 的哈希桶(由于 SideTable 内部又在维护数组, 所以这是一个哈希桶结构), 哈希值由对象的地址计算得出.
什么是哈希桶呢?
有俩个关键字k1\k2,哈希函数得出同一个value。即f(k1)=f(k2),k1!=k2.这时候如果是普通的哈希表,那就放一个value,让下一个寻找空的地址,只要足够大,总能找到。
图1这样对一整个表加一把锁,是我们平时比较常见的。如果我一次写操作需要操作表中多个单元格的数据,比如第一次操作0、1、2位置的数据,第二次操作0、2、3位置的数据。像这种情况锁的粒度就是以整张表为单位的,才能保证数据的安全。
图2这样对表中的各个元素分别加一把锁就是我们说的分离锁。适用于表中元素相互独立,你对第一个元素做写操作的时候不需要影响到其他元素。
上文中所说的结构就是SideTables这个大的Hash表中每一个小单元格(SideTable)都带有一把锁。做写操作的时候(操作对象引用计数)单元格之间相互独立,互相没影响。所以降低了锁的粒度。
/**
* 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_entries: hash数组,用来存储弱引用对象的相关信息weak_entry_t
//这里不是指weak_entry_t是哈希表,是一个value是weak_entry_t的哈希数组weak_entries
num_entries: hash数组中的元素个数
mask:hash数组长度-1,会参与hash计算。(注意,这里是hash数组的长度,而不是元素个数。比如,数组长度可能是64,而元素个数仅存了2个)
max_hash_displacement:可能会发生的hash冲突的最大次数,用于判断是否出现了逻辑错误(hash表中的冲突次数绝不会超过该值)
weak_table_t是一个典型的hash结构。weak_entries是一个动态数组,用来存储weak_entry_t类型的元素,这些元素实际上就是OC对象的弱引用信息
weak_entry_t的结构是一个数组,其存储的元素是弱引用对象指针的指针, 通过操作指针的指针,就可以使得weak 引用的指针在对象析构后,指向nil。不过比较特殊的是,超出4个会用动态数组来管理
id __weak obj;
指向obj这个指针的指针。
这里我们用lldb打印验证它是个数组
id __weak obj;
id __strong obj1 = [[NSObject alloc] init];
for(int i = 0; i < 1000; i++) {
[obj1 retain];
}
obj = obj1;
NSLog(@"%p", &obj);
id __weak obj3;
obj3 = obj1;
p *newTable
((anonymous namespace)::SideTable) $0 = {
slock = {
mLock = (_os_unfair_lock_opaque = 0)
}
refcnts = {
Buckets = 0x000000010113e830
NumEntries = 1
NumTombstones = 0
NumBuckets = 4
}
weak_table = {
weak_entries = 0x0000000103808200
num_entries = 1
mask = 63
max_hash_displacement = 0
}
}
(lldb) p $0.weak_table
(weak_table_t) $1 = {
weak_entries = 0x0000000103808200
num_entries = 1
mask = 63
max_hash_displacement = 0
}
(lldb) p $1.weak_entries
(weak_entry_t *) $2 = 0x0000000103808200
(lldb) p *$2
(weak_entry_t) $3 = {
referent = (value = 18446744069396535776)
= {
= {
referrers = 0xffff800110400ae8
out_of_line_ness = 0
num_refs = 4611650835197199040
mask = 0
max_hash_displacement = 0
}
= {
inline_referrers = {
[0] = (value = 18446603340788796136)
[1] = (value = 18446603340788796160)
[2] = (value = 0)
[3] = (value = 0)
}
}
}
}
struct weak_entry_t {
DisguisedPtr referent; //被引用的弱对象
//// 引用该对象的对象列表,联合。 引用个数小于4,用inline_referrers数组。 用个数大于4,用动态数组weak_referrer_t *referrers
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的结构定义中有联合体,在联合体的内部有定长数组inline_referrers[WEAK_INLINE_COUNT]和动态数组weak_referrer_t *referrers两种方式来存储弱引用对象的指针地址。通过out_of_line()这样一个函数方法来判断采用哪种存储方式。当弱引用该对象的指针数目小于等于WEAK_INLINE_COUNT时,使用定长数组。当超过WEAK_INLINE_COUNT时,会将定长数组中的元素转移到动态数组中,并之后都是用动态数组存储。
到这里我们已经清楚了弱引用表的结构是一个hash结构的表,Key是所指对象的地址,Value是weak指针的地址(这个地址的值是所指对象的地址)数组。那么接下来看看这个弱引用表是怎么维护这些数据的。
/// Adds an (object, weak pointer) pair to the weak table.
//向弱表添加一个(对象,弱指针)对。
id
weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
// 如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作
if (!referent || referent->isTaggedPointer()) return referent_id;
// ensure that the referenced object is viable
bool deallocating;
// // 确保被引用的对象可用(没有在析构,同时应该支持weak引用)
if (!referent->ISA()->hasCustomRR()) {
deallocating = referent->rootIsDeallocating();
}
else {
BOOL (*allowsWeakReference)(objc_object *, SEL) =
(BOOL(*)(objc_object *, SEL))
object_getMethodImplementation((id)referent,
@selector(allowsWeakReference));
if ((IMP)allowsWeakReference == _objc_msgForward) {
return nil;
}
deallocating =
! (*allowsWeakReference)(referent, @selector(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_table中找到referent对应的weak_entry,并将referrer加入到weak_entry中
weak_entry_t *entry;
//这里就比较糊了,referrer和referrent指的谁呢?这是调用
// weak_register_no_lock(&newTable->weak_table, (id)newObj, location,
crashIfDeallocating);
//这是原型
//id weak_register_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id, bool crashIfDeallocating)
//也就是说,referent_id是新的被弱引用对象,referrer_id是__weak指针的地址。
if ((entry = weak_entry_for_referent(weak_table, referent))) {
append_referrer(entry, referrer); // 如果能找到weak_entry,则将referrer插入到weak_entry中
}
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;
}
weak_table:weak_table_t结构类型的全局的弱引用表。
referent_id:weak指针。
*referrer_id:weak指针地址。
crashIfDeallocating :若果被弱引用的对象正在析构,此时再弱引用该对象是否应该crash。
总结
如果referent为nil 或 referent 采用了TaggedPointer计数方式,直接返回,不做任何操作。
如果对象正在析构,则抛出异常。
如果对象不能被weak引用,直接返回nil。
如果对象没有正在析构且可以被weak引用,则调用weak_entry_for_referent方法根据弱引用对象的地址从弱引用表中找到对应的weak_entry,如果能够找到则调用append_referrer方法向其中插入weak指针地址。否则新建一个weak_entry。
//从返回看,这个函数的意思是取得一个weak_entries中的数组元素,也就是__weak指针的地址。
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; // 这里通过 & weak_table->mask的位操作,来确保index不会越界
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];
}
static void append_referrer(weak_entry_t *entry, objc_object **new_referrer)
{
if (! entry->out_of_line()) { // 如果weak_entry 尚未使用动态数组
// 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;
}
}
// 如果inline_referrers的位置已经存满了,则要转型为referrers,做动态数组。
// 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) {// 如果动态数组中元素个数大于或等于数组位置总空间的3/4,则扩展数组空间为当前长度的一倍
return grow_refs_and_insert(entry, new_referrer);
//扩容并插入
}
/// 如果不需要扩容,直接插入到weak_entry中
// 注意,weak_entry是一个哈希表,key:w_hash_pointer(new_referrer) value: new_referrer
size_t begin = w_hash_pointer(new_referrer) & (entry->mask);
size_t index = begin; // 初始的hash index
size_t hash_displacement = 0; // 用于记录hash冲突的次数,也就是hash再位移的次数
while (entry->referrers[index] != nil) {
hash_displacement++;
index = (index+1) & entry->mask; //// index + 1, 移到下一个位置,再试一次能否插入。(这里要考虑到entry->mask取值,一定是:0x111, 0x1111, 0x11111, ... ,因为数组每次都是*2增长,即8, 16, 32,对应动态数组空间长度-1的mask,也就是前面的取值。)
if (index == begin) bad_weak_table(entry); // // index == begin 意味着数组绕了一圈都没有找到合适位置,这时候一定是出了什么问题。
}
if (hash_displacement > entry->max_hash_displacement) {
entry->max_hash_displacement = hash_displacement; // 记录最大的hash冲突次数, max_hash_displacement意味着: 我们尝试至多max_hash_displacement次,肯定能够找到object对应的hash位置
}
weak_referrer_t &ref = entry->referrers[index]; // // 将ref存入hash数组,同时,更新元素个数num_refs
ref = new_referrer;
entry->num_refs++;
}
(index+1) & entry->mask这句话是怎么确保不越界呢?
他是数组长度减1,如果是 8,则值为 0111,如果你取值到了8,1000,经过&之后,值又变回了0,所以不回越界。
void
weak_unregister_no_lock(weak_table_t *weak_table, id referent_id,
id *referrer_id)
{
objc_object *referent = (objc_object *)referent_id;
objc_object **referrer = (objc_object **)referrer_id;
weak_entry_t *entry;
if (!referent) return;
if ((entry = weak_entry_for_referent(weak_table, referent))) { // 查找到referent所对应的weak_entry_t
remove_referrer(entry, referrer); // 在referent所对应的weak_entry_t的hash数组中,移除referrer
// 移除元素之后, 要检查一下weak_entry_t的hash数组是否已经空了
bool empty = true;
if (entry->out_of_line() && entry->num_refs != 0) {
empty = false;
}
else {
for (size_t i = 0; i < WEAK_INLINE_COUNT; i++) {
if (entry->inline_referrers[i]) {
empty = false;
break;
}
}
}
if (empty) { // 如果weak_entry_t的hash数组已经空了,则需要将weak_entry_t从weak_table中移除
weak_entry_remove(weak_table, entry);
}
}
用一个老例子。
一个大神写的
假设有80个学生需要咱们安排住宿,同时还要保证学生们的财产安全。应该怎么安排?
显然不会给80个学生分别安排80间宿舍,然后给每个宿舍的大门上加一把锁。那样太浪费资源了锁也挺贵的,太多的宿舍维护起来也很费力气。
我们一般的做法是把80个学生分配到10间宿舍里,每个宿舍住8个人。假设宿舍号分别是101、102 、… 110。然后再给他们分配床位,01号床、02号床等。然后给每个宿舍配一把锁来保护宿舍内同学的财产安全。为什么不只给整个宿舍楼上一把锁,每次有人进出的时候都把整个宿舍楼锁上?显然这样会造成宿舍楼大门口阻塞。
OK假如现在有人要找102号宿舍的2号床的人聊天。这个人会怎么做?
1、找到宿舍楼(SideTables)的宿管,跟他说自己要找10202(内存地址当做key)。
2、宿管带着他SideTables[10202]找到了102宿舍SideTable,然后把102的门一锁lock,在他访问102期间不再允许其他访客访问102了。(这样只是阻塞了102的8个兄弟的访问,而不会影响整栋宿舍楼的访问)
3、然后在宿舍里大喊一声:"2号床的兄弟在哪里?"table.refcnts.find(02)你就可以找到2号床的兄弟了。
4、等这个访客离开的时候会把房门的锁打开unlock,这样其他需要访问102的人就可以继续进来访问了。
SideTables == 宿舍楼;SideTable == 宿舍;RefcountMap里存放着具体的床位
苹果之所以需要创造SideTables的Hash冲突是为了把对象放到宿舍里管理,把锁的粒度缩小到一个宿舍SideTable。RefcountMap的工作是在找到宿舍以后帮助大家找到正确的床位的兄弟。
当对象的引用计数为0时,底层会调用_objc_rootDealloc方法对对象进行释放,而在_objc_rootDealloc方法里面会调用rootDealloc方法。如下是rootDealloc方法的代码实现。
inline void
objc_object::rootDealloc()
{
if (isTaggedPointer()) return; // fixme necessary?
if (fastpath(isa.nonpointer &&
!isa.weakly_referenced &&
!isa.has_assoc &&
!isa.has_cxx_dtor &&
!isa.has_sidetable_rc))
{
assert(!sidetable_present());
free(this);
}
else {
object_dispose((id)this);
}
}
1.首先判断对象是否是Tagged Pointer,如果是则直接返回。
2.如果对象是采用了优化的isa计数方式,且同时满足对象没有被weak引用!isa.weakly_referenced、没有关联对象!isa.has_assoc、没有自定义的C++析构方法!isa.has_cxx_dtor、没有用到SideTable来引用计数!isa.has_sidetable_rc则直接快速释放。
3.如果不能满足2中的条件,则会调用object_dispose方法
id
object_dispose(id obj)
{
if (!obj) return nil;
objc_destructInstance(obj);
free(obj);
return nil;
}
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.
if (cxx) object_cxxDestruct(obj);
if (assoc) _object_remove_assocations(obj);
obj->clearDeallocating();
}
return obj;
}
上面这一段代码很清晰,如果有自定义的C++析构方法,则调用C++析构函数。如果有关联对象,则移除关联对象并将其自身从Association Manager的map中移除。调用clearDeallocating方法清除对象的相关引用。
inline void
objc_object::clearDeallocating()
{
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());
}
clearDeallocating中有两个分支,现实判断对象是否采用了优化isa引用计数,如果没有的话则需要清理对象存储在SideTable中的引用计数数据。如果对象采用了优化isa引用计数,则判断是都有使用SideTable的辅助引用计数(isa.has_sidetable_rc)或者有weak引用(isa.weakly_referenced),符合这两种情况中一种的,调用clearDeallocating_slow方法。
// Slow path of clearDeallocating()
// for objects with nonpointer isa
// that were ever weakly referenced
// or whose retain count ever overflowed to the side table.
NEVER_INLINE void
objc_object::clearDeallocating_slow()
{
ASSERT(isa.nonpointer && (isa.weakly_referenced || isa.has_sidetable_rc));
SideTable& table = SideTables()[this];// 在全局的SideTables中,以this指针为key,找到对应的SideTable
table.lock();
if (isa.weakly_referenced) {// 如果obj被弱引用
weak_clear_no_lock(&table.weak_table, (id)this);// 在SideTable的weak_table中对this进行清理工作
}
}
if (isa.has_sidetable_rc) {/ /如果采用了SideTable做引用计数
table.refcnts.erase(this);// 在SideTable的引用计数中移除this
}
table.unlock();
}
void
weak_clear_no_lock(weak_table_t *weak_table, id referent_id)
{
objc_object *referent = (objc_object *)referent_id;
weak_entry_t *entry = weak_entry_for_referent(weak_table, referent);
if (entry == nil) {
/// XXX shouldn't happen, but does with mismatched CF/objc
//printf("XXX no entry for clear deallocating %p\n", referent);
return;
}
// zero out references
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]; // 取出每个weak ptr的地址
if (referrer) {
if (*referrer == referent) {
*referrer = nil; //// 如果weak ptr确实weak引用了referent,则将weak ptr设置为nil,这也就是为什么weak 指针会自动设置为nil的原因
}
else if (*referrer) { // // 如果所存储的weak ptr没有weak 引用referent,这可能是由于runtime代码的逻辑错误引起的,报错
_objc_inform("__weak variable at %p holds %p instead of %p. "
"This is probably incorrect use of "
"objc_storeWeak() and objc_loadWeak(). "
"Break on objc_weak_error to debug.\n",
referrer, (void*)*referrer, (void*)referent);
objc_weak_error();
}
}
}
weak_entry_remove(weak_table, entry); // 由于referent要被释放了,因此referent的weak_entry_t也要移除出weak_table
}
}
1、weak的原理在于底层维护了一张weak_table_t结构的hash表,key是所指对象的地址,value是weak指针的地址数组。
2、weak 关键字的作用是弱引用,所引用对象的计数器不会加1,并在引用对象被释放的时候自动被设置为 nil。
3、对象释放时,调用clearDeallocating函数根据对象地址获取所有weak指针地址的数组,然后遍历这个数组把其中的数据设为nil,最后把这个entry从weak表中删除,最后清理对象的记录。
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
};
上面说到这个 RefcountMap refcnts是存储引用计数的,那它存储的原理是什么呢
typedef objc::DenseMap,size_t,RefcountMapValuePurgeable> RefcountMap;
template ,
typename KeyInfoT = DenseMapInfo,
typename BucketT = detail::DenseMapPair>
class DenseMap : public DenseMapBase,
KeyT, ValueT, ValueInfoT, KeyInfoT, BucketT> {
friend class DenseMapBase;
// Lift some types from the dependent base class into this class for
// simplicity of referring to them.
using BaseT = DenseMapBase;
BucketT *Buckets;
unsigned NumEntries;
unsigned NumTombstones;
unsigned NumBuckets;
可以看到,它的类型定义十分复杂。这个map有三个参数,我们来看一下
第一个参数是第二次见到了,在weak_entry_t的结构体里就见到过.
这东西叫泛型,指定了数组、集合存储的类型是什么。这里就是指的objc_object *
DisguisedPtr
size_t:Value
RefcountMapValuePurgeable:是否需要在value=0的时候自动释放响应的hash节点。此处是true。
关于size_t
size_t表达的就是对应对象的引用计数值,实际上它就是一个unsign long型的变量。
size_t的每个byte位代表的含义:
第一位表示的是对象是否有弱引用(weakly_referenced),0代表无,1代表有
第二位表示当前对象是否正在进行dealloc(deallocating)
其他位置存储对象实际的引用计数值。
所以我们在计算对象的引用计数值时,需要对这个值向右偏移2位。(如同源码中定义的宏:SIDE_TABLE_RC_SHIFT 2)
BucketT *Buckets;
unsigned NumEntries;
unsigned NumTombstones;
unsigned NumBuckets;
这里的原理从SideTables搬运
1.Buckets 指针管理一段连续内存空间, 也就是数组, 数组成员是 BucketT 类型的对象, 我们这里将 BucketT 对象称为桶(实际上这个数组才应该叫桶, 苹果把数组中的元素称为桶应该是为了形象一些, 而不是哈希桶中的桶的意思). 桶数组在申请空间后, 会进行初始化, 在所有位置上都放上空桶(桶的 key 为 EmptyKey 时是空桶), 之后对引用计数的操作, 都要依赖于桶.
桶的数据类型实际上是 std::pair, 类似于 swift 中的元祖类型, 就是将对象地址和对象的引用计数(这里的引用计数类似于 isa, 也是使用其中的几个 bit 来保存引用计数, 留出几个 bit 来做其它标记位)组合成一个数据类型.
2.NumEntries 记录数组中已使用的非空的桶的个数.
3.NumTombstones, Tombstone 直译为墓碑, 当一个对象的引用计数为0, 要从桶中取出时, 其所处的位置会被标记为 Tombstone. NumTombstones 就是数组中的墓碑的个数. 后面会介绍到墓碑的作用.
4.NumBuckets 桶的数量, 因为数组中始终都充满桶, 所以可以理解为数组大小.
通过阅读文章,这个墓碑的作用很简单,相当于是设立一个标志位,让hash算法继续寻找下一个空位置。
1.如图,很不巧,通过hash计算,abcd的下表都是0,那么他们线性探测后,存放位置如图1所属,此时c被释放了,如果设置的是空桶,显然,这里可以插入新元素,要给d、e增加引用计数就得写额外的代码,
2。如果此时初始化了一个新的对象 f, 根据哈希算法查找到下标为 2 的桶时发现桶中放置了墓碑, 此时会记录下来下标 2. 接下来继续哈希算法查找位置, 查找到空桶时, 就证明表中没有对象 f, 此时 f 使用记录好的下标 2 的桶而不是查找到的空桶, 就可以利用到已经释放的位置.
weak实现