首页
/
每日頭條
/
生活
/
循環引用如何查
循環引用如何查
更新时间:2024-10-21 05:45:13

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

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

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

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

而對于循環引用,這裡主要是講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
推荐阅读
兔子吃什麼
兔子吃什麼
1、兔子是草食性動物,喜歡吃的食物有:胡蘿蔔、紅薯、洋白菜、南瓜、菠菜、黃瓜、蘿蔔葉子、橘子、香蕉、葡萄、蘋果、車前草、蒲公英、鵝腸菜、豆腐渣、面包等等。對于有些食物不能大量食用:玉米、花生、馬鈴薯等。2、不能吃的食物:巧克力、咖啡、酒、洋蔥、韭菜、大蔥,大蒜等。特别要注意食物種的水分不要太多。否則會導緻兔子拉稀甚至死亡。喂食前應将食物洗幹淨,将表面的水分擦幹後在給兔子吃。煮過或者冷凍的食物都不可
2024-10-21
尺子上有什麼
尺子上有什麼
尺上有刻度,以量度長度。一些尺子,在中間留有特殊形狀如字母或圓形的洞,方便用者畫圖,尺子通常用于測量計算長度,一般分為卷尺,遊标卡尺,直角尺,直尺。“蛇仔尺”是專用來畫曲線的尺。三角尺是直角三角形或等腰三角形的尺,方便畫平行線或垂直線。計算尺:一種計算工具。軟尺:常用來量度人體部分。拉尺:用來量度建築物、家具等。遊标卡尺:用來量比較精小的東西。在尺規作圖中,尺被視為可畫無窮長的直線的工具。水平尺:
2024-10-21
除甲醛排名第一的植物
除甲醛排名第一的植物
龜背竹是除甲醛排名第一的。1、龜背竹。龜背竹可以吸收室内90%的甲醛中的甲苯,本身耐陰易存活,常用于...
2024-10-21
多面體的畫法
多面體的畫法
1、二十面體,由很多個三角形構成:(1)用線條勾勒出大概輪廓(6個邊);(2)在輪廓中按近大遠小畫出...
2024-10-21
空調制熱是30更熱還是16更熱
空調制熱是30更熱還是16更熱
1、空調制熱中30度溫度要比16度溫度高會更熱。2、空調制熱時溫度調節越高制熱效果越好,室内越暖和。...
2024-10-21
Copyright 2023-2024 - www.tftnews.com All Rights Reserved