iOS記憶體管理之MRC

来源:https://www.cnblogs.com/r360/archive/2022/08/08/16561589.html
-Advertisement-
Play Games

前言: 在iOS中,使用引用計數來管理OC對象記憶體 一個新創建的OC對象引用計數預設是1,當引用計數減為0,OC對象就會銷毀,釋放其占用的記憶體空間。 調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1。 記憶體管理的經驗總結 當調用alloc、new、copy、m ...


前言:

在iOS中,使用引用計數來管理OC對象記憶體
一個新創建的OC對象引用計數預設是1,當引用計數減為0,OC對象就會銷毀,釋放其占用的記憶體空間。
調用retain會讓OC對象的引用計數+1,調用release會讓OC對象的引用計數-1。

記憶體管理的經驗總結

當調用alloc、new、copy、mutableCopy方法返回了一個對象,在不需要這個對象時,要調用release或者autorelease釋放它。
想擁有某個對象,就讓他的引用計數+1;不想再擁有某個對象,就讓他的引用計數-1。

一、 MRC 手動管理記憶體(Manual Reference Counting)

1、引用計數器

引用計數器:
一個整數,表示為「對象被引用的次數」。系統需要根據對象的引用計數器來判斷對象是否需要被回收。

關於「引用計數器」,有以下幾個特點:

  • 每個 OC 對象都有自己的引用計數器。

  • 任何一個對象,剛創建的時候,初始的引用計數為 1。

    即使用 alloc、new 或者 copy 創建一個對象時,對象的引用計數器預設就是 1。

  • 當沒有任何人使用這個對象時,系統才會回收這個對象。也就是說:

    當對象的引用計數器為 0 時,對象占用的記憶體就會被系統回收。

    如果對象的引用計數器不為 0 時,那麼在整個程式運行過程,它占用的記憶體就不可能被回收(除非整個程式已經退出)。

2、引用計數器操作

  • 為保證對象的存在,每當創建引用到對象需要給對象發送一條 retain 消息,可以使引用計數器值 +1 ( retain 方法返回對象本身)。
  • 當不再需要對象時,通過給對象發送一條 release 消息,可以使引用計數器值 -1。
  • 給對象發送 retainCount 消息,可以獲得當前的引用計數器值。
  • 當對象的引用計數為 0 時,系統就知道這個對象不再需要使用了,所以可以釋放它的記憶體,通過給對象發送 dealloc 消息發起這個過程。
  • 需要註意的是:release 並不代表銷毀 / 回收對象,僅僅是將計數器 -1。
// 創建一個對象,預設引用計數器是 1
RHPerson *person1 = [[RHPerson alloc] init];
NSLog(@"retainCount = %zd", [person1 retainCount]);

// 只要給對象發送一條 retain 消息,引用計數器加1
[person1 retain];
NSLog(@"retainCount = %zd", [person1 retainCount]);

// 只要給對象發送一條 release 消息,引用計數器減1
[person1 release];
NSLog(@"retainCount = %zd", [person1 retainCount]);

// 當 retainCount 等於0時,對象被銷毀
[person1 release];

NSLog(@"--------------");
2022-07-11 16:09:24.102850+0800 Interview01-記憶體管理[8035:264221] retainCount = 1
2022-07-11 16:09:24.103083+0800 Interview01-記憶體管理[8035:264221] retainCount = 2
2022-07-11 16:09:24.103126+0800 Interview01-記憶體管理[8035:264221] retainCount = 1
2022-07-11 16:09:24.103231+0800 Interview01-記憶體管理[8035:264221] -[RHPerson dealloc]
2022-07-11 16:09:24.103259+0800 Interview01-記憶體管理[8035:264221] --------------
Program ended with exit code: 0

3、dealloc 方法

  • 當一個對象的引用計數器值為 0 時,這個對象即將被銷毀,其占用的記憶體被系統回收。
  • 對象即將被銷毀時系統會自動給對象發送一條 dealloc 消息(因此,從 dealloc 方法有沒有被調用,就可以判斷出對象是否被銷毀)
  • dealloc 方法的重寫(註意是在 MRC 中)
    • 一般會重寫 dealloc 方法,在這裡釋放相關資源,dealloc 就是對象的遺言
    • 一旦重寫了 dealloc 方法,就必須調用 [super dealloc],並且放在最後面調用
- (void)dealloc {
    NSLog(@"%s", __func__);
    [super dealloc];
}

dealloc 使用註意:

  • 不能直接調用 dealloc 方法。

  • 一旦對象被回收了, 它占用的記憶體就不再可用,堅持使用會導致程式崩潰(野指針錯誤)。

    4、野指針和空指針

  • 只要一個對象被釋放了,我們就稱這個對象為「僵屍對象(不能再使用的對象)」。

  • 當一個指針指向一個僵屍對象(不能再使用的對象),我們就稱這個指針為「野指針」。

  • 只要給一個野指針發送消息就會報錯(EXC_BAD_ACCESS 錯誤)。

RHPerson *person1 = [[RHPerson alloc] init];
[person1 release];
[person1 release];
[person1 release];
  • 為了避免給野指針發送消息會報錯,一般情況下,當一個對象被釋放後我們會將這個對象的指針設置為空指針。

  • 空指針:

    • 沒有指向存儲空間的指針(裡面存的是 nil, 也就是 0)。

    • 給空指針發消息是沒有任何反應的。

      RHPerson *person1 = [[RHPerson alloc] init];
      [person1 release];
      person1 = nil;
      [person1 release];
      

二、記憶體管理思想

1、單個對象記憶體管理思想

思想一:自己創建的對象,自己持有,自己負責釋放
  • 通過 allocnewcopymutableCopy 方法創建並持有對象。
  • 當自己持有的對象不再被需要時,必須調用 releaseautorelease 方法釋放對象。
id obj1 = [[NSObject alloc] init];
[obj1 release];

id obj2 = [NSObject new];
[obj2 release];
思想二:非自己創建的對象,自己也能持有
  • 除了用上面方法(alloc / new / copy / mutableCopy 方法)所取得的的對象,因為非自己生成並持有,所以自己不是該對象的持有者。
  • 通過調用 retain 方法,即便是非自己創建的對象,自己也能持有對象。
  • 同樣當自己持有的對象不再被需要時,必須調用 release 方法來釋放對象。
id obj3 = [NSArray array];
[obj3 retain];
[obj3 release];
  • 無論是否是自己創建的對象,自己都可以持有,並負責釋放。
  • 計數器有加就有減。
  • 曾經讓對象的計數器 +1,就必須在最後讓對象計數器 -1。

2、多個對象記憶體管理思想

多個對象之間往往是通過 setter 方法產生聯繫的,其記憶體管理的方法也是在 setter 方法、dealloc 方法中實現的。所以只有瞭解了 setter 方法是如何實現的,我們才能瞭解到多個對象之間的記憶體管理思想。

#import <Foundation/Foundation.h>

#import "RHRoom.h"

NS_ASSUME_NONNULL_BEGIN

@interface RHPerson : NSObject

{
    RHRoom *_room;
}

- (void)setRoom:(RHRoom *)room;

- (RHRoom *)room;
@end

NS_ASSUME_NONNULL_END
#import "RHPerson.h"

@implementation RHPerson

- (void)setRoom:(RHRoom *)room {
    if (_room != room) {
        [_room release];
        _room = [room retain];
    }
}

- (RHRoom *)room {
    return _room;
}

- (void)dealloc {
    [_room release];
    [super dealloc];
    NSLog(@"%s", __func__);
}

@end

三、 @property 參數

  • 在成員變數前加上 @property,系統就會自動幫我們生成基本的 setter / getter 方法,但是不會生成記憶體管理相關的代碼。
@property(nonatomic) int val;
  • 同樣如果在 property 後邊加上 assign,系統也不會幫我們生成 setter 方法記憶體管理的代碼,僅僅只會生成普通的 getter / setter 方法,預設什麼都不寫就是 assign
@property(nonatomic, assign) int val;
  • 如果在 property 後邊加上 retain,系統就會自動幫我們生成 getter / setter 方法記憶體管理的代碼,但是仍需要我們自己重寫 dealloc 方法。
@property(nonatomic, retain) RHRoom *room;

四、自動釋放池

1、自動釋放池

當我們不再使用一個對象的時候應該將其空間釋放,但是有時候我們不知道何時應該將其釋放。為瞭解決這個問題,Objective-C 提供了 autorelease 方法。

  • autorelease 是一種支持引用計數的記憶體管理方式,只要給對象發送一條 autorelease 消息,會將對象放到一個自動釋放池中,當自動釋放池被銷毀時,會對池子裡面的「所有對象」做一次 release 操作。

註意:這裡只是發送 release 消息,如果當時的引用計數(reference-counted)依然不為 0,則該對象依然不會被釋放。

  • autorelease 方法會返回對象本身,且調用完 autorelease 方法後,對象的計數器不變。
NSObject *obj = [NSObject new];
[obj autorelease];
NSLog(@"obj.retainCount = %zd", obj.retainCount);

2、使用 autorelease 有什麼好處呢?

  • 不用再關心對象釋放的時間
  • 不用再關心什麼時候調用release

3、autorelease 的原理實質上是什麼?

autorelease 實際上只是把對 release 的調用延遲了,對於每一個 autorelease,系統只是把該對象放入了當前的 autorelease pool 中,當該 pool 被釋放時,該 pool 中的所有對象會被調用 release 方法。

4、autorelease 的創建方法

// 第一種方式:使用 NSAutoreleasePool 創建
// 創建自動釋放池
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
// 銷毀自動釋放池
[pool release];
// 第二種方式:使用 @autoreleasepool 創建
@autoreleasepool {
// 開始代表創建自動釋放池
// 結束代表銷毀自動釋放池
}

5、autorelease 的使用方法

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
RHPerson *p = [[[RHPerson alloc] init] autorelease];
[pool release];
@autoreleasepool {
// 開始代表創建自動釋放池
RHPerson *p = [[[RHPerson alloc] init] autorelease];
// 結束代表銷毀自動釋放池
}

6、autorelease 的註意事項

  • 並不是放到自動釋放池代碼中,都會自動加入到自動釋放池
@autoreleasepool {
// 因為沒有調用 autorelease,所以沒有加入到自動釋放池中
RHPerson *p = [[RHPerson alloc] init];
// 結束代表銷毀自動釋放池
}
  • 在自動釋放池的外部發送 autorelease 不會被加入到自動釋放池中
    • autorelease 是一個方法,只有在自動釋放池中調用才有效。
@autoreleasepool {
}
// 沒有與之對應的自動釋放池, 只有在自動釋放池中調用autorelease才會放到釋放池
Person *p = [[[Person alloc] init] autorelease];
[p run];
 
// 正確寫法
@autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
 }
 
// 正確寫法
Person *p = [[Person alloc] init];
@autoreleasepool {
    [p autorelease];
}

7、自動釋放池的嵌套使用

  • 自動釋放池是以棧的形式存在。
  • 由於棧只有一個入口,所以調用 autorelease 會將對象放到棧頂的自動釋放池。

棧頂就是離調用 autorelease 方法最近的自動釋放池。

@autoreleasepool { // 棧底自動釋放池
    @autoreleasepool {
        @autoreleasepool { // 棧頂自動釋放池
            Person *p = [[[Person alloc] init] autorelease];
        }
        Person *p = [[[Person alloc] init] autorelease];
    }
}
  • 自動釋放池中不適宜放占用記憶體比較大的對象。
    • 儘量避免對大記憶體使用該方法,對於這種延遲釋放機制,還是儘量少用。
    • 不要把大量迴圈操作放到同一個 @autoreleasepool 之間,這樣會造成記憶體峰值的上升。
// 記憶體暴漲
@autoreleasepool {
    for (int i = 0; i < 99999; ++i) {
        Person *p = [[[Person alloc] init] autorelease];
    }
}
// 記憶體不會暴漲
for (int i = 0; i < 99999; ++i) {
    @autoreleasepool {
        Person *p = [[[Person alloc] init] autorelease];
    }
}

8、autorelease 錯誤用法

  • 不要連續調用 autorelease
@autoreleasepool {
 // 錯誤寫法, 過度釋放
    Person *p = [[[[Person alloc] init] autorelease] autorelease];
 }
  • 調用 autorelease 後又調用 release(錯誤)。
@autoreleasepool {
    Person *p = [[[Person alloc] init] autorelease];
    [p release]; // 錯誤寫法, 過度釋放
}

五、 MRC 中避免迴圈引用

定義兩個類 Person 類和 Dog 類

  • Person 類:
#import <Foundation/Foundation.h>
@class Dog;
 
@interface Person : NSObject
@property(nonatomic, retain)Dog *dog;
@end
  • Dog 類:
#import <Foundation/Foundation.h>
@class Person;
 
@interface Dog : NSObject
@property(nonatomic, retain)Person *owner;
@end

執行以下代碼:

int main(int argc, const char * argv[]) {
    Person *p = [Person new];
    Dog *d = [Dog new];
 
    p.dog = d; // retain
    d.owner = p; // retain  assign
 
    [p release];
    [d release];
 
    return 0;
}

就會出現 A 對象要擁有 B 對象,而 B 對應又要擁有 A 對象,此時會形成迴圈 retain,導致 A 對象和 B 對象永遠無法釋放。

那麼如何解決這個問題呢?

不要讓 A retain B,B retain A。
讓其中一方不要做 retain 操作即可。
當兩端互相引用時,應該一端用 retain,一端用 assign。


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

-Advertisement-
Play Games
更多相關文章
  • 定義: 刪除數據表就是將資料庫中已經存在的表從資料庫中刪除。註意,在刪除表的同時,表的定義和表中所有的數據均會被刪除。因此,在進行刪除操作前,最好對錶中的數據做一個備份,以免造成無法輓回的後果。本節將詳細講解資料庫表的刪除方法。 1 刪除一個或多個沒有被其他表關聯的數據表 如果一個數據表沒有和其它表 ...
  • Orange是提高工作便利性的 集成開發工具 可以極大方便資料庫管理和 用戶的數據訪問進程 Orange是 DB運營管理和開發解決方案 通過方便而又強大的功能 快速可視化編程前端 以便瀏覽數據分析和可視化 開發商介紹 Ware Valley成立於2001年,是一家全球性的軟體公司,也是南韓唯一一個為 ...
  • 更多技術交流、求職機會、試用福利,歡迎關註位元組跳動數據平臺微信公眾號,回覆【1】進入官方交流群 序言 埋點數據作為推薦、搜索、產品優化的基石,其數據質量的重要性不言而喻,而要保障埋點數據的質量,埋點驗證則首當其衝。工欲善其事必先利其器,要做好埋點驗證會面臨很多技術挑戰:易用性、準確性、實時性、穩定性 ...
  • 一、數字營銷是數字化轉型排頭兵 《⼗四五數字經濟發展規劃》中強調,要⼤⼒推進數字化轉型,形成數據驅動的智能決策能⼒,提升企業整體運營效率。 要做好數字化轉型,企業可從產、研、供、銷、⽤等多個環節入手,而 “銷” 恰好是第一關鍵要素,企業轉型往往從營銷場景入手,因此我們說數字化營銷是企業數字化轉型的排 ...
  • vivo 互聯網伺服器團隊 - Wang Zhi 一、業務背景 從技術的角度來說,技術方案的選型都是受限於實際的業務場景,都以解決實際業務場景為目標。 在我們的實際業務場景中,需要以游戲的維度收集和上報行為數據,考慮數據的量級,執行盡最大努力交付且允許數據的部分丟棄。 數據上報支持游戲的維度的批量上 ...
  • MySQL源碼解析之執行計劃 MySQL執行計劃介紹 MySQL執行計劃代碼概覽 MySQL執行計劃總結 一、MySQL執行計劃介紹 在MySQL中,執行計劃的實現是基於JOIN和QEP_TAB這兩個對象。其中JOIN類表示一個查詢語句塊的優化和執行,每個select查詢語句(即Query_bloc ...
  • 1.ETCD概述 1.1 ETCD概述 etcd是一個高可用的分散式的鍵值對存儲系統,常用做配置共用和服務發現。由CoreOS公司發起的一個開源項目,受到ZooKeeper與doozer啟發而催生的項目,名稱etcd源自兩個想法,即Linux的**/etc文件夾和d分散式系統。/etc**文件夾是用 ...
  • 首先測試電腦的win鍵是否已經失靈:調出電腦軟鍵盤(具體方式可以自行百度),按下win鍵看電腦是否有反應,多試幾次。 在確定不是電腦鍵盤失靈的前提下,對win鍵進行恢復方法如下: 方法一:長按Fn鍵,再按下win鍵。 方法二:(1)打開任務管理器(在任務欄右鍵滑鼠)。 (2)找到“桌面視窗管理器”, ...
一周排行
    -Advertisement-
    Play Games
  • Timer是什麼 Timer 是一種用於創建定期粒度行為的機制。 與標準的 .NET System.Threading.Timer 類相似,Orleans 的 Timer 允許在一段時間後執行特定的操作,或者在特定的時間間隔內重覆執行操作。 它在分散式系統中具有重要作用,特別是在處理需要周期性執行的 ...
  • 前言 相信很多做WPF開發的小伙伴都遇到過表格類的需求,雖然現有的Grid控制項也能實現,但是使用起來的體驗感並不好,比如要實現一個Excel中的表格效果,估計你能想到的第一個方法就是套Border控制項,用這種方法你需要控制每個Border的邊框,並且在一堆Bordr中找到Grid.Row,Grid. ...
  • .NET C#程式啟動閃退,目錄導致的問題 這是第2次踩這個坑了,很小的編程細節,容易忽略,所以寫個博客,分享給大家。 1.第一次坑:是windows 系統把程式運行成服務,找不到配置文件,原因是以服務運行它的工作目錄是在C:\Windows\System32 2.本次坑:WPF桌面程式通過註冊表設 ...
  • 在分散式系統中,數據的持久化是至關重要的一環。 Orleans 7 引入了強大的持久化功能,使得在分散式環境下管理數據變得更加輕鬆和可靠。 本文將介紹什麼是 Orleans 7 的持久化,如何設置它以及相應的代碼示例。 什麼是 Orleans 7 的持久化? Orleans 7 的持久化是指將 Or ...
  • 前言 .NET Feature Management 是一個用於管理應用程式功能的庫,它可以幫助開發人員在應用程式中輕鬆地添加、移除和管理功能。使用 Feature Management,開發人員可以根據不同用戶、環境或其他條件來動態地控制應用程式中的功能。這使得開發人員可以更靈活地管理應用程式的功 ...
  • 在 WPF 應用程式中,拖放操作是實現用戶交互的重要組成部分。通過拖放操作,用戶可以輕鬆地將數據從一個位置移動到另一個位置,或者將控制項從一個容器移動到另一個容器。然而,WPF 中預設的拖放操作可能並不是那麼好用。為瞭解決這個問題,我們可以自定義一個 Panel 來實現更簡單的拖拽操作。 自定義 Pa ...
  • 在實際使用中,由於涉及到不同編程語言之間互相調用,導致C++ 中的OpenCV與C#中的OpenCvSharp 圖像數據在不同編程語言之間難以有效傳遞。在本文中我們將結合OpenCvSharp源碼實現原理,探究兩種數據之間的通信方式。 ...
  • 一、前言 這是一篇搭建許可權管理系統的系列文章。 隨著網路的發展,信息安全對應任何企業來說都越發的重要,而本系列文章將和大家一起一步一步搭建一個全新的許可權管理系統。 說明:由於搭建一個全新的項目過於繁瑣,所有作者將挑選核心代碼和核心思路進行分享。 二、技術選擇 三、開始設計 1、自主搭建vue前端和. ...
  • Csharper中的表達式樹 這節課來瞭解一下表示式樹是什麼? 在C#中,表達式樹是一種數據結構,它可以表示一些代碼塊,如Lambda表達式或查詢表達式。表達式樹使你能夠查看和操作數據,就像你可以查看和操作代碼一樣。它們通常用於創建動態查詢和解析表達式。 一、認識表達式樹 為什麼要這樣說?它和委托有 ...
  • 在使用Django等框架來操作MySQL時,實際上底層還是通過Python來操作的,首先需要安裝一個驅動程式,在Python3中,驅動程式有多種選擇,比如有pymysql以及mysqlclient等。使用pip命令安裝mysqlclient失敗應如何解決? 安裝的python版本說明 機器同時安裝了 ...