記憶體管理系列文章
記憶體管理—MRC時代的手動記憶體管理
記憶體管理—weak的實作原理
記憶體管理——autorelease原理分析
記憶體管理——定時器問題
iOS程式的記憶體布局
iOS引用計數的存儲
我在isa的深入體會一文中介紹過,蘋果從arm64架構開始,對isa進行了優化,通過位域計數将更多資訊存儲在了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這個命名含義是額外的引用計數,也就是除了建立時候的那一次
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中的字典)。
weak指針實作原理
我們可以給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在出了臨時作用域之後,就釋放了。
*********************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
指針組成的數組。
weak實作原理總結
- 當一個對象
被
obj
指針指向時,這個
weak
指針會以
weak
作為
obj
,被存儲到
key
類的
sideTable
這個散清單上對應的一個weak指針數組裡面。
weak_table
- 當一個對象
的
obj
方法被調用時,Runtime會以
dealloc
為
obj
,從
key
的
sideTable
散清單中,找出對應的
weak_table
指針清單,然後将裡面的
weak
指針逐個置為
weak
nil
。
以上就是
指針的實作原理
weak