iOS引用計數的存儲
我在isa的深入體會一文中介紹過,蘋果從arm64架構開始,對isa進行了優(yōu)化,通過位域計數將更多信息存儲在了isa指針當中,充分利用了isa的內存空間。目前isa的結構如下
union isa_t
{
Class cls;
uintptr_t bits;
struct {
uintptr_t nonpointer : 1;
uintptr_t has_assoc : 1;
uintptr_t has_cxx_dtor : 1;
uintptr_t shiftcls : 33; // MACH_VM_MAX_ADDRESS 0x1000000000
uintptr_t magic : 6;
uintptr_t weakly_referenced : 1;
uintptr_t deallocating : 1;
uintptr_t has_sidetable_rc : 1;
uintptr_t extra_rc : 19;
};
其中的extra_rc就是用來存放引用計數的,它使用了isa上面的19個二進制為作為存儲空間。extra_rc這個命名含義是額外的引用計數,也就是除了創(chuàng)建時候的那一次retain操作之外,在其他時刻對象進行過retain操作的次數。因此一個對象實際的引用計數 = extra_rc + 1(創(chuàng)建的那一次)。當然extra_rc能夠表達的數量也是有限的,當對象的引用超過了extra_rc的表示范圍之后,isa內部的has_sidetable_rc,用來指示對象的引用計數無法存儲在isa當中,并且將引用計數的值存放到一個叫SideTable的類的屬性當中。SideTable的定義可以在objc源碼的NSObject.mm文件中找到。如下
struct SideTable {
spinlock_t slock;
RefcountMap refcnts;
weak_table_t weak_table;
}
如果isa存不下引用計數的話,那么引用計數就會被存放在SideTable的refcnts中,從類型RefcountMap可以看出,它實際上是一個散列表的結構(類似OC中的字典)。
weak指針實現原理
我們可以給property屬性設置strong、weak、unsafe_unretained,轉化到成員變量上分別是__strong、__weak、__unsafe_unretained.下面我們來看一下他們的區(qū)別,我們通過下面幾段代碼案例以及運行結果來逐個說明
*********************mian.m***************************
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"臨時作用域開始");
{
CLPerson *person = [[CLPerson alloc] init];
NSLog(@"person對象:%@", person);
}
NSLog(@"臨時作用域結束");
NSLog(@"strongPerson:%@", strongPerson);
}
return 0;
}
*******************運行結果********************
2019-09-02 19:59:00.835983+0800 Block學習[24021:2941713] 臨時作用域開始
2019-09-02 19:59:00.836482+0800 Block學習[24021:2941713] person對象:<CLPerson: 0x100704a60>
2019-09-02 19:59:00.836541+0800 Block學習[24021:2941713] -[CLPerson dealloc]
2019-09-02 19:59:00.836575+0800 Block學習[24021:2941713] 臨時作用域結束
Program ended with exit code: 0
上面這個案例很清晰的說明,局部變量person在出了臨時作用域之后,就釋放了。
*********************mian.m***************************
int main(int argc, const char * argv[]) {
@autoreleasepool {
__strong CLPerson *strongPerson;
NSLog(@"臨時作用域開始");
{
CLPerson *person = [[CLPerson alloc] init];
NSLog(@"person對象:%@", person);
strongPerson = person;
}
NSLog(@"臨時作用域結束");
NSLog(@"strongPerson:%@", strongPerson);
}
return 0;
}
*******************運行結果********************
2019-09-02 20:00:07.368972+0800 Block學習[24033:2942509] 臨時作用域開始
2019-09-02 20:00:07.369392+0800 Block學習[24033:2942509] person對象:<CLPerson: 0x1007003c0>
2019-09-02 20:00:07.369430+0800 Block學習[24033:2942509] 臨時作用域結束
2019-09-02 20:00:07.369442+0800 Block學習[24033:2942509] strongPerson:<CLPerson: 0x1007003c0>
2019-09-02 20:00:07.369460+0800 Block學習[24033:2942509] -[CLPerson dealloc]
Program ended with exit code: 0
當person被作用域外的__strong指針指向時,可以看到臨時作用域結束之后,person對象并沒有被銷毀,說明__strong指針增加了person的引用計數
*********************mian.m***************************
int main(int argc, const char * argv[]) {
@autoreleasepool {
__weak CLPerson *weakPerson;
NSLog(@"臨時作用域開始");
{
CLPerson *person = [[CLPerson alloc] init];
NSLog(@"person對象:%@", person);
weakPerson = person;
}
NSLog(@"臨時作用域結束");
NSLog(@"weakPerson:%@", weakPerson);
}
return 0;
}
*******************運行結果********************
2019-09-02 21:48:58.332332+0800 Block學習[24180:2987983] 臨時作用域開始
2019-09-02 21:48:58.332851+0800 Block學習[24180:2987983] person對象:<CLPerson: 0x100600d20>
2019-09-02 21:48:58.332889+0800 Block學習[24180:2987983] -[CLPerson dealloc]
2019-09-02 21:48:58.332920+0800 Block學習[24180:2987983] 臨時作用域結束
2019-09-02 21:48:58.332938+0800 Block學習[24180:2987983] weakPerson:(null)
Program ended with exit code: 0
當person被作用域外的__weak指針指向時,可以看到臨時作用域結束之后,person和第一種情況一樣,直接釋放了,說明__weak指針沒有增加person的引用計數,并且,person釋放時候,__weak指針被置為nil,防止了野指針錯誤
*********************mian.m***************************
int main(int argc, const char * argv[]) {
@autoreleasepool {
__unsafe_unretained CLPerson *unsafePerson;
NSLog(@"臨時作用域開始");
{
CLPerson *person = [[CLPerson alloc] init];
NSLog(@"person對象:%@", person);
unsafePerson = person;
}
NSLog(@"臨時作用域結束");
NSLog(@"unsafePerson:%@", unsafePerson);
}
return 0;
}
*******************運行結果********************
當
person被作用域外的__unsafe_unretained指針指向時,可以看到臨時作用域結束之后,person和第一種情況一樣,直接釋放了,說明__unsafe_unretained指針也沒有增加person的引用計數,但是最后卻出現了EXC_BAD_ACCESS報錯,說明是野指針問題。
【這樣就看出了__weak和__unsafe_unretained的區(qū)別就是前者會在對象被釋放的時候自動置為nil,而后者卻不行。 】那么蘋果是如何實現對象釋放后,自動將__weak指針清空的呢?下面我們就從源碼來挖掘一下。
因為__weak指針是在對象釋放的時候被清空的,所以我們從對象的dealloc方法入手,我們可以在objc源碼的NSObject.mm文件中找到dealloc的實現如下
- (void)dealloc {
_objc_rootDealloc(self);
}
**********************************
void _objc_rootDealloc(id obj)
{
assert(obj);
obj->rootDealloc();
}
***********************************
inline void objc_object::rootDealloc()
{
//??如果是Tagged Pointer,就直接返回
if (isTaggedPointer()) return; // fixme necessary?
/*
??如果同時滿足
1. 是優(yōu)化過的isa、
2. 沒有被weak指針引用過、
3. 沒有關聯對象、
4. 沒有C++析構函數、
5. 沒有sideTable,
就可以直接釋放內存free()
*/
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);
}
}
進入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.
//??如果有C++析構函數,就調用一下
if (cxx) object_cxxDestruct(obj);
//??如果有關聯對象,就進行關聯對象移除操作
if (assoc) _object_remove_assocations(obj);
//??完了之后調用下面的函數
obj->clearDeallocating();
}
return obj;
}
*********************************
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());
}
上看的方法里,如果isa是普通指針,就直接調用sidetable_clearDeallocating函數,如果是個優(yōu)化過的isa,那么就走clearDeallocating_slow函數。我們查看一下這兩個函數
可以看到這兩個函數內部都是通過調用
weak_clear_no_lock(&table.weak_table, (id)this);來處理__weak指針的,其中第一個參數就是sideTable的成員weak_table,第二個參數就是需要被釋放的對象。我們看看該函數的內部邏輯這里的核心方法是
weak_entry_for_referent再點進去很明顯,上面的方法里面,通過需要釋放的對象
referent根據一定的算法得出一個索引index,然后再從weak_table里面利用index拿到對象referent所對應的weak指針,這就說明weak_table內部其實就是一個散列表結構,通過對象作為key,value就是指向該對象的weak指針組成的數組。
weak實現原理總結
- 當一個對象
obj被weak指針指向時,這個weak指針會以obj作為key,被存儲到sideTable類的weak_table這個散列表上對應的一個weak指針數組里面。- 當一個對象
obj的dealloc方法被調用時,Runtime會以obj為key,從sideTable的weak_table散列表中,找出對應的weak指針列表,然后將里面的weak指針逐個置為nil。
以上就是weak指針的實現原理