【轉】iOS學習之容易造成迴圈引用的三種場景

来源:http://www.cnblogs.com/gfxxbk/archive/2016/06/14/5585528.html
-Advertisement-
Play Games

ARC已經出來很久了,自動釋放記憶體的確很方便,但是並非絕對安全絕對不會產生記憶體泄露。導致iOS對象無法按預期釋放的一個無形殺手是——迴圈引用。迴圈引用可以簡單理解為A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,導致任何時候引用計數都不為0,始終無法釋放。若當前對象是一個ViewContr ...


ARC已經出來很久了,自動釋放記憶體的確很方便,但是並非絕對安全絕對不會產生記憶體泄露。導致iOS對象無法按預期釋放的一個無形殺手是——迴圈引用。迴圈引用可以簡單理解為A引用了B,而B又引用了A,雙方都同時保持對方的一個引用,導致任何時候引用計數都不為0,始終無法釋放。若當前對象是一個ViewController,則在dismiss或者pop之後其dealloc無法被調用,在頻繁的push或者present之後記憶體暴增,然後APP就duang地掛了。下麵列舉我們變成中比較容易碰到的三種迴圈引用的情形。

(1)計時器NSTimer

一方面,NSTimer經常會被作為某個類的成員變數,而NSTimer初始化時要指定self為target,容易造成迴圈引用。 另一方面,若timer一直處於validate的狀態,則其引用計數將始終大於0。先看一段NSTimer使用的例子(ARC模式):

1 #import <Foundation/Foundation.h>
2 @interface Friend : NSObject
3 - (void)cleanTimer;
4 @end
 1 #import "Friend.h"
 2 @interface Friend ()
 3 {
 4     NSTimer *_timer;
 5 }
 6 @end
 7 
 8 @implementation Friend
 9 - (id)init
10 {
11     if (self = [super init]) {
12         _timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(handleTimer:)
13                                                 userInfo:nil repeats:YES];
14     }
15     return  self;
16 }
17 
18 - (void)handleTimer:(id)sender
19 {
20     NSLog(@"%@ say: Hi!", [self class]);
21 }
22 - (void)cleanTimer
23 {
24     [_timer invalidate];
25     _timer = nil;
26 }
27 - (void)dealloc
28 {
29     [self cleanTimer];
30     NSLog(@"[Friend class] is dealloced");
31 }

在類外部初始化一個Friend對象,並延遲5秒後將friend釋放(外部運行在非arc環境下)

1         Friend *f = [[Friend alloc] init];
2         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
4             [f release];
5         });

我們所期待的結果是,初始化5秒後,f對象被release,f的dealloc方法被調用,在dealloc裡面timer失效,對象被析構。但結果卻是如此:

1 2 3 4 5 6 7 8 9 10 2015-03-18 18:00:35.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:36.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:37.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:38.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:39.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!//運行了5次後沒按照預想的停下來 2015-03-18 18:00:40.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:41.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:42.300 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:43.299 WZLCodeLibrary[41422:3390529] Friend say: Hi! 2015-03-18 18:00:44.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!<br>.......根本停不下來.....

這是為什麼呢?主要是因為從timer的角度,timer認為調用方(Friend對象)被析構時會進入dealloc,在dealloc可以順便將timer的計時停掉並且釋放記憶體;但是從Friend的角度,他認為timer不停止計時不析構,那我永遠沒機會進入dealloc。迴圈引用,互相等待,子子孫孫無窮盡也。問題的癥結在於-(void)cleanTimer函數的調用時機不對,顯然不能想當然地放在調用者的dealloc中。一個比較好的解決方法是開放這個函數,讓Friend的調用者顯式地調用來清理現場。如下:

1 2 3 4 5 Friend *f = [[Friend alloc] init]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{     [f cleanTimer];     [f release]; });

=======================================

(2)block

block在copy時都會對block內部用到的對象進行強引用(ARC)或者retainCount增1(非ARC)。在ARC與非ARC環境下對block使用不當都會引起迴圈引用問題,一般表現為,某個類將block作為自己的屬性變數,然後該類在block的方法體裡面又使用了該類本身,簡單說就是self.someBlock = ^(Type var){[self dosomething];或者self.otherVar = XXX;或者_otherVar = ...};block的這種迴圈引用會被編譯器捕捉到並及時提醒。舉例如下,依舊以Friend類為例子:

#import "Friend.h"

@interface Friend ()
@property (nonatomic) NSArray *arr;
@end

@implementation Friend
- (id)init
{
    if (self = [super init]) {
         self.arr = @[@111, @222, @333];
        self.block = ^(NSString *name){
            NSLog(@"arr:%@", self.arr);
        };
    }
    return  self;
}

我們看到,在block的實現內部又使用了Friend類的arr屬性,xcode給出了warning, 運行程式之後也證明瞭Friend對象無法被析構:

網上大部分帖子都表述為"block裡面引用了self導致迴圈引用",但事實真的是如此嗎?我表示懷疑,其實這種說法是不嚴謹的,不一定要顯式地出現"self"字眼才會引起迴圈引用。我們改一下代碼,不通過屬性self.arr去訪問arr變數,而是通過實例變數_arr去訪問,如下:

由此我們知道了,即使在你的block代碼中沒有顯式地出現"self",也會出現迴圈引用!只要你在block里用到了self所擁有的東西!但對於這種情況,我們無法通過加__weak聲明或者__block聲明去禁止block對self進行強引用或者強制增加引用計數。但我們可以通過其他指針來避免迴圈引用(多謝xq_120的提醒),具體是這麼做的:

 

1 2 3 4 5 __weak typeof(self) weakSelf = self;  self.blkA = ^{ __strong typeof(weakSelf) strongSelf = weakSelf;//加一下強引用,避免weakSelf被釋放掉  NSLog(@"%@", strongSelf->_xxView); //不會導致迴圈引用. };

  

對於self.arr的情況,我們要分兩種環境去解決:

1)ARC環境下:ARC環境下可以通過使用_weak聲明一個代替self的新變數代替原先的self,我們可以命名為weakSelf。通過這種方式告訴block,不要在block內部對self進行強制strong引用:(如果要相容ios4.3,則用__unsafe_unretained代替__weak,不過目前基本不需考慮這麼low的版本)

1          self.arr = @[@111, @222, @333];
2         __weak typeof(self) weakSelf=self;
3         self.block = ^(NSString *name){
4             NSLog(@"arr:%@", weakSelf.arr);
5         };

2)MRC環境下:解決方式與上述基本一致,只不過將__weak關鍵字換成__block即可,這樣的意思是告訴block:小子,不要在內部對self進行retain了!

=========================================================

(3)委托delegate

在委托問題上出現迴圈引用問題已經是老生常談了,本文也不再細講,規避該問題的殺手鐧也是簡單到哭,一字訣:聲明delegate時請用assign(MRC)或者weak(ARC),千萬別手賤玩一下retain或者strong,畢竟這基本逃不掉迴圈引用了!

本篇博客轉載於 編程小翁@博客園,郵件[email protected],微信Jilon,原文:http://www.cnblogs.com/wengzilin/p/4347974.html


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

-Advertisement-
Play Games
更多相關文章
  • javascript 字元串 和 數字的轉換,話說好靈活,感覺回不去pascal了 ...
  • var files=[];var errors=[]; var chunk=<%=request.getParameter("chunk")%>; var root="",xml=""; var max_file_size='20MB'; var image='<%=request.getParam ...
  • 一般要用到遞歸,就要判斷對象是否和父類型是否一樣 這裡演示簡單的對象遞歸,還有數組遞歸類似。 返回結果:1,2,3,4,5,6,7,8,9 ...
  • 一、數組的擴展,ES6在數組擴展了一些API,以實現更多的功能 1.Array.from:可以將類數組和可遍歷的數據結構轉換成真正的數組,如下所示 如果參數是真正的數組,則直接返回一個一樣的新數組,參數也可是一個實現了Iterator介面的數據結構,如set,如下所示 Array.from還支持第二 ...
  • 一、簡介 1.1 GCD (Grand Central Dispatch )是Apple開發的一個多核編程的解決方法。 Grand 含義是“偉大的、巨集大的”,Central含義“中央的”,Dispatch含義是“分發、派遣,調度”; 1.2 GCD中有2個核心概念 任務:執行什麼操作 隊列:用來存放 ...
  • 網路安全與加密 1.數據安全 2.Base64 3.常見的加密演算法和其它 4.單向散列函數 5.對稱加密 6.非對稱加密 7.數字簽名 1.數字簽名的應用場景 需要嚴格驗證發送方身份信息情況 2.數字簽名原理 1)客戶端處理 ①對"消息"進行 HASH 得到 "消息摘要" ②發送方使用自己的私鑰對" ...
  • 多線程基本概念 多線程實現方案 1.pthread 2.NSThread (1)基本使用 (2)設置線程屬性 (3)線程的狀態 (4)線程安全 (5)線程間通信 (6)如何計算代碼段的執行時間 3.GCD (1)GCD基本知識 兩個核心概念 隊列和任務 同步函數和非同步函數 (2)GCD基本使用【重點 ...
  • command+shift+0會出現如下圖 然後輸入你想找的API 記得找帶Reference這種標記的文檔 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...