內存管理剖析(五)—— weak指針實現原理

內存管理傳送門🦋🦋🦋

內存管理剖析(一)—MRC時代的手動內存管理java

內存管理剖析(二)——定時器問題c++

內存管理剖析(三)——iOS程序的內存佈局算法

內存管理剖析(四)——autorelease原理分析數組

iOS引用計數的存儲

我在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存不下引用計數的話,那麼引用計數就會被存放在SideTablerefcnts中,從類型RefcountMap能夠看出,它其實是一個散列表的結構(相似OC中的字典)。ide

weak指針實現原理

咱們能夠給property屬性設置strongweakunsafe_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;
}


*******************運行結果********************
複製代碼

imageperson被做用域外的__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函數。咱們查看一下這兩個函數 image image 能夠看到這兩個函數內部都是經過調用weak_clear_no_lock(&table.weak_table, (id)this);來處理__weak指針的,其中第一個參數就是sideTable的成員weak_table,第二個參數就是須要被釋放的對象。咱們看看該函數的內部邏輯 image 這裏的核心方法是weak_entry_for_referent再點進去 image 很明顯,上面的方法裏面,經過須要釋放的對象referent根據必定的算法得出一個索引index,而後再從weak_table裏面利用index拿到對象referent所對應的weak指針,這就說明weak_table內部其實就是一個散列表結構,經過對象做爲keyvalue就是指向該對象weak指針組成的數組。

weak實現原理總結

  • 當一個對象objweak指針指向時,這個weak指針會以obj做爲key,被存儲到sideTable類的weak_table這個散列表上對應的一個weak指針數組裏面。
  • 當一個對象objdealloc方法被調用時,Runtime會以objkey,從sideTableweak_table散列表中,找出對應的weak指針列表,而後將裏面的weak指針逐個置爲nil

以上就是weak指針的實現原理

內存管理傳送門🦋🦋🦋

內存管理剖析(一)—MRC時代的手動內存管理

內存管理剖析(二)——定時器問題

內存管理剖析(三)——iOS程序的內存佈局

內存管理剖析(四)——autorelease原理分析

相關文章
相關標籤/搜索