iOS開發--引用計數與ARC

来源:http://www.cnblogs.com/azuo/archive/2016/11/22/6066868.html
-Advertisement-
Play Games

以下是關於記憶體管理的學習筆記:引用計數與ARC。 iOS5以前自動引用計數(ARC)是在MacOS X 10.7與iOS 5中引入一項新技術,用於代替之前的手工引用計數MRC(Manual Reference Counting)管理Objective-C中的對象【官方也叫MRR(Manual Ret ...


以下是關於記憶體管理的學習筆記:引用計數與ARC。

iOS5以前自動引用計數(ARC)是在MacOS X 10.7與iOS 5中引入一項新技術,用於代替之前的手工引用計數MRC(Manual Reference Counting)管理Objective-C中的對象【官方也叫MRR(Manual Retain Release)】。如今,ARC下的iOS項目幾乎把所有記憶體管理事宜都交給編譯器來決定,而開發者只需專註於業務邏輯。

但是,對於iOS開發來說,記憶體管理是個很重要的概念,如果先要寫出記憶體使用效率高而又沒有bug的代碼,就得掌握其記憶體管理模型的細節。

一、引用計數

1.與記憶體管理的關係?

在Objective-C記憶體管理中,每個對象都有屬於自己的計數器:如果想讓某個對象繼續存活(例如想對該對象進行引用),就遞增它的引用計數;當用完它之後,就遞減該計數;當沒人引用該對象,它的計數變為0之後,系統就把它銷毀。 

這個,就是引用計數在其中充當的角色:用於表示當前有多少個對象想令此對象繼續存活程式中;

 

2.引用計數的介紹:

引用計數(Reference Count),也叫保留計數(retain count),表示對象被引用的次數。一個簡單而有效的管理對象生命周期的方式。

 

3.引用計數的工作原理:

  1. 當我們創建(alloc)一個新對象A的時候,它的引用計數從零變為 1;
  2. 當有一個指針指向這個對象A,也就是某對象想通過引用保留(retain)該對象A時,引用計數加 1;
  3. 當某個指針/對象不再指向這個對象A,也就是釋放(release)該引用,我們將其引用計數減 1;
  4. 當對象A的引用計數變為 0 時,說明這個對象不再被任何指針指向(引用)了,這個時候我們就可以對象A銷毀,所占記憶體將被回收且所有指向該對象的引用也都變得無效了。系統也會將其占用的記憶體標記為“可重用”(reuse);

流程參考圖如下:

(圖片表格取自《編寫高質量iOS與OS X代碼的52個有效方法》一書)

 

4.操作引用計數的方法:

A.以下是NSObject協議中聲明的3個用於操作計數器的方法:
  • retain : 保留。保留計數+1;
  • release : 釋放。保留計數 -1;
  • autorelease :稍後(清理“自動釋放池”時),再遞減保留計數,所以作用是延遲對象的release;
B.dealloc方法:另外,當計數為0的時候對象會自動調用dealloc。而我們可以在dealloc方法做的,就是釋放指向其他對象的引用,以及取消已經訂閱的KVO、通知;(自己不能調用dealloc方法,因為運行期系統會在恰當的時候調用它,而且一旦調用dealloc方法,對象不再有效,即使後續方法再次調用retain。)

所以,調用release後會有2種情況:

調用前計數>1,計數減1;

調用前計數<1,對象記憶體被回收; 

 
C.retainCount:獲取引用計數的方法。

Eg: [object retainCount]; //得到object的引用計數

 

retain、release、autorelease詳解:

retain作用:

調用後計數+1,保留對象操作。但是當對象被銷毀、記憶體被回收的時候,即使使用retain也不再有效;

autorelease作用:

autorelease不立即釋放,而是註冊到autoreleasepool(自動釋放池)中,等到pool結束時釋放池再自動調用release進行釋放工作。

autorelease看上去很像ARC,但是實際上更類似C語言中的自動變數(局部變數),當某自動變數超出其作用域(例如大括弧),該自動變數將被自動廢棄,而autorelease中對象實例的release方法會被調用;[與C不同的是,開發者可以設定變數的作用域。]

釋放時間:每個Runloop中都創建一個Autorelease pool(自動釋放池),每一次的Autorelease,系統都會把該Object放入了當前的Autorelease pool中,併在Runloop的末尾進行釋放,而當該pool被釋放時,該pool中的所有Object會被調用Release。 所以,一般情況下,每個接受autorelease消息的對象,都會在下個Runloop開始前被釋放。

例如可用以下場景:(需要從ARC改為使用手動管理的可以做如下的設置: 在Targets的Build Phases選項下Compile Sources下選擇要不使用ARC編譯的文件,雙擊它,輸入-fno-objc-arc即可使用MRC手工管理記憶體方式;)

-(NSString *)getSting
{
    NSString *str = [[NSString alloc]initWithFormat:@"I am Str"];
    return [str autorelease];
}

自動釋放池中的釋放操作會等到下一次時間迴圈時才會執行,所以調用以下: 

NSString *str = [self getSting];
NSLog(@"%@",str);

返回的str對象得以保留,延遲釋放。因此可以無需再NSLog語句之前執行保留操作,就可以將返回的str對象輸出。

所以可見autorelease的作用是能延長對象的生命期。使其在跨越方法調用邊界後依然可以存活一段時間。

release作用:

release會立即執行釋放操作,使得計減1;

有這樣一種情況:當某對象object的引用計數為1的時候,調用“[object release];”,此時如果再調用NSLog方法輸出object的話,可能程式就會崩潰,當然只是有可能,因為對象所占記憶體在“解除分配(deallocated)”之後,只是放回“可用記憶體池(avaiable pool)”,但是如果執行NSLog時,尚未覆寫對象記憶體,那麼該對象依然有效,所以程式有可能不會崩潰,由此可見,因過早地釋放對象而導致的bug很難調試。

為避免這種情況,一般調用完對象之後都會清空指針:"object = nil",這樣就能保證不會出現指向無效對象的指針,也就是懸掛指針(dangling pointer);

懸掛指針:指向無效對象的指針。

 

那麼,向已經釋放(dealloc)的對象發送消息,retainCount會是多少?

原則是不可以這麼做。因為該對象的記憶體已經被回收,而我們向一個已經被回收的對象發了一個 retainCount 消息,所以它的輸出結果應該是不確定的,例如為減少一次記憶體的寫操作,不將這個值從 1 變成 0,所以很大可能輸出1。例如下麵這種情況:

Person *person = [[Person alloc] init]; //此時,計數 = 1   
[person retain];    //計數 = 2   
[person release];   //計數 = 1   
[person release];   //很可能計數 = 1;  

雖然第四行代碼把計數1release了一次,原理上person對象的計數會變成0,但是實際上為了優化對象的釋放行為,提高系統的工作效率,在retainCount為1時release系統會直接把對象回收,而不再為它的計數遞減為0,所以一個對象的retainCount值有可能永遠不為0;

因此,不管是否為ARC的開發環境中,也不推薦使用retainCount來做為一個對象是否存在於記憶體之中的依據。

 

 


 

二、ARC

1.背景:

ARC是iOS 5推出的新功能,全稱叫 ARC(Automatic Reference Counting)。

即使2014 年的 WWDC 大會上推出的Swift 語言,該語言仍然使用 ARC 技術作為其管理方式。

2.ARC是什麼?

需要註意的是,ARC並不是GC(Garbage Collection 垃圾回收器),它只是一種代碼靜態分析(Static Analyzer)工具,背後的原理是依賴編譯器的靜態分析能力,通過在編譯時找出合理的插入引用計數管理代碼,從而提高iOS開發人員的開發效率。 

Apple的文檔里是這麼定義ARC:

自動引用計數(ARC)是一個編譯器級的功能,它能簡化Cocoa應用中對象生命周期管理(記憶體管理)的流程。

3.ARC在做什麼?

在編譯階段,編譯器將在項目代碼中自動為分配對象插入retain、release和autorelease,且插入的代碼不可見。

但是,需要註意的是,ARC模式下引用計數規則還起作用,只是編譯器會為開發者分擔大部分的記憶體管理工作,除了插入上述代碼,還有一部分優化以及分析記憶體的管理工作。

作用:

  • a.降低記憶體泄露等風險 ; 
  • b.減少代碼工作量,使開發者只需專註於業務邏輯;

4.ARC具體為引用計數做了哪些工作?

編譯階段自動添加代碼:

編譯器會在編譯階段以恰當的時間與地方給我們填上原本需要手寫的retain、release、autorelease等記憶體管理代碼,所以ARC並非運行時的特性,也不是如java中的GC運行時的垃圾回收系統;因此,我們也可以知道,ARC其實是處於編譯器的特性。

例如:

-(void)setup
{
    _person = [person new];
}

在手工管理記憶體的環境下,_person是不會自動保留其值,而在ARC下編譯,其代碼會變成:

-(void)setup
{
    person *tmp = [person new];
    _person = [tmp retain];
    [tmp release];
}

當然,在開發工作中,retain和release對於開發人員來說都可以省去,由ARC系統自動補全,達到同樣的效果。

但實際上,ARC系統在自動調用這些方法時,並不通過普通的Objective-C消息派發控制,而是直接調用底層C語言的方法:

比如retain,ARC在分析到某處需要調用保留操作的地方,調用了與retain等價的底層函數 objc_retain,所以這也是ARC下不能覆寫retain、release或者autorelease的原因,因為這些方法在ARC從來不會被直接調用。

運行期組件的優化:

ARC是編譯器的特性,但也包含了運行期組件,所執行的優化很有意義。

例子:

person工廠方法personWithName可以得到一個person對象,在這裡調用並賦值給person的一個實例_one

_one = [person personWithName:@"name"];

可能會出現這種情況:

在personWithName方法中,返回對象給_one之前,為其調用了一次autorelease方法。

由於實例變數是個強引用,所以編譯器會在設置其值的時候還需要執行一次保留操作。  

person *tmp = [person personWithName:@"name"]; //在personWithName方法返回前已有調用一次autorelease方法進行保留操作;
_one = [tmp retain];

很明顯,autorelease與緊跟其後的retain是重覆的。為提升性能,可以將二者刪去,捨棄autorelease這個概念,並且規定返回對象的技術都比期望值多1,但是為了向後相容非ARC等情況,ARC採取另外一種方式:

ARC可以在運行期檢測到這一對多餘的操作。

  1. 返回對象時,不直接調用autorelease,改為調用objc_autoreleaseReturnValue,用來檢測返回之後即將要執行的代碼中,含有retain操作,則設置全局數據結構(此數據結構具體內容因處理器而異)中的一個標誌位,而不執行autorelease操作。
  2. 同樣,若方法返回一個自動釋放對象,調用personWithName方法的代碼段不執行retain,改為執行objc_retainAutoreleaseReturnValue函數。此函數檢測剛纔的那個標誌位,若已經置位了,則不執行retain操作。

而,設置並檢測標誌位,要比調用autorelease和retain更快,這就使得這一情況的處理得到優化。

修改2個函數後優化完整結果如下: 【例子來自《編寫高質量iOS與OS X代碼的52個有效方法》一書P126】

我們可以通過兩個函數的偽代碼大致描述如下:

    

像是objc_autoreleaseReturnValue這個函數是如何檢測方法調用者是否會立刻保留對象呢,這就要交給處理器來解決了。

由於必須查看原始機器碼指令方可判斷出這一點需要處理器來定。

所以,其實只有編譯器的作者才能知道這裡是如何實現此函數的。

ARC的安全性:

在編寫屬性的設置方法(setter)時,如果使用手工管理方式,可能會需要如下編寫:

-(void)setObject:(id)object
{
    [_object release];
    _object = [object retain];
}

但是這樣寫會出現問題:如果說新值object和實例變數_object的值是相同的,而且只有當前實例變數對象還在引用這個值,那麼設置方法中的釋放操作會使得該值保留計數為0,系統將其回收,所以接下來的保留操作,將會令應用程式崩潰。

而在使用ARC的環境下,就不可能會發送這樣的的“邊界情況”了:

剛纔的代碼在ARC下可以這樣寫(當然,我們知道如果不需要覆寫setter方法,也可以不編寫此方法,直接使用"self.object = xxx"也可以安全地調用。):

-(void)setObject:(id)object
{
    _object = object;
}

 而且ARC會用一種安全的方式來設置:先保留新值,再釋放舊值,最後設置實例變數。

在手工管理的情況下,我們需要特別註意這種"邊緣情況",但是ARC下,我們就可以很輕鬆地編寫這種代碼了,而不用去考慮這種情況如何處理了。

總結:將記憶體管理交由編譯器運行期組件來做,可以使代碼得到多種優化,而上面是其中一種方式。

 

5.ARC下需要註意的規則

不能顯式調用以下代碼:

 (NSZone:記憶體區)

不能再使用NSAutoreleasePool對象,ARC提供了@autoreleasepool塊來代替它,這樣更有效率;

關於dealloc:

  • 不能顯式調用dealloc;
  • 不能再dealloc中調用【super dealloc】(非ARC下則需要調用.);
  • 不能在dealloc 中釋放資源(非ARC下需要釋放不同的對象); 

6.所有權修飾符

oc編程中為了處理對象,可將變數類型定義為id類型或各種對象類型。使用這些限定符可以確切地聲明對象變數和屬性的生命周期; 

所謂對象類型就是指向NSObject這樣的oc類的指針,例如“NSObject *”。id類型用於隱藏對象類型的類名部分。相當於C語言中常用的“void *”;

ARC下,id類型和對象類型上必須附加所有權修飾符;

所有權修飾符一共有4種:

__strong:

強引用,可以引用別的對象為強引用,相當於retain的特性;表明變數持有alloc/new/copy/mutableCopy方法群創建的對象的強引用,強引用變數會在其作用域里被保留,在超出作用域後被釋放,為預設的修飾符;

例如以下代碼

id objc = [[NSObject alloc] init];

實際上已被附上所有權修飾符:

id __strong objc = [[NSObject alloc] init];

__weak:

使用__strong,有可能2個對象相互強引用或者1個對象對自身強引用則會發生迴圈引用(如下圖,或者叫保留環),所以當對象在超出其生存周期後,本應被系統廢棄卻仍然被引用者所持有,所以造成記憶體泄露(應當廢棄的對象在超出生命周期後,繼續存在);

         

而當我們對可能會發送迴圈引用的對象進行__weak弱引用修飾,弱引用變數不會持有對象,且生成的對象會立刻釋放,可避免迴圈引用,並且弱引用還有另外一個特點,若對象被系統回收,該弱引用變數將自動失效並且賦值為nil。

 __unsafe_unretained: 不安全的所有權徐師傅,ARC的記憶體管理是編譯器的工作,而附有__unsafe_unretained修飾符的變數不屬於編譯器的記憶體管理對象。與__weak作用一樣,也可以避免迴圈引用;但是不同的是,__unsafe_unretained屬性的變數不會將變數設置為nil,而是就處於於懸掛狀態;

 

__autoreleasing:在ARC中使用“@autoreleasepool塊”來取代“NSAutoreleasePool”類對象的生成,通過將對象賦值給附加了__autoreleasing修飾符的變數來替代調用autorelease方法; 

 

Other:ARC需要註意的事項?

1.過度使用 block 之後,無法解決迴圈引用問題。

2.遇到底層 Core Foundation 對象,需要自己手工管理它們的引用計數時,我們需轉換關鍵字,作為橋接轉換以解決 Core Foundation 對象與 Objective-C 對象相對轉換的問題:

__bridge:使用__bridge標記可以在不修改相關對象的引用計數的情況下,將對象從Core Foundation框架數據類型轉換為Foundation框架數據類型(反之亦然)。

__bridge_retained:會將相關對象的引用計數加 1,並且可以將Core Foundation框架數據類型對象轉換為Foundation框架數據類型對象,並從ARC接管對象的所有權。

__bridge_transfer:可以將Foundation框架數據類型對象轉換為Core Foundation框架數據類型對象,並且會將對象的所有權交給ARC管理,也就是說引用計數交由ARC管理;

 

總結:就推薦2本經典的書(估計很多人早就看完了

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

-Advertisement-
Play Games
更多相關文章
  • 設置導航欄nav全透明 - (void)viewDidLoad {裡面添加 swift:版本 // 1、設置視圖背景顏色 // self.view.backgroundColor = UIColor(white: 0.25, alpha: 1.0) // // // 2、設置導航欄標題屬性:設置標題 ...
  • 針對RecyclerView的頭部和底部,官方並沒有給我們提供像listView一樣可以直接通過addHeaderView()/addFooterView()的方法,所以只能靠我們自己去實現了,那怎麼實現呢?大家都知道RecyclerView已經為我們封裝好了Adapter和ViewHolder,在... ...
  • 在Android開發中,我們經常會需要在Android界面上彈出一些對話框,比如詢問用戶或者讓用戶選擇。這些功能我們叫它Android Dialog對話框,AlertDialog實現方法為建造者模式。下麵我們模擬卸載應用程式時彈出的最為普通的警告對話框,如下圖: layout佈局界面代碼示例: Ja ...
  • 作為開發者我們需要經常站在用戶角度考慮問題,比如在應用商城下載軟體時,當用戶點擊下載按鈕,則會有下載進度提示頁面出現,現在我們通過線程休眠的方式模擬下載進度更新的演示,如圖(這裡為了截圖方便設置對話進度條位於屏幕上方): layout界面代碼(僅部署一個按鈕): Java代碼實現(通過線程實現模擬下 ...
  • 最近隨視頻教程學習Android,原本都是用Adt寫Android程式,中途教程換成了Android Studio,於是我自己下了android studio 2.2.2安裝好,並下載好sdk,也跟著更換開發環境。 但教程並未講解如何將舊有的Eclipse項目導入到Android Studio(以下 ...
  • 在編寫xml文件時,為了預覽效果,經常會使用預設填上一些內容,比如TextView時,隨便寫上一個text 但是如果這個在實際發佈的時候忘記了刪除這個text,就有可能出現問題了 其實在Android Studio上,可以使用一個更加優雅和高效的方式,那就是使用tools屬性 首先添加tools的n ...
  • 寫一個計算的方法,計算1 + 2 。 面向對象,詮釋萬物皆對象思想。 思路: YGInteger.h YGInteger.m 中寫一個description方法,和 擴展中,將數字轉換成字元串的方法 YGInteger+YGInit.h 這是Integer的Init分類 YGInteger+YGIn ...
  • MobileCoreService這個系統的庫,裡面有個私有的類LSApplicationWorkspace ,利用運行時可以獲得私有類裡面的方法,- (id)allInstalledApplications; 該方法能夠獲得設備上所有的應用信息,包括系統的和用戶的應用 獲得的應用的信息是一個類對象 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...