IOS 看懂此文,你的block再也不需要WeakSelf弱引用了!,iosweakself

來源:互聯網
上載者:User

IOS 看懂此文,你的block再也不需要WeakSelf弱引用了!,iosweakself
前言:

最近都在折騰 Sagit 架框的記憶體釋放的問題,所以對這一塊有些心得。

對於新手,學到的文章都在教你用:typeof(self) __weak weakSelf = self。

對於老手,可能早習慣了到處了WeakSelf了。

這次,就來學學,如何不用WeakSelf。

1:從引用計數器開始:

這裡先設計一個TableBlock類:

@interface BlockTable : NSObjecttypedef void (^AddCellBlock)();@property (nonatomic,copy)AddCellBlock addCell;@end

先這麼簡單,一個BlockTable只有一個block屬性,然後輸出一段釋放的日誌。

-(void)dealloc{    NSLog(@"Table relase");//relase為錯誤字,為了和保持一致的錯別字,這裡就不改了。}

接著,隨意找一個地方寫寫代碼:來new了一個BlockTable,並列印一下資訊:

這時候它的引用數是1,並且出了Table relase 。

接著給addCell屬性賦一個值,並運行:

一個空的事件,裡面並沒有引用到table,所以引用數還是1。

2:開始循環參考

在block引用table,讓它產生循環參考,並運行:

我們看到:引用數變成了3,沒有輸出對象釋放資訊了,為啥不是2呢?大大的問號!!

一個屬性賦值,為什麼增強兩個引用計數?

3:猜解跳躍的計數器

接下來,把屬性設定為nil,運行看看:

設定為nil,還有2?

也正常釋放了?

為了證實自己對這個看起來就很明顯的猜想:重寫addCell的setter方法,不進行任何儲存:

@implementation BlockTable-(void)setAddCell:(AddCellBlock)addCell{    }

同時去掉置為nil的代碼:再運行看看:

計數器仍為2,而且也釋放了。

經過思考,出來了以下的結論:

1:塊的定義本身,就會造成1次引用,不過這次引用,在塊離開所在的函數時,釋放時,抵消掉引用數。2:存檔塊的時候,會造成1次引用,而這個引用,是記憶體無法釋放的原因。
4:根據上述解釋,得到一個瘋狂的結論:
只要block的代碼只執行1次的,都可以任性的self或其它強引用。事實上,我們寫的代碼,很多block的確只執行一次,不管是傳的時候就執行,還是傳完之後過段時間回調再執行。認定只要執行1次的,就不需要WeakSelf,除非第三方架構的設計者造孽留坑,忘了在存檔block執行後補上block=nil這一刀。
5:消滅賦值的引用計數:

繼續發揮想象力,既然存的時候,會增加一次引用,辣麼,讓它不增加引用不就好了:

@implementation BlockTable-(void)setAddCell:(AddCellBlock)addCell{    __weak AddCellBlock addCellWeak=addCell;    _addCell=addCellWeak;}

我們先給這個block定義一個弱引用,然後再賦值給_addCell,運行看看:

哇草,成功了!計數器為2,正常釋放了,看來自己的想象力,還是可以的!!

接下來,我們補充完善一下代碼,增加一個reloadData方法,方法裡呼叫事件。

完整的代碼如下:

@interface BlockTable : NSObjecttypedef void (^AddCellBlock)();@property (nonatomic,copy)AddCellBlock addCell;-(void)reloadData;@end@implementation BlockTable-(void)setAddCell:(AddCellBlock)addCell{    __weak AddCellBlock addCellWeak=addCell;    _addCell=addCellWeak;}-(void)reloadData{    if(self.addCell)    {        self.addCell();
     self.addCell();//沒事來兩次,類比table多次迴圈清加cell }}-(void)dealloc{ NSLog(@"Table relase");}@end

修改一下增加日誌輸出,現在再執行一下看看:

一切看起來都相當完美,不需要引入第三,需要多次使用的,只是在存的時候,存個弱引用,就搞定了。

6:弱引用降低計數的缺陷:
塊的定義,和使用的情境,必須在同一個函數。說白了就是塊離開函數體就會消亡,所以要用要趕緊,且用且珍惜。

正常一個Table寫完代碼reloadData後,資料出來了。

但如果後面還跟有一個重新整理重新載入的功能?

而這個重新調用reloadData的地方,可能跟block不在同一個函數,比如代碼像這樣:

-(void)start{    BlockTable *table=[BlockTable new];    self.table=table;//搞到全域變數中    table.addCell = ^{        __weak typeof(table) this=table;        NSLog(@"addCell call");    };    [table reloadData];    NSLog(@"table retain = %ld",CFGetRetainCount((__bridge CFTypeRef)(table)));}-(void)reflesh{    [self.table reloadData];}

給外面的類定義了一個table屬性,然後調用完start後再調用reflesh,運行,會怎樣呢?

出現了IOS上最可怕的EXC_BAD_ACCESS 野指標錯誤。

對於block離開函數後,消亡了容易理解,只是這裡:

這什麼是直接拋異常?哥不是作了判斷了嗎?

讓我們換種代碼寫法:

另外從看:_addCell還是有值的。

為什麼if(self.addCell)判斷就直接死,if(_addCell)卻沒死呢?正常self.addCell正常不是也return _addCell嗎?這個問題,留給讓你們思考了。

 

最可怕的,還是下面的這段話:

7:避開野指標,仍是弱引用,功能不變

OK,繼續發揮想象力,看看怎麼避開野指標,同時還是實現上述的效果:

1:把block屬性從copy改成weak

@property (nonatomic,weak)AddCellBlock addCell;

2:賦值代碼手工copy:

-(void)setAddCell:(AddCellBlock)addCell{    addCell=[addCell copy];    _addCell=addCell;    //_addCell=[addCell copy];這樣簡寫是不行的,不明白為蝦米呢        //    原來是這樣寫的:    //   __weak AddCellBlock addCellWeak=addCell;    //    _addCell=addCellWeak ;}

再次運行,神奇的事情發生了:

流程還是很順,不會有野批針異常,Table也釋放了。

唯一的遺憾,就是跳出函數後,block不能再複用了:

8:block的copy方法:

對於預設傳進來的block(有三種形態:全域、棧、堆)

全域 copy 還是全域堆 copy 還是堆棧 copy 變成堆

說白了,copy只對類型是棧是才有效。

這是因為:棧的block,在執行完後出括弧後,直接是銷毀對象。

如果有弱引用過去,會造成野指標。

而其它兩種類型,銷毀時,會將指標指向一個null 指標。

addCell=[addCell copy] 和預設copy的屬性 _addCell=addCell 也是執行了copy操作。

執行後,addCell的類型就變成堆形態,這樣銷毀的時候,是null 指標。

9:null 指標和野指標的區別:
null 指標:指向一個:人為創造的一個指標,它的名字叫空,有座空房子,裡面什麼也沒有。野指標:就是指向的都不知哪去了,連空房子都木有。
10:擴充想象力,如何消滅引用數,還能長久保留? 

弱引用的壞處,就是block出了函數,就不再可用這個block了。

那還能怎麼辦呢?沒事,我還有想象力!!!!!

如果block可以重建呢?

比如:

1:將block轉成字串存檔,適當時機還原回來重新賦值2:將block序列化儲存,適當時機還原回來?3:runtime讀取block的__FuncPtr,存檔再動態建立?

虛擬碼大體如下:

-(void)setAddCell:(AddCellBlock)addCell{    addCell=[addCell copy];    _addCell=addCell;        //_addCell=[addCell copy];這樣簡寫是不行的,不明白為蝦米呢        //    原來是這樣寫的:    //   __weak AddCellBlock addCellWeak=addCell;    //    _addCell=addCellWeak ;        //存檔block的字串}-(void)reloadData{    if(!_addCell)    {        //從存檔的block字串還原block        //_addCell=還原block    }    if(_addCell)    {        _addCell();        _addCell();    }}

那麼就剩下兩個問題?

1:怎麼把block存檔?2:怎麼將存檔資料還原成block。

對搞C#的來說,這些都家常便飯,oc這塊還不熟,有路過的朋友可順路給支支招!!

11:如果第10的方式解決不了,就只能,只能,引入時機第三者了

不過這個引入第三者,只是一個時機切入點,在這個時機觸發的時候,將其中的一方的引用設定為nil。

像Sagit架構的布局方面的時機,就選在導航回退等事件中處理。

不過這裡需要一個小技巧:

在存檔block時,不一定要存在當前對象,也可以用一個統一的全域block管理起來。

這樣在業務處理時,根據業務情況,從全域block裡來移除某些block即可。

具體取決於業務,所以這個就不展開了。

總結:

相信,一路看下,看懂了,後續的情況,基本上已經用不上WeakSelf這東西了,因為像一個block,其生命週期必須和持有人保持一致的,還是挺少的。

而這種少的情況,如果第10步解決了,基本就全都解決了,解決不了,還有11。

相信讀完此文,如果能完全理解,你就再也看不到block前WeakSelf這種,WeakSelf也沒有存在必要了。

最後,歡迎大夥關注IT連創業,雖然最近我都在折騰IOS,哈哈。

不過IOS基礎還是要打勞,後續產品改進起來才有質的飛躍。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.