【iOS】——MRC

一、引用计数

内存管理的核心是引用计数器,用一个整数来表示对象被引用的次数,系统需要根据引用计数器来判断对象是否需要被回收。

在每次 RunLoop 迭代结束后,都会检查对象的引用计数器,如果引用计数器等于 0,则说明该对象没有地方继续使用它了,可以将其释放掉。

引用计数器特点如下:

  • 每个对象都有引用计数器
  • 任何一个对象,刚创建的时候,初始的引用计数为 1
  • 没有任何对象持有该对象的引用计数时,也就是对象的引用计数为0,回收该对象
  • 如果对象的引用计数始终不为0,则始终不会被回收除非程序停止运行
对象操作对应方法
生成并持有对象alloc/new/copy/mutableCopy方法
持有对象retain方法
释放对象release方法
废弃对象dealloc方法
返回对象引用引用计数retainCount方法
int main(int argc, const char * argv[]) {@autoreleasepool {// 只要创建一个对象默认引用计数器的值就是 1。Person *p = [[Person alloc] init];NSLog(@"retainCount = %lu", [p retainCount]); // 打印 1// 只要给对象发送一个 retain 消息, 对象的引用计数器就会 +1。[p retain];NSLog(@"retainCount = %lu", [p retainCount]); // 打印 2// 通过指针变量 p,给 p 指向的对象发送一条 release 消息。// 只要对象接收到 release 消息, 引用计数器就会 -1。// 只要对象的引用计数器为 0, 系统就会释放对象。[p release];// 需要注意的是: release 并不代表销毁 / 回收对象, 仅仅是将计数器 -1。NSLog(@"retainCount = %lu", [p retainCount]); // 1[p release]; // 0NSLog(@"--------");}return 0;
}

对象即将被销毁时会调用dealloc方法,因此可通过dealloc方法有没有被调用,就可以判断出对象是否被销毁

重写了 dealloc 方法,就必须调用 [super dealloc],并且放在最后面调用,重写dealloc方法应在类的实现中进行,不能直接调用 dealloc 方法

- (void)dealloc {NSLog(@"Person dealloc");// 注意:super dealloc 一定要写到所有代码的最后面[super dealloc]; 
}

二、野指针和空指针

被释放的对象称为僵尸对象,此时对象不能被使用

指向僵尸对象的指针称为野指针

给一个野指针发送消息就会报错(EXC_BAD_ACCESS 错误)

没有指向存储空间的指针称为空指针(里面存的是 nil, 也就是 0)

在这里插入图片描述

为了避免给野指针发送消息会报错,一般情况下,当一个对象被释放后我们会将这个对象的指针设置为空指针

三、内存管理思想

单个对象内存管理思想

  • 自己创建的对象自己持有

alloc\new\copy\mutableCopy 方法名开头来创建的对象意味着自己生成的对象只有自己持有。

id obj = [[NSObject alloc] init];
  • 不是自己创建的对象自己也能持有

用 alloc / new / copy / mutableCopy 以外的方法取得的对象,因为非自己生成并持有,所以自己不是该对象的持有者。(比如 NSMutableArray 类的 array方法),但是我们可以通过retain来手动持有对象。

//取得的对象存在但不持有
id obj = [NSMutableArray array];
//持有该对象
[obj retain];
  • 不再需要自己持有的对象就将其释放

释放自己生成并持有的对象

//自己持有对象
id obj = [[NSObject alloc] init];
//释放对象
//指向对象的指针仍然被保留在变量obj中,貌似可以访问,但对象一经释放绝对不可访问
[obj release];

释放非自己生成但持有的对象

id obj = [NSMutableArray array];
//持有该对象
[obj retain];[obj release];
  • 无法释放不是自己持有的对象

如果不是自己持有的对象一定不能进行释放,倘若在应用程序中释放了非自己所持有的对象就会造成崩溃。

多个对象内存管理思想

多个对象之间往往是通过 setter 方法产生联系的,通过setter方法让一个对象持有另一个对象的引用计数,其内存管理的方法也是在 setter 方法、dealloc 方法中实现的。

多个对象内存管理分为以下几种情况:

1.一个对象不持有另外一个对象

此种情况两个实例对象没有任何关联因此可以将各自释放掉

2.一个对象持有另外一个对象

此种情况通过setter方法将对象赋值给另外一个对象的成员变量,因此会让被持有对象的引用次数加1,所以就需要在setter方法中使用retain方法,为了保证该对象释放同时另外一个对象也能释放,再将另外一个对象引用计数减一

- (void)setMyObject:(MyObject*)myObj { // 引用计数器 +1[myObj retain];_myObj = myObj;
}

当前对象要释放时因为还持有别的对象所以还要将持有的对象进行销毁

- (void)dealloc {// 人释放了, 那么房间也需要释放[_myObj release];NSLog(@"%s", __func__);[super dealloc];
}

3.一个对象持有并释放掉一个对象后持有另外一个对象

此种情况如果还使用先前的getter方法,不难发现将第一个对象赋值给该对象时第一个对象的引用计数加一,再将第一个对象引用计数减一后将第二个对象赋值给该对象时第二个对象的引用计数加一,再将第二个对象引用计数减一,释放该对象会让第二个对象的引用计数减一但是第一个对象则不会减一,因此会造成第一个对象无法被释放掉。

因此就需要修改setter方法每当我们需要更换持有的对象时就让原来持有的对象的引用计数减一。

- (void)setMyObject:(MyObject*)myObj { [_myObj release];// 引用计数器 +1[myObj retain];_myObj = myObj;
}
4. 一个对象持有一个对象并释放该对象后再次持有该对象

此种情况使用上一个getter方法不难发现,我们将那个对象赋值给该对象后该对象对那个对象的引用计数加一,再将那个对象引用计数减一,此时两个对象的引用计数相同,此时再次进行赋值会导致那个对象的引用计数先减一此时为0变成了野指针,此时再对野指针进行retain操作就会报错。

此时就需要在setter方法中判断是否重复赋值,如果是同一个实例对象,就不需要重复进行 releaseretain

- (void)setMyObject:(MyObject*)myObj { if (_myObj != myObj) {[_myObj release];// 引用计数器 +1[myObj retain];_myObj = myObj;}
}

因为 retain 不仅仅会对引用计数器 +1, 而且还会返回当前对象,所以上述代码可最终简化成:

- (void)setMyObject:(MyObject*)myObj { if (_myObj != myObj) {[_myObj release];// 引用计数器 +1_myObj = [myObj retain];}
}

四、 @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)MyObject *myObj;

五、自动释放池

当我们不再使用一个对象的时候应该将其空间释放,但是有时候我们不知道何时应该将其释放。为了解决这个问题,Objective-C 提供了 autorelease 方法。

autorelease 是一种支持引用计数的内存管理方式,主要用于延迟对象的释放,只要给对象发送一条 autorelease 消息,会将对象注册到一个自动释放池中,当自动释放池被销毁时,会对池子里面的所有对象做一次 release 操作。

autorelease方法会返回对象本身,且调用完autorelease方法后,对象的计数器不变。

Person *p = [Person new];
p = [p autorelease];
NSLog(@"count = %lu", [p retainCount]); // 计数还为 1

1.autoreleasepool的创建

autoreleasepool有两种创建方法

  • 第一种是使用 NSAutoreleasePool 创建
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // 创建自动释放池
[pool release]; // [pool drain]; 销毁自动释放池
  • 第二种是使用 @autoreleasepool 创建
@autoreleasepool
{ // 开始代表创建自动释放池} // 结束代表销毁自动释放池

2.autorelease 的使用方法

NSAutoreleasePool *autoreleasePool = [[NSAutoreleasePool alloc] init];
Person *p = [[[Person alloc] init] autorelease];
[autoreleasePool drain];

需要注意的是对象调用autorelease方法需要在创建autoreleasepool之后否则无法将其注册到autoreleasepool。

@autoreleasepool
{ // 创建一个自动释放池Person *p = [[Person new] autorelease];// 将代码写到这里就放入了自动释放池
} // 销毁自动释放池(会给池子中所有对象发送一条 release 消息)

在自动释放池的外部发送 autorelease 不会被加入到自动释放池中。就算对象创建在自动释放池内没调用autorelease方法也不会将其注册到autoreleasepool。

@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];
}

3.自动释放池的嵌套使用

  • 自动释放池是以栈的形式存在。

  • 由于栈只有一个入口,所以调用 autorelease 会将对象放到栈顶的自动释放池。

@autoreleasepool { // 栈底自动释放池@autoreleasepool {@autoreleasepool { // 栈顶自动释放池Person *p = [[[Person alloc] init] autorelease];}Person *p = [[[Person alloc] init] autorelease];}
}

4.自动释放池的注意事项

  • 自动释放池中不适宜放占用内存比较大的对象

因为自动释放池是延迟释放机制,只有清空池子时才会将注册到池子的对象销毁,此时如果有多个内存较大的对象就会造成短时间内内存暴涨。

  • 不要把大量循环操作放到同一个 @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];}
}

两段代码的区别在于第一个自动释放池是在循环外部创建的,所有的Person对象都被添加到了同一个自动释放池中。它们并不会被立即释放。只有当自动释放池被排空时,也就当@autoreleasepool块执行完毕后,所有这些对象才会被release。因此,在循环结束前,所有创建的Person对象都存在于内存中,这会导致内存使用量显著增加,即所谓的“内存暴涨”。

第二个每次循环迭代时都会创建一个新的自动释放池。每一个Person对象都在创建它的那个迭代的自动释放池中被管理,并且在该迭代结束时,该自动释放池就会被排空,从而立即释放该迭代中创建的Person对象。由于每个Person对象都在其创建后的短时间内被释放,因此内存使用量不会像第一个例子那样累积,从而避免了“内存暴涨”的问题。

  • 不要连续调用 autorelease
@autoreleasepool {// 会导致过度释放Person *p = [[[[Person alloc] init] autorelease] autorelease];}
  • 调用 autorelease 后又调用 release
@autoreleasepool {Person *p = [[[Person alloc] init] autorelease];[p release]; //过度释放
}

六、循环引用

对象A和对象B互相作为对方的成员变量时也就是互相持有对方时相互引用了对方作为自己的成员变量,只有当自己销毁时,才会将成员变量的引用计数减1,这就导致了A的销毁依赖于B的销毁,同样B的销毁依赖于A的销毁,这样就造成了循环引用问题。

循环引用也分为几种类型:

  1. 自循环引用
  2. 相互循环引用
  3. 多循环引用

1.自循环引用

假如有一个对象,内部强持有它的成员变量obj,若此时我们给obj赋值为原对象时,就是自循环引用。

2.相互循环引用

对象A内部强持有obj,对象B内部强持有obj,若此时对象A的obj指向对象B,同时对象B中的obj指向对象A,就是相互引用。

3.多循环引用

假如类中有对象1…对象N,每个对象中都强持有一个obj,若每个对象的obj都指向下个对象,就产生了多循环引用。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/46781.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

面对人工智能发展的伦理挑战:应对策略与未来方向

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

手写new

手写new new是什么执行new会发生什么实现new new是什么 new 操作符是可以创建一个用户定义的对象的实例或具有构造函数的内置对象的实例 function Car (make, model, year) {this.make makethis.model modelthis.year year } Car.prototype.running function () {return …

[Linux]添加sudoers

之前我们讲过sudo这个命令,它可以让我们普通用户进行短暂的提权,上回我们讲完了vim 本篇是个短篇,目的就是让我们之后的学习中可以使用sudo命令。 首先我们先登录root用户 ls /etc/sudoer 我们需要改的就是上面的这个文件 vim /etc/sudoers 我们用vim打开 把光标移动到这…

微信小程序实现和AI语音对话功能

1.效果 微信小程序与AI语音对话 2.效果主要实现技术 ①AI语音合成&#xff08;阿里云平台&#xff09; ②微信小程序同声传译功能 ③本功能是用原生微信小程序实现的&#xff08;可自行转成uniapp代码&#xff09; 3.同声传译 进入微信服务市场&#xff0c;搜索同声传译就能找…

python关于excel常用函数(pandas篇)

iterrows函数&#xff1a; Pandas的基础数据结构可以分为两种&#xff1a;DataFrame和Series。不同于Series的是&#xff0c;Dataframe不仅有行索引还有列索引 。df.iterrows( )函数&#xff1a;可以返回所有的行索引&#xff0c;以及该行的所有内容。 pd.read_excel&#xf…

小型数控车床对现代制造业的影响

小型数控车床作为现代制造业的重要生产工具&#xff0c;集成了计算机控制、精密机械、电子技术和自动化技术&#xff0c;为各种复杂零件的加工&#xff0c;在生产效率和精度上带来了显著提升&#xff0c;它是制造业中不可或缺的基础装备&#xff0c;在金属切削加工领域发挥着关…

车间数据采集网关的工作原理和应用场景-天拓四方

在智能制造日益盛行的今天&#xff0c;车间数据采集作为整个生产流程中的关键环节&#xff0c;其重要性愈发凸显。数据采集网关作为这一环节的核心设备&#xff0c;扮演着承上启下的重要角色。本文旨在深入探讨车间数据采集网关的工作原理和应用场景。 一、数据采集网关的工作…

Java基础知识——继承

目录 一、什么是继承 二、类的继承格式 三、继承的特点 四、继承的类型 五、继承的关键字 六、为什么使用继承 一、什么是继承 继承是面向对象编程&#xff08;OOP&#xff09;的四大基本原则之一&#xff0c;它允许我们创建一个新类&#xff0c;继承并扩展现有类的属性和…

【HarmonyOS学习】Calendar Kit日历管理

简介 Calendar Kit提供日历与日程管理能力&#xff0c;包括日历的获取和日程的创建能力。 Calendar Kit为用户提供了一系列接口来获取日历账户&#xff0c;并使用特定的接口向日历账户中写入日程。 如果写入的日程带有提醒时间则系统会在时间到达时向用户发送提醒。 约束点…

eclipse 新建类class文件增加copyright版权信息

1、Window -> Preferences 2、输入code,找到code templates Java > Code Style > Code Templates 比如进行如何的设置&#xff1a; 3、新增类文件&#xff0c;会自动增加版权&#xff1a;

2024.7.12单片机PWM

遇到了一个光标变成下划线的问题&#xff1a; Keil5光标变下划线&#xff0c;变回来的方法_keil5光标是下划线-CSDN博客 这里是用了输入捕获&#xff08;IC&#xff1a;input capture&#xff09;&#xff0c;输出比较&#xff08;OC:Output Compare&#xff09;区别 学到这…

解析DDD开发框架Axon

在微服务架构盛行的当下&#xff0c;领域驱动设计&#xff08;DDD&#xff09;也得到了崭新的发展。在DDD中包含了聚合、领域事件等核心概念&#xff0c;也需要引入CQRS、事件溯源等架构模式。对于开发人员而言&#xff0c;如何简单而高效的实现这些核心概念和架构模式是一大痛…

集群节点状态异常的解决方式

文章目录 集群节点状态异常的解决方式问题概述解决方式1.关闭所有服务2.对所有集群删除Hadoop相关文件2.1 删除Hadoop系统运行时创建的临时数据和文件2.2 删除Hadoop的数据文件 3.重新对Hadoop节点进行初始化和启用4.重启服务&#xff0c;检查节点状态 集群节点状态异常的解决方…

软件测试工作流程

1、目的 有效的保证软件质量;有效的制定不同测试类型(软件系统测试、音频主观性测试、专项测试、自动化测试、性能测试、用户体验测试)的软件测试计划;按照计划进行测试,发现软件中存在的问题;对软件中已经解决的问题进行有效的验证;判定测试过程和问题验证的有效性。2、…

PostgreSQL(二十一)clog的作用与管理

一、CLOG的概念及作用 1、基础概念 &#xff08;1&#xff09;CLOG&#xff1a;记录事务号的状态&#xff0c;可以用其判断行的可见性。每个事务状态占用两个bit位。 tip&#xff1a;事务的状态有4种&#xff1a;IN_PROGRESS&#xff0c;COMMITTED&#xff0c;ABORTED和SUB_…

如何应对AI发展下的伦理挑战

目录 1.概述 2.构建可靠的AI隐私保护机制 2.1. 最小化数据收集 2.2. 数据去标识化 2.3. 加密技术 2.4. 分布式学习和边缘计算 2.5. 强化用户控制权 2.6. 独立审计和合规性检查 2.7. 持续教育和培训 2.8.小结 3.确保AI算法的公正性和透明度 3.1.增强AI决策透明度的方…

第一百五十九节 Java IO教程 - Java输入流、文件输入流、缓冲输入流、推回输入流

Java IO教程 - Java输入流 抽象基本组件是InputStream类。 InputStream|--FileInputStream |--ByteArrayInputStream |--PipedInputStream|--FilterInputStream|--BufferedInputStream |--PushbackInputStream |--DataInputStream |--ObjectInputStream我们有FileInputStream&…

【C++】——类和对象(中)

文章目录 类的默认成员函数构造函数析构函数拷贝构造函数赋值运算符重载运算符重载 const成员函数 类的默认成员函数 在C中&#xff0c;类&#xff08;class&#xff09;可以拥有多种成员函数&#xff0c;其中一些成员函数在类定义中没有显式声明时&#xff0c;编译器会隐式地…

Windows上LabVIEW编译生成可执行程序

LabVIEW项目浏览器(Project Explorer)中的"Build Specifications"就是用来配置项目发布方法的。在"Build Specifications"右键菜单中选取"New"&#xff0c;可以看到程序有几种不同的发布方法&#xff1a;Application(EXE)、Installer、.Net Inte…

C++第七弹 -- C/C++内存管理

目录 前言一. C/C内存分布二. C语言中动态内存管理方式三. C中动态内存管理四. operator new与operator delete函数五. new和delete的实现原理1.内置类型2. 自定义类型 六. 定位new表达式(placement-new)七. 常见面试题总结 前言 在C/C编程中&#xff0c;内存管理是至关重要的…