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

来源:https://www.cnblogs.com/cyq1162/archive/2018/01/09/8235768.html
-Advertisement-
Play Games

最近都在折騰 Sagit 架框的記憶體釋放的問題,所以對這一塊有些心得。對於新手,學到的文章都在教你用:typeof(self) __weak weakSelf = self。對於老手,可能早習慣了到處了WeakSelf了。這次,就來學學,如何不用WeakSelf。 ...


前言:

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

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

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

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

1:從引用計數器開始:

這裡先設計一個TableBlock類:

@interface BlockTable : NSObject

typedef 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 : NSObject

typedef 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,在執行完後出括弧後,直接是銷毀對象。

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

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

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

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

9:空指針和野指針的區別:

空指針:指向一個:人為創造的一個指針,它的名字叫空,有座空房子,裡面什麼也沒有。

野指針:就是指向的都不知哪去了,連空房子都木有。

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基礎還是要打勞,後續產品改進起來才有質的飛躍。


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • Oracle基礎練習題,採用Oracle資料庫自帶的表,適合初學者,其中包括了一些簡單的查詢,已經具有Oracle自身特點的單行函數的應用 本文使用的實例表結構與表的數據如下: emp員工表結構如下: dept部門表: 提示:工資 = 薪金 + 佣金 emp表的現有數據如下: dept表的現有數據如 ...
  • ERROR 2006 (HY000): MySQL server has gone away ...
  • 和關係資料庫一樣,Neo4j同樣可以創建索引來加快查找速度。 在關係資料庫中創建索引需要索引欄位和指向記錄的指針,通過索引可以快速查找到表中的行。 在Neo4j中,其索引是通過屬性來創建,便於快速查找節點或者關係。 手動索引 先來說一下怎樣創建手動索引。 創建索引採用顯示創建,就像添加節點一樣添加索 ...
  • imei、imsi是大部分應用自動採集的設備信息,Android中需要在MINIFEST聲明許可權,6.0以上手機還需要用戶顯示授權才可以正常獲取。這兩個標識有什麼區別、聯繫?為什麼應用這麼執著於採集這兩個信息呢? imei IMEI(International Mobile Equipment Id ...
  • 看到很多小程式里,點客服,提示關註公眾號,比如製作器里這個功能,能夠自動引導關註公眾號,圖文體驗非常好,研究了小程式客服介面後,我們就自己把它做成一個工具了,方便小程式的運營人員。芝麻小客服 體驗傳送門 http://xiaokefu.hotapp.cn 特點: (1)不需要開發,只需要在微信的小程 ...
  • 首先,咱得先說下註意點: Android中主要通過RecognizerIntent來實現語音識別,其實代碼比較簡單,但是如果找不到設置,就會拋出異常 ActivityNotFoundException,所以我們需要捕捉這個異常。而且語音識別在模擬器上是無法測試的,因為語音識別是訪問google 雲端 ...
  • 首先看一下安裝apk文件的代碼 測試發現該段代碼在7.0一下的機型上可以成功打開指定路徑下的指定apk文件 , 但是在7.0+的機型上調用該代碼會報錯: 原因在於:Android 7.0 版本開始 禁止向你的應用外公開 file:// URI。 如果一項包含文件 file:// URI類型 的 In ...
  • libyuv是Google開源庫,可用作圖像數據格式的轉換,比如視頻流編解碼時格式的轉換,YUV數據轉化RGB等 libyuv靜態庫 為了方便使用,已經將libyuv源代碼打包成了iOS靜態庫, "libyuv靜態庫" libyuv使用 下麵以nv12(yuv420sp)轉化為I420(yuv420 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...