循环引用如何查(自动引用计数与循环引用)
循环引用如何查(自动引用计数与循环引用)
2024-11-21 07:52:22  作者:许仙白素贞  网址:https://m.xinb2b.cn/tech/psc433906.html

本文首发于公众号【程序员华仔】

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

本文主要讲解 自动引用计数循环引用 这两个大问题。

对于自动引用计数,没有什么争议。

而对于循环引用,这里主要是讲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)。如下图。


同样地,这种强引用关系就包含了循环引用。即john对象持有了johnCar,johnCar也持有了john。 如下图。


当我们要释放john 和johnCar时,引用计数并不会降为 0,对象也不会被 ARC 销毁,这样就导致了内存泄露。

john = nil;

johnCar = nil;

未释放对象。

解决类对象的循环引用

针对上面的问题,在OC中,主要使用弱引用来解决循环引用问题。

弱引用

弱引用不会对其引用的对象进行强引用,因而不会阻止 ARC 销毁被引用的对象。

这个特性阻止了引用变为循环引用。我们只要在声明属性或者变量时,在前面加上 weak 关键字,就表明这是一个弱引用。

在底层的实现上,系统维护着一个弱引用表,每一次声明弱应用变量或属性都会在这张表中登记, 当对弱引用变量赋值时,就在这张表中建立起弱引用与对象之间的关系。有多少次赋值,就会建立多少次弱引用关系。

由于弱引用不会持有所引用的对象,即使引用存在,对象也有可能被销毁。因此,ARC 会在引用的对象被销毁后自动将其弱引用赋值为 nil,这个操作是为了对象的安全。

这样,对于上述的john和johnCar的循环引用问题,我们只要一方使用弱引用,就可以解除循环引用的问题。如下图所示:


在person释放的时候,可以不用等待Car的持有关系,因为它是弱引用。这种方式就解决了循环引用的问题,避免了内存泄露。

最后说明下:

关于循环引用有:1.类对象的循环引用;2.委托的循环引用;3.block的循环引用三个场景。

这次主要介绍类对象的循环引用和解决方法。

对于委托的循环引用,根本原因和类对象一样的(相互持有),解决方法就是在声明处使用weak关键字就可以了。

对于block的循环引用,内容比较多,见我的另一文章【探究Block底层原理(三)】

  • 职场称呼正确和错误示范(被列为职场三大)
  • 2024-11-22被列为职场三大要要到毕业季,很多稚嫩的应届毕业生加入职场小白队伍,尤其是对职场前辈的称呼,叫“姐”是对的,叫“姐姐”就翻脸,职场称呼,套路太深!明明是尊称,为何不受待见?我们一起来看看吧!文/小道职场禁词1:“姐姐。
  • 孩子上学梦到在学校(孩子都要上学了)
  • 2024-11-22孩子都要上学了最近这是怎么了?谁能告诉我!前天晚上,梦见语文考试本来是连答案一起发下来的(估计是老师发错了),结果我的中国好同桌抄的太嗨,被老师给没收了以至于我好多题答不上来,别提有多着急了!试卷不怪我不会,出题也。
  • 青海省西宁市好消息(青海西宁再次上榜)
  • 2024-11-22青海西宁再次上榜“这几天的青海太美了!”“西宁夜色让人陶醉!”“青海西宁再次上榜实至名归!”来自携程集团的最新数据显示:今年6月、7月两个月,最受游客喜爱的私家团目的地城市有:乌鲁木齐、呼伦贝尔、丽江、贵阳、三亚、西。
  • 红高粱罗汉宋佳伦走红(红高粱火热荧屏)
  • 2024-11-22红高粱火热荧屏宋佳伦饰演罗汉被赞道德楷模宋佳伦秦海璐剧照娄底新闻网讯传奇大戏《红高粱》正在北京、山东、浙江,东方四大卫视热播中并同时取得收视前五的好成绩,可谓火热荧屏剧中,宋佳伦饰演的罗汉有礼有节有着仁义礼智信,无。
  • 张棪琰恋爱史(张棪琰的第六感有多强)
  • 2024-11-22张棪琰的第六感有多强昨日,由潘粤明、王鸥领衔主演,张棪琰、马文忠、张凯丽、董璇特别出演的都市剧《新居之约》与大家见面了剧中,张棪琰饰演了一位个性张扬,勇敢追爱的小区户主朱迪衣着光鲜的她,本开心地挽着老公的胳膊,一起看房,。
  • 毛晓慧今年几岁了(毛晓慧是和刘亦菲杠上了)
  • 2024-11-22毛晓慧是和刘亦菲杠上了眼瞅着冬天就要来惹,大噶有没有准备穿秋裤了呢?~《演员请就位》大噶知道的吧,祝绪丹、毛晓慧、佟梦实三人合作了《仙剑一》,分别饰演林月如、赵灵儿、李逍遥…毛晓慧和佟梦实大噶可还记得吧,之前他俩演了新版《。
  • 百家姓中有没有黑姓(黑难老)
  • 2024-11-22黑难老姓名是姓氏加名字,名字里的建国、援朝、开放等,已经被刚、强、伟等取代,而梓涵、一诺、浩宇等又带着时代特色被人们叫起虽然人们的名字在变,姓氏却一直在不变地传承01新“百家姓”2019年1月,公安部户政管。
  • 最想合作的三个女演员(出道就演女一号)
  • 2024-11-22出道就演女一号说到娱乐圈中的影视行业,最辛苦的就是武打戏和军旅大戏了,当年拍摄时间最长和最辛苦的就是康洪雷导演的《士兵突击》了,王宝强、陈思成、张译,如今都已经大红大紫了,有的成了一线大明星,有的成为了导演,除了这。
  • 赵露思空气刘海直发(剪了刘海的赵露思)
  • 2024-11-22剪了刘海的赵露思不知道最近大家有没有刷到赵露思新剧《偷偷藏不住》的这几张路透图照片中她留着齐刘海,扎着高马尾,身穿白衬衫,阳光又温柔,萌萌觉得这才是我心中的桑稚~而且不得肉丝不说,赵露思的身高和气质简直就是甜妹本甜,。
  • 关于世界十大无敌战神排行榜(韩国评出亚洲第一战神)
  • 2024-11-22韩国评出亚洲第一战神说起韩国这个小国啊,如今的许多中国人可能都会露出迷之微笑,要从经济水平上来说说它怎么着也是个发达国家了,可是要从它其他各种操作来说,可能连及格都算不上,更别说什么发达了中国人向来都奉行一句话“人不犯我。
  • 广州金媒人相亲活动(金媒人相亲活动)
  • 2024-11-22金媒人相亲活动一个人的修养高低,是可以通过言谈举止、待人接物、衣着打扮、爱好品位、文化素养等等各个方面判断的而交谈是建立良好人际关系的重要途径,是连接人与人之间的桥梁在相亲活动中,男生和女生通过面对面交谈,从而可以。