iOS ARC中引用計數(shù)的實現(xiàn)
iOS weak 的實現(xiàn)
ARC中的數(shù)據(jù)結構以及尋址方式
SideTables 是一個全局的 hash 表,用來存儲對象多余的引用計數(shù)瞭稼,以及弱引用表
一忧陪、 SideTables
- SideTables 是一個全局的 hash 表, 其中包含了一個數(shù)量為64的數(shù)組议慰,數(shù)組中的存儲的為SideTable結構體,其中通過對象內存地址作為hash表的key
enum { StripeCount = 64 };
PaddedT array[StripeCount];
static unsigned int indexForPointer(const void *p) {
uintptr_t addr = reinterpret_cast<uintptr_t>(p);
return ((addr >> 4) ^ (addr >> 9)) % StripeCount;
}
- SideTable中包含一個 c++ Map
RefcountMap refcnts
用來對象存儲額外的引用計數(shù)革为,一個結構體weak_table_t weak_table
用來存儲對象的弱引用數(shù)據(jù)
RefcountMap refcnts
中通過一個size_t
(64位系統(tǒng)中占用64位)來保存引用計數(shù),其中1位用來存儲固定標志位扶歪,在溢出的時候使用特占,一位表示正在釋放中糙置,一位表示是否有弱引用,其余位表示實際的引用計數(shù)
-
RefcountMap refcnts
是一個C++的對象摩钙,內部包含了一個迭代器 - 其中以
DisguisedPtr<objc_object>
對象指針為key罢低,size_t
為value保存對象引用計數(shù) - 將key、value通過
std::pair
打包以后胖笛,放入迭代器中,所以取出值之后宜岛,.first代表key长踊,.second代表value
在上一篇中、retain release
方法會使用到SideTables來存放引用計數(shù)
// Move some retain counts to the side table from the isa field.
// Returns true if the object is now pinned.
bool
objc_object::sidetable_addExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
if (oldRefcnt & SIDE_TABLE_RC_PINNED) return true;
uintptr_t carry;
size_t newRefcnt =
addc(oldRefcnt, delta_rc << SIDE_TABLE_RC_SHIFT, 0, &carry);
if (carry) {
refcntStorage =
SIDE_TABLE_RC_PINNED | (oldRefcnt & SIDE_TABLE_FLAG_MASK);
return true;
}
else {
refcntStorage = newRefcnt;
return false;
}
}
// Move some retain counts from the side table to the isa field.
// Returns the actual count subtracted, which may be less than the request.
size_t
objc_object::sidetable_subExtraRC_nolock(size_t delta_rc)
{
assert(isa.nonpointer);
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return 0;
}
size_t oldRefcnt = it->second;
// isa-side bits should not be set here
assert((oldRefcnt & SIDE_TABLE_DEALLOCATING) == 0);
assert((oldRefcnt & SIDE_TABLE_WEAKLY_REFERENCED) == 0);
size_t newRefcnt = oldRefcnt - (delta_rc << SIDE_TABLE_RC_SHIFT);
assert(oldRefcnt > newRefcnt); // shouldn't underflow
it->second = newRefcnt;
return delta_rc;
}
- 第一個方法用來將多余的引用計數(shù)保存到SideTables中, 第二個方法用來取出引用計數(shù)
- 其中都用到了SideTables中的
RefcountMap refcnts
萍倡,但是兩個方法的實現(xiàn)并不相同
// 第一種方式
SideTable& table = SideTables()[this];
size_t& refcntStorage = table.refcnts[this];
size_t oldRefcnt = refcntStorage;
// 第二種方式
SideTable& table = SideTables()[this];
RefcountMap::iterator it = table.refcnts.find(this);
if (it == table.refcnts.end() || it->second == 0) {
// Side table retain count is zero. Can't borrow.
return 0;
}
size_t oldRefcnt = it->second;
- 其實第一種方式是通過重載
[]
操作符來實現(xiàn)查找的身弊,但是添加的時候需要判斷如果不存在記錄的話,需要新增一條數(shù)據(jù)保存列敲,所以使用了兩種不同的方法
// 通過操作符重載實現(xiàn) 查找阱佛、新增插入
value_type& FindAndConstruct(const KeyT &Key) {
BucketT *TheBucket;
if (LookupBucketFor(Key, TheBucket))
return *TheBucket;
return *InsertIntoBucket(Key, ValueT(), TheBucket);
}
ValueT &operator[](const KeyT &Key) {
return FindAndConstruct(Key).second;
}
- 兩個方法都是獲取到
RefcountMap refcnts
進行引用計數(shù)的增加和減少
weak_table_t 弱引用表
- 全局弱引用表 weak_table_t
struct weak_table_t {
weak_entry_t *weak_entries;
size_t num_entries;
uintptr_t mask;
uintptr_t max_hash_displacement;
};
- 弱引用表的內部結構
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];
};
};
};
1??. 其中主要包含了兩個屬性DisguisedPtr<objc_object> referent
對象指針,還有一個容器類保存所以只需這個對象的弱引用
2??. 共用體中包含兩種結構體戴而,當弱引用數(shù)量少于4的時候凑术,使用數(shù)據(jù)結構來存儲,當超過4個的時候使用hash表進行存儲所意,out_of_line_ness 默認為 ob00淮逊,當弱引用數(shù)量大于4的時候,設置為 REFERRERS_OUT_OF_LINE ob10扶踊,通過判斷out_of_line_ness來決定用什么方式存儲
3??. weak_referrer_t *referrers
是一個二級指針實現(xiàn)的hash表
二泄鹏、 weak 的實現(xiàn)原理
{
id obj1 = [[NSObject alloc] init];
id __weak obj2 = obj1;
}
當我們創(chuàng)建弱引用對象時,系統(tǒng)會編譯成以下代碼
id obj2;
objc_initWeak(&obj2, obj1);
objc_destroyWeak(&obj2);
通過 objc_initWeak(&obj2, obj1);
創(chuàng)建weak引用秧耗,在對象作用域結束時备籽,使用objc_destroyWeak(&obj2);
來釋放引用
- 創(chuàng)建弱引用
id
objc_initWeak(id *location, id newObj)
{
if (!newObj) {
*location = nil;
return nil;
}
return storeWeak<false/*old*/, true/*new*/, true/*crash*/>
(location, (objc_object*)newObj);
}
void
objc_destroyWeak(id *location)
{
(void)storeWeak<true/*old*/, false/*new*/, false/*crash*/>
(location, nil);
}
兩個方法最終都調用了storeWeak
,但是兩個方法調用參數(shù)不同分井。
init方法中车猬,將要引用的對象作為參數(shù)傳遞,并且HaveOld參數(shù)為false杂抽,HaveNew參數(shù)為true
而destroy方法中诈唬,將nil作為新對象傳入,并且HaveOld參數(shù)為true缩麸,HaveNew參數(shù)為false
HaveOld 代表是否有舊的引用铸磅,如果為true,則代表有舊的引用需要釋放
HaveNew 代表是否有新的引用,如果為true阅仔,則代表要存儲新的引用
template <bool HaveOld, bool HaveNew, bool 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.
// 分別獲取新舊值相關聯(lián)的引用表
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);
// 如果舊值改變就重新獲取舊值相關聯(lián)的表
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.
// 如果有新值吹散,判斷新值所屬的類是否已經初始化,如果沒有初始化八酒,則先執(zhí)行初始化空民,防止+initialize內部調用storeWeak產生死鎖
if (HaveNew && newObj) {
Class 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.
// 如果存儲成功則設置SideTable中弱引用標志位
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;
}
以上就是 store_weak 這個函數(shù)的實現(xiàn),它主要做了以下幾件事:
- 分別獲取新舊值的散列表指針
- 如果有舊值就調用 weak_unregister_no_lock 函數(shù)清除舊值
- 如果有新值就調用 weak_register_no_lock 函數(shù)分配新值
weak_register_no_lock 實現(xiàn):
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;
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;
}
weak_register_no_lock 用來保存弱引用信息,具體實現(xiàn)如下:
- 判斷對象是否可以弱引用,是否正在釋放哲泊。
- 查詢weak_table_t, 判斷對象是否已經保存有相關聯(lián)的弱引用信息
- 如果已經有相關弱引用信息挑社,則調用
append_referrer
方法添加進現(xiàn)在的weak_entry_t結構中,如果沒有相關聯(lián)信息,則創(chuàng)建weak_entry_t 節(jié)點,并且插入到weak_table_t弱引用表中。
weak_unregister_no_lock 實現(xiàn):
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))) {
remove_referrer(entry, referrer);
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_remove(weak_table, entry);
}
}
// Do not set *referrer = nil. objc_storeWeak() requires that the
// value not change.
}
weak_unregister_no_lock 用來移除弱引用信息葫慎,具體實現(xiàn)如下:
- 查詢弱引用表中是否保存有相關聯(lián)的弱引用信息
- 如果有,則調用
remove_referrer
方法移除相關聯(lián)的弱引用信息 - 移除相關聯(lián)弱引用信息之后薇宠,判斷存儲數(shù)組是否為空偷办,如果為空,則調用
weak_entry_remove
移除weak_entry_t節(jié)點
在dealloc中澄港,釋放對象時會調用一下方法
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();
}
此方法用來移除SideTables中對象相關聯(lián)的數(shù)據(jù)
- 判斷是否有弱引用椒涯,如果有調用
weak_clear_no_lock
方法移除所有相關聯(lián)弱引用 - 判斷是否有額外的引用計數(shù)存儲在SideTables中,如果有則調用
table.refcnts.erase(this);
清除
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];
if (referrer) {
if (*referrer == referent) {
*referrer = nil;
}
else if (*referrer) {
_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);
}
weak_clear_no_lock 方法用來清空對象相關聯(lián)的所有弱引用數(shù)據(jù)慢睡,具體實現(xiàn)如下:
- 判斷對象是否有相關聯(lián)的弱引用數(shù)據(jù)逐工,如果沒有則直接return
- 通過out_of_line判斷實際存儲結構,如果為0b10則用的是指針數(shù)組漂辐,否則是長度為4的數(shù)組結構
- 遍歷數(shù)組泪喊,將所有對象設置為nil
- 調用
weak_entry_remove(weak_table, entry);
移除節(jié)點