首页
/
每日頭條
/
生活
/
循環引用如何查
循環引用如何查
更新时间:2025-12-19 07:10:31

本文首發于公衆号【程序員華仔】

------------------------

本文主要講解 自動引用計數循環引用 這兩個大問題。

對于自動引用計數,沒有什麼争議。

而對于循環引用,這裡主要是講Object-C語言下的循環引用, 因為據我了解,Swift語言下也有循環引用。這兩者根本原因是一緻的,但解決方法有很大的差異。 所以這裡特别說明是Object-C語言下的循環引用。對于Swift下的循環引用,以後再講解。

自動引用計數

概念

說自動引用計數之前,先說下引用計數,引用計數是蘋果公司設計出來,用來跟蹤和管理App的内存情況的一套機制。它的運行機制大概是,當創建一個類對象,引用計數就為1,retain一次,計數 1,release一次,計數-1, 當計數減為0,系統就釋放該對象,内存也回收,實現了對App的内存管理。

引用計數又分為:手動引用計數(Manual Reference Count)自動引用計數(Auto Reference Count),前者簡稱MRC,後者為ARC

MRC在iOS開發前期使用。主要由開發人員來管理引用計數。即要用的時候,retain一次,要釋放的時候release一次。直到引用計數為零,系統才會釋放對象,回收内存。

ARC是後來才推出的内存管理機制,它簡化了流程。引用計數不需要開發人員來管理和維護了,全由系統幫助完成。 簡單的來說,ARC無須開發人員考慮内存的管理情況,它會在類對象被引用的時候,引用計數 1;在release的時候,引用計數-1;在類對象不再使用的時候,自動釋放其占用的内存。讓開發人員從複雜的内存管理中解脫出來,大大地提高了開發人員的效率。

顯然地,ARC更好用。那這麼好用的ARC,我們再深入講下它的工作機制。

自動引用計數的工作機制

正如前面所說一樣,每當創建一個新的類對象時,ARC就會分配一塊内存來儲存該對象的信息。内存中會包含對象的類型信息,同時引用計數器會 1 。

當其他對象引用這個類對象時,計數再 1,若一直引用,那引用計數就一直累加。

當引用的類對象設置為nil,内部實現就是調用release一次,引用計數就會-1。多個類對象設置為nil, 就會減少多個計數值。

當對象不再使用了,ARC 就會釋放對象所占用的内存,并讓釋放的内存能挪作他用。這确保了不再被使用的對象,不會一直占用内存空間。當然它的判斷條件就是引用計數是否為零。為零就釋放内存。

當 ARC 釋放了類對象,類對象所對應的方法和屬性将不能再被調用。否則App就會崩潰。

自動引用計數内部實現機制

在引用計數的底層實現中,是通過維護一個引用計數表來跟蹤和計算每一個對象的引用情況。其中表裡的key為内存塊地址的散列值,value為該對象的引用計數。

每引用一次,value的值就 1, 同樣的release一次,value值就-1,隻有value值為0,才會銷毀該對象并從引用計數表移除該key和value。

上述的對象引用過程,其實就是對對象的強引用。之所以稱之為“強”引用,是因為它會将對象牢牢地保持住,隻要強引用還在,對象是不允許被銷毀的。

在實際開發過程中,聲明一個類對象,默認就是強引用。

Person *person1;

該聲明等價于

__strong Person *person1;

自動引用計數的實踐

下面的例子展示ARC的工作機制。

1.創建一個Person類,定義name屬性。在init和dealloc函數上分别打印init和dealloc的日志。

#import <Foundation/Foundation.h>

@interface Person : NSObject {

}

@property(nonatomic,retain) NSString *name;

@end

@implementation Person

- (id)init {

self = [super init];

NSLog(@"Person is being init");

return self;

}

- (void)dealloc {

NSLog(@"Person is being dealloc");

}

@end

2.然後在ViewdidLoad函數中定義 三個對象

Person *person1;

Person *person2;

Person *person3;

由于三個對象沒有賦值,所以為空的。

這三個對象是強引用關系,因為它們等價于

__strong Person *person1;

__strong Person *person2;

__strong Person *person3;

3.接着創建Person對象并賦值給person1。

person1 = [[Person alloc] init];

通過這一段代碼創建了一個Person對象,并把對象賦值給person1。也就是說person1強引用了Person對象。

在引用計數表中,就實現了 1操作。

4.接着在把person1賦值給person2,person3。實現person2,person3的強引用。

person2 = person1;

person3 = person1;

注:在實際開發中不會這麼分解寫代碼。這裡隻是方便解說。正常的寫法是2,3,4一起完成,如下代碼所示:

Person *person1 = [[Person alloc] init];

Person *person2 = person1;

Person *person3 = person1;

5.對person1和person2對象進行釋放操作。

person1 = nil;

person2 = nil;

通過這兩行代碼操作,Person對象内存釋放嗎? 顯然沒有。因為person3還持有該引用。

6.最後,person3設置為nil, 引用計數為0,ARC 才會銷毀Person對象,回收内存。

以上就是自動引用計數的使用過程。

類對象間的循環引用

在上面的例子中,ARC 會跟蹤新創建的Person對象的引用計數,并且會在 Person對象不再被需要時銷毀它。

然而,我們可能會寫出一個類對象的引用計數永遠不能為0 的代碼。即如果兩個對象互相持有對方,每個對象都讓對方一直存在,這種情況就是所謂的循環引用。

下面以具體類來說明下。

1.新建兩個類,Person和Car

Class Car;

@interface Person : NSObject {

}

@property(nonatomic,retain) NSString *name;

@property(nonatomic,retain) Car *car;

@end

Class Person;

@interface Car : NSObject {

}

@property(nonatomic,retain) NSString *color;

@property(nonatomic,retain) Person *person;

@end

上述Person類中有兩個屬性,name表示“Person”對應的姓名,car表示“Person”對應的一輛車。假設這“Person”對象隻有姓名和車。

同樣地,Car對象下,color和person屬性,對應的就是車的款式(以顔色代替)和車主。

2.創建類對應的實例并給屬性賦值。

Person * john = [[Person alloc] init];

Car * johnCar = [[Car alloc] init];

john.name = “John”;

john.car = johnCar;

johnCar.color = “red”;

johnCar.person = john;

以上代碼,在兩個對象被創建和賦值後,就表現了強引用的關系。對象john現在有一個指向Car對象的強引用(johnCar),而變對象johnCar 有一個指向 Person 對象的強引用(car)。如下圖。

循環引用如何查(自動引用計數與循環引用)1

同樣地,這種強引用關系就包含了循環引用。即john對象持有了johnCar,johnCar也持有了john。 如下圖。

循環引用如何查(自動引用計數與循環引用)2

當我們要釋放john 和johnCar時,引用計數并不會降為 0,對象也不會被 ARC 銷毀,這樣就導緻了内存洩露。

john = nil;

johnCar = nil;

未釋放對象。

解決類對象的循環引用

針對上面的問題,在OC中,主要使用弱引用來解決循環引用問題。

弱引用

弱引用不會對其引用的對象進行強引用,因而不會阻止 ARC 銷毀被引用的對象。

這個特性阻止了引用變為循環引用。我們隻要在聲明屬性或者變量時,在前面加上 weak 關鍵字,就表明這是一個弱引用。

在底層的實現上,系統維護着一個弱引用表,每一次聲明弱應用變量或屬性都會在這張表中登記, 當對弱引用變量賦值時,就在這張表中建立起弱引用與對象之間的關系。有多少次賦值,就會建立多少次弱引用關系。

由于弱引用不會持有所引用的對象,即使引用存在,對象也有可能被銷毀。因此,ARC 會在引用的對象被銷毀後自動将其弱引用賦值為 nil,這個操作是為了對象的安全。

這樣,對于上述的john和johnCar的循環引用問題,我們隻要一方使用弱引用,就可以解除循環引用的問題。如下圖所示:

循環引用如何查(自動引用計數與循環引用)3

在person釋放的時候,可以不用等待Car的持有關系,因為它是弱引用。這種方式就解決了循環引用的問題,避免了内存洩露。

最後說明下:

關于循環引用有:1.類對象的循環引用;2.委托的循環引用;3.block的循環引用三個場景。

這次主要介紹類對象的循環引用和解決方法。

對于委托的循環引用,根本原因和類對象一樣的(相互持有),解決方法就是在聲明處使用weak關鍵字就可以了。

對于block的循環引用,内容比較多,見我的另一文章【探究Block底層原理(三)】

,
Comments
Welcome to tft每日頭條 comments! Please keep conversations courteous and on-topic. To fosterproductive and respectful conversations, you may see comments from our Community Managers.
Sign up to post
Sort by
Show More Comments
推荐阅读
好吃又不貴的四種魚
好吃又不貴的四種魚
“一年之計在于春”,春天冰雪消融、萬物複蘇,正是養生的好時節,又是孩子長個的“黃金期”,面對如此美麗的春天,操碎心的老媽們又開始忙着給孩子補充各種營養,春季飲食要注意清淡,不要過度食用辛辣的食物,要多吃應季的水果蔬菜,還要補充足夠的蛋白質,...
2025-12-19
物理和玄學有什麼區别
物理和玄學有什麼區别
我是吟風者,歡迎您關注我。熱力學第二定律告訴我們宇宙是熵增的,可産生于自然的生命确是反熵增的,如果道的弱化是熵增,産生萬物的自然卻是反熵增,那麼道法自然該是不對的嗎?首先要說一下,我們不能拿現在的科學理論去套以前人的思念。熵增可以理解為秩序...
2025-12-19
醫保卡辦理
醫保卡辦理
醫保卡辦理?去社保局辦理參保本人近期二寸免冠彩照兩張,如參保人是學齡前少年兒童,則提供母子或父子近期二寸免冠彩照兩張,今天小編就來聊一聊關于醫保卡辦理?接下來我們就一起去研究一下吧!醫保卡辦理去社保局辦理。參保本人近期二寸免冠彩照兩張,如參...
2025-12-19
新概念英語第一冊55課一課一練
新概念英語第一冊55課一課一練
【往期回顧】一課一題一語法:新概念英語第1冊第99-100課下面是第99-100課的參考答案:【答案】1.我認為這個蛋糕很好吃。Ithinkthatthecakeisverydelicious.2.恐怕他無法參加明天的運動會了。I'...
2025-12-19
奧海120w桌面充電器評測
奧海120w桌面充電器評測
Hello,大家好!今天我們為大家開箱評測Aohi奧海100W、65W移動電源!畢竟手機等電子産品作為我們現在的随身物品,出門在外幾乎到哪裡都離不開手機了。我們買東西付錢,尋找路線等等我們也都需要手機的參與,但很多時候,我們用手機的次數過多...
2025-12-19
Copyright 2023-2025 - www.tftnews.com All Rights Reserved