我在isa的深刻體會一文中介紹過,蘋果從arm64架構開始,對isa進行了優化,經過位域計數將更多信息存儲在了isa指針當中,充分利用了isa的內存空間。目前isa的結構以下markdown
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這個命名含義是額外的引用計數,也就是除了建立時候的那一次retain
操做以外,在其餘時刻對象進行過retain
操做的次數。所以一個對象實際的引用計數 = extra_rc
+ 1
(建立的那一次)。固然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中的字典)。ide
咱們能夠給property屬性設置strong
、weak
、unsafe_unretained
,轉化到成員變量上分別是__strong
、__weak
、__unsafe_unretained
.下面咱們來看一下他們的區別,咱們經過下面幾段代碼案例以及運行結果來逐個說明函數
*********************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在出了臨時做用域以後,就釋放了。oop
*********************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
的區別就是前者會在對象被釋放的時候自動置爲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. 是優化過的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
函數,若是是個優化過的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
指針組成的數組。
- 當一個對象
obj
被weak
指針指向時,這個weak
指針會以obj
做爲key
,被存儲到sideTable
類的weak_table
這個散列表上對應的一個weak指針數組裏面。- 當一個對象
obj
的dealloc
方法被調用時,Runtime會以obj
爲key
,從sideTable
的weak_table
散列表中,找出對應的weak
指針列表,而後將裏面的weak
指針逐個置爲nil
。以上就是
weak
指針的實現原理