【iOS】内存管理

news2024/9/23 9:34:10

文章目录

  • 前言
  • 理解引用计数
    • 引用计数原理
    • 属性存取方法中的内存管理
    • 自动释放池
    • 保留环
  • 以ARC简化引用计数
    • 使用ARC时必须遵守的命名规则
    • 变量的内存管理语义
    • ARC如何清理实例变量
    • 覆写内存管理的方法
  • 在dealloc方法中只释放应用并解除监听


前言

内存管理:
在Objective-C这样的面向对象的语言里,内存管理是很重要的概念。理解了内存管理模型的种种细节之后,Objective-C的内存管理就没有那么复杂了。尤其是有了“自动引用计数(ARC)”之后,更为简单。ARC几乎把所有的内存管理事宜都交给了编译器处理,开发者只需注重业务逻辑。


提示:以下是本篇文章正文内容,下面案例可供参考

理解引用计数

Objective-C使用引用计数来管理内存。每个对象都有可以递增或递减的计数器。
如果要使某个对象存活,递增其引用计数。用完之后,递减其计数,计数变为0的时候,就没人关注此对象了,销毁它。

引用计数原理

在引用计数架构下,对象有一个计数器,用以表示当前有多少个事物想令此对象继续存活下去。这在Objectiv-C中叫做“保留计数”,也可以叫“引用计数”。

Retain递增保留计数
release递减保留计数
autorelease待稍后清理“自动释放池”时,再递减保留计数。
  • 查看保留计数的方法叫做retainCount,此方法不太有用。
  • 对象创造出来时,引用计数至少为1.若想令其继续存活,则调用retain方法。要是某部分代码不在使用此对象,不想令其继续存活,就调用release或者autorelease方法。当保留计数归零时,对象就回收(deallocated)了,系统会将占用的内存标记为“可重用”。此时,所有指向该对象的引用也都变得无效了。
    请添加图片描述
  • 应用程序在其生命周期中会创建很多对象,这些对象都相互联系着。相互关联的对象就构成了一张“对象图”。对象如果持有指向其他对象的强引用,那么前者就“拥有”后者。也就是说,对象向令其所引用的那些对象继续存活,就可以将其“保留”。等用完之后,在做释放。请添加图片描述
  • 在图示中,ObjectB和ObjectC都引用了ObjectA。若ObjectB和ObjectC都不在使用ObjectA,则其保留计数降为0,此时可以摧毁ObjectA。如果还有其他对戏那个想令ObjectB和ObjectC继续存活,而应用程序里又有另外一些对象想令那些对象继续存活。如果按“引用计数”回溯,那么最终会发现一个“根对象”。在Mac OS X应用程序中,此对象就是NSAppliction对象。而在iOS应用程序中,是UIApplication对象。两者都是应用程序启动时创建的单例。
  • 如果代码中直接调用release方法,ARC下就无法编译。在Objective-C中,调用alloc方法所返回的对象由调用者所拥有。也就是说,调用者已经通过alloc方法表达了想令该对象继续存活下去的意愿。此时保留计数至少为1。保留计数绝不一定是某个值,只能说执行的操作是递增了还是递减了该计数
  • 如果调用release方法之后,基于某些原因,其保留计数降为0,此时对象所占内存可能会回收。这样的话,在调用NSLog kennel就会使程序崩溃。对象所占的内存在“解除分配”后,只是放回“可用内存池”,如果执行NSLog时还未覆写对象内存,那么该对象仍然有效,此时程序不会崩溃。因过早释放对象所导致的bug很难查找
  • 为避免使用无效对象,一般调用完release之后都会清空指针。就可以保证不会出现指向无效对象的指针,这种指针通常称为“悬挂指针”。

属性存取方法中的内存管理

  • 对象图由互相关联的对象构成,树枝通过在其元素上调用retain方法来保留那些对象。其他对戏那个也可以保留别的对象,一般通过属性来实现。访问属性时,会调用相关实例变量的获取方法及设置方法。若属性为“strong”关系,则设置的属性值会保留。
 - (void)setFoo:(id)foo{
    [foo retain];
    [_foo release];
    _foo = foo;
}
  • 此方法会保留新值并释放旧值,然后更新实例变量,令其指向新值。顺序很重要,假如还未保留新值就先把旧值释放了,而且两个值又指向同一个对象,那么先执行的release操作就可能导致系统将此对象永久回收。而后的retain操作无法令已经彻底回收的对象复生,于是实例变量就变成了悬挂指针。

自动释放池

调用release方法会立即递减保留计数(还有可能让系统回收此对象)。如果改用autorelease,此方法会稍后递减计数,通常是在下一次“事件循环”时递减,也可能更早一些。

 - (NSString*)stringValue {
    NSString* str = [[NSString alloc] initWithFormat:@"I am this :%@", self];
    return str;
}
  • 这个方法返回的str对象的保留计数比预期值多1。因为调用alloc方法会令保留计数加1,没有与之对应的释放操作。需要想办法如何将多出来的这个一次保留抵消掉。
  • 不能在方法内释放str,否则还没等方法返回,系统就把对象回收了。这里应该使用autorelease,他会在稍后释放对象。从而给调用者留下足够长的时间,使其可以在需要时先保留返回值。此方法可以保证对象在跨越“方法调用边界”后一定存活。释放操作会在清空最外层的自动释放池时执行,除非有自己的释放池,否则这个时机就是当前线程的下一次事件循环。
 - (NSString*)stringValue {
    NSString* str = [[NSString alloc] initWithFormat:@"I am this :%@", self];
    return [str autorelease];
}
  • 此时,此对象必然存活。由于返回的str对象将与稍后自动释放,无需再执行内存管理操作。因为自动释放池中的释放操作要等到下一次事件循环时才会执行。NSLog中使用str对象前就不需要手工执行保留操作。如果要持有此对象,那就需要保留,并于稍后释放。

保留环

保留环是值呈现环状的相互引用的多个对象。他容易导致内存泄漏。因为循环中的对象的保留计数不会降为0.对于每个循环中的每个对象来说,至少还有另一个对象引用着它。

请添加图片描述

  • 图示即为保留环,在垃圾收集系统下,所有的对象的引用计数至少为1。在垃圾收集系统中,通常将这种情况认定为“孤岛”。此时,垃圾收集器会把三个对象全部回收走。在Objective-C的引用计数中,通常采用“弱引用”来解决问题,或是从外界命令循环中的某个对象不在保留另一个对象。这两种方法都能打破保留环,从而避免内存泄漏。

以ARC简化引用计数

需要执行保存和释放操作的地方很容易就能看出来。Clang编译器自带一个“静态分析器”,用于指明程序里引用计数出问题的地方。

if ([self shouleLogMessage]) {
    NSString* message = [[NSString alloc] initWithFormat: @"I am object, %p", self ];
    NSLog (@"message = %@". message);
}
  • 此代码存在内存泄漏问题。因为if语句末尾并未释放message对象。而在if语句之外又无法调用message对象,此时message对象所占的内存就泄漏了。如果调用NSString的alloc方法所返回的message对象的保留计数比期望值多1,内存泄漏。
  • “静态分析器”要做的事就是套用判断内存是否泄漏的规则,分析出内存泄漏问题的对象。
  • 自动引用计数这一思路是“静态分析器根据需要,预先加入适当的保留或者释放操作以避免一些问题。”自动引用计数所做的事情与其名称相符,就是自动管理引用计数。
  • 使用ARC时一定要记住,引用计数实际上还是执行的,只不过保留和释放操作现在是由ARC自动添加。除了为方法所返回的对象正确运用内存管理语义之外,ARC还有更多的功能。
  • ==ARC会自动执行retain,release。autorelease,delloc等操作。在ARC下调用这些内存管理方法都是非法的。==直接调用这些方法都会产生编译错误,ARC要分析何处应该自动调用内存管理方法,所以如果手动调用,会干扰其工作。
  • 在ARC调用这些方法时,不通过OC的消息派发机制,而是直接调用底层C语言版本。这样做性能更好。

使用ARC时必须遵守的命名规则

  • 将内存管理语义在方法名中表示出来早已成为OC的管理,ARC将它确立为硬性规定。这些规则简单的体现在方法名上,如果方法名以下列词语开头,则其返回的对象归调用者所有。如:alloc,new,copy,mutableCopy。
  • 归调用者所有的意思是:调用上述四种方法的代码要负责释放方法所返回的对象。也就是说,这些对象的保留计数是正值,而调用了这四种方法的那段代码要将其中一次保留的操作抵消掉。如果还有其他对象保留此对象,并对其调用了autorelease,那么其保留计数的值可能比1大。
  • 若方法名不以上述四个词语开头,则其返回的对象并不归调用者所有。在这种情况下,返回的对象会自动释放,其值在跨越方法时调用边界后依然有效。如果想让对象多存活一段时间,必须令调用者保留它才行。
  • ARC通过命名约定内存管理规则标准化。除了会自动调用“保留”与“释放”之外,使用ARC可以执行一些手工操作很难甚至无法完成的优化。ARC也包含运行期组件,此时执行的优化很有意义。在ARC环境下编译代码时,必须考虑“向后兼容性”,以兼容那些不适应ARC的代码。
  • ARC可以在运行期检测到这一对多余的操作,也就是autorelease及紧跟其后的retain。为了优化代码,在方法中返回自动释放的对象时,要执行一个特殊函数。此时不直接调用对象的autorelease方法,而是改用objc_autoreleaseReturnValue。此函数会检视当前方法返回之后即将要执行的那段代码。如果发现那段代码要在返回的对象上执行retain操作,则设置全局数据结构中的一个标志位,而不执行autorelease操作。与之相似,如果方法返回了一个自动释放的对象,而调用方法的代码要保留此对象,那么此时不直接执行retain,而改为执行objc_retainAutoreleaseedReturnValue函数。此函数要检测刚才提到的标志位,如果已经置位,则不执行retain操作。并设置检测标志位,要比autorelease和retain快
  • 将内存管理交由编译器和运行期组件来做,,可以使代码得到多种优化。

变量的内存管理语义

  • ARC也会处理局部变量和实例变量的内存管理。默认情况下,每个变量都是指向对象的强引用。
  • ARC会用一种安全的方法来设置:先保留新值,再释放旧值,最后设置实例变量。在应用程序中,可以用下列修饰符来改变局部变量和实例变量的语义。
_ _strong:默认语义,保留其值
_ _unsafe_unretained:不保留此值,这么做不太安全,因为等到再次使用变量时,变量有可能已经被回收了
__weak:不保留此值,但是变量可以安全使用,因为如果系统把这个对象回收了,那么变量也会自动清空
__autoreleasing:把对象“按引用传递”给方法时,使用这个特殊的修饰符,此值在方法返回时自动释放。

请添加图片描述

  • 不论采用哪种写法,在设置实例变量时都不会保留其值。只有在使用新版运行期程序库时,加了_weak修饰的weak引用才会自动清空。
  • 我们经常给局部变量加上修饰符,用以打破由“块”所引入的“保留环”。块会自动保留其所捕获的对象,而如果其中有某个对象又保留了块本身,就可能导致“保留环”。可以使用_weak局部变量来打破这种“保留环”

ARC如何清理实例变量

要管理内存,ARC就必须在“回收分配给对象的内存”时生成必要的清理代码。凡是具备强引用的变量,都必须释放,ARC会在dealloc方法中插入这些代码。

  • 用了ARC后,就不要编写[obj dealloc]这样的方法了。因为ARC会借用Objective-C++的一项特性来生成清理例程。回收Objective-C++对象时,待回收的对象就会调用所有C++对象的析构函数。编译器如果发现某个对象里含有C++对象,就会生成名为.cxx_destruct的方法。
  • 如果有非Objective-C的对象,仍需要清理内存。

覆写内存管理的方法

不使用ARC时,可以覆写内存管理方法。在实现单里类的时候,因为单例不可释放,所以我们经常覆写release方法。将其替换为“空操作”。但在ARC环境下不能这样做,因为会干扰到ARC分析对象生命周期的工作。

在dealloc方法中只释放应用并解除监听

对象在经历其生命周期后,最终会被系统回收。此时要执行dealloc方法。在每个对象的生命期内,此方法仅执行一次,也就是当保留计数降为0时。

  • 我们应该在dealloc方法中释放对象所拥有的引用,也就是把所有的Objective-C对象都释放掉,ARC会通过自动生成的.cxx_destruct方法,在dealloc中为你自动添加这些释放代码。对象所拥有的其他非Objective-C的对象也要释放。
  • 在dealloc方法中,我们通常还要把原来配置过的观测行为都清理掉。如果给对象发送某种通知,一般都应该在此处注销通知。
  • 如果手动管理引用计数不使用ARC,那么最后还要调用[super dealloc]。ARC会自动执行此操作,这再次表明其比手动管理更简单,更安全。若选择手动管理,还要将当前对象所拥有的全部Objective-C对象逐个释放。
  • 开销较大或系统内存稀缺的资源不在dealloc中释放引用。比如文件描述符,套接字,大块内存等,都属于这种资源。不能依赖dealloc方法必定会在某个特定的时机调用,因为有一些无法预料的对象可能也持有此对象。在这种情况下,如果我们一定要等到系统调用dealloc方法的时候才释放,那么保留这些稀缺资源的时间就过长了,这么做不合适。通常的做法是:实现另外一个方法,当应用程序用完资源对象后,就调用此方法。
  • 在清理方法而非dealloc方法中清理资源是因为系统不能保证每个创建出来的dealloc都会执行。极个别情况下,当应用程序终止时,仍有对象处于存活状态,这些对象没有收到dealloc消息。当应用程序终止后,其占用的资源也会返回给操作系统,所以实际上这些对象也就等于是消亡了。不调用dealloc方法是为了优化程序效率。这也说明了系统未必会在每个对象上调用dealloc方法。
  • 如果对象管理着某些资源,那么在dealloc中也要调用“清理方法”,以防止开发者忘了清理这些资源。有时可能不想只输出一条错误消息,而是要抛出异常来表明不调用某个方法是严重的错误
  • 编写dealloc方法时还应该注意,不要在其中随便调用其他方法。如果调用了其他方法,那么等到那些人物执行完毕的时候,系统研究把当前这个待回收的对象彻底摧毁了。经常导致应用程序崩溃,因为那些任务执行完毕后,要回调此对象,告诉该对象任务已经完成,而此时如果对象已经摧毁,回调操作就会出错。
  • 调用dealloc方法里的那个线程会执行“最终的释放操作”,令对象的保留计数为0。但是某些方法必须在特定的县城里调用才行。若在dealloc里调用了那些方法,无法保证当前这个线程就是那些方法所需要的线程。

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

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

相关文章

Windows平台下的内存泄漏检测

Windows平台下的内存泄漏检测一、使用_CrtDumpMemoryLeaks定位内存泄露添加对应的头文件转储内存泄漏信息程序任意点退出指定调试信息输出二、定位具体内存泄露位置内存快照转储内存快照比较内存快照完整例子三、使用WinDbg定位获取堆信息查看指定堆的使用情况获取地址信息获取…

【Docker】初级篇

【Docker】初级篇(一)Docker简介【1】docker是什么【2】容器与虚拟机比较【3】能干嘛【4】去哪下(二)Docker安装【1】前提说明【2】Docker的基本组成【3】安装步骤(1)确定是CentOS7及以上版本(2…

抽烟打电话行为识别系统 yolo

抽烟打电话行为识别系统通过yolo深度学习框架模型,对现场画面区域进行7*24小时实时监测,发现抽烟打电话等违规行为立即抓拍存档预警。YOLOv5是一种单阶段目标检测算法,该算法在YOLOv4的基础上添加了一些新的改进思路,使其速度与精…

【 shell 编程 】第4篇 数组和函数

数组和函数 文章目录数组和函数一、数组1.普通数组2.关联数组3.数组和循环二、函数1.定义函数2.调用函数一、数组 变量:用一个固定的字符串,代替一个不固定字符串。 数组:用一个固定的字符串,代替多个不固定字符串。 1.普通数组…

Python代码实现学生管理系统

Python代码实现学生管理系统 需求说明 实现一个命令行版本的学生管理系统 功能: 新增学生 显示学生 查找学生 删除学生 存档到文件 创建入口函数 使用一个全局列表 students 表示所有学生信息. 使用 menu 函数和用户交互. 这是一个自定义函数. 使用 insert , show ,…

MacOS Ventura安装失败的原因及解决方法分享

2022年10月,苹果公司向Mac电脑用户推送了MacOS Ventura正式版更新,此次更新为MacOS带来了台前调度、连续互通相机、iMessage 撤回、编辑等功能。吸引众多Mac电脑用户不由纷纷下载安装,但各用户在安装的过程中经常遇到更新MacOS Ventura时突然…

物联网与射频识别技术,课程实验(五)

实验5—— 基于随机二进制树的防冲突算法的实现与性能分析 实验说明: 利用Python或Matlab模拟基于随机二进制树的防冲突算法; 分析标签数量k对遍历所有标签所需时间的影响; 分析标签ID的长度、分布对算法性能的影响; 利用Python或…

MQTT+STM32+ESP8266-01s硬件传递的JSON数据到前端和后端出现中文乱码问题

最近在做一个关于MQTT相关毕设项目,数据传输过程中出现了中文乱码问题,大致就是硬件发送的JSON主题数据中包含中文(如下图1所示),软件后端和软件前端接受该主题数据后出现了中文乱码,出现乱码一般都是硬件传递到后端和前端的编码不一致导致的,所以前端和后端接受该JSON数据的时…

2023.1.1 学习周报

文章目录摘要文献阅读1.题目2.摘要3.问题和方案4.介绍5.方法5.1 Symbolic Description5.2 The Short-Term Memory Priority Model5.3 The STAMP Model5.4 The Short-Term Memory Only Model6.实验6.1 评价指标6.2 实验结果7.结论深度学习加性模型点积模型缩放点积模型双线性模型…

数值优化之凸函数

本文ppt来自深蓝学院《机器人中的数值优化》 目录 1 凸函数的性质 ​2 凸函数的性质 1 凸函数的性质 凸函数最重要的性质就是Jensens inequality,也就是琴生不等式。 若能取到等号则是凸函数,若不能取到等号则是强凸函数,若不等号相反,则…

spring session

文章目录Spring Session 架构及应用场景为什么要spring-sessionSR340规范与spring-session的透明继承Spring Session探索特点核心 APIservlet session 与 spring-session 关系webflux 与 spring session 的关系基于 Servlet 的 Spring Session 实现思考题背景1、注册到 Filter …

Java 并发编程知识总结【一】

JUC 是什么? java.util.concurrent 在并发编程中使用的工具类 concurrent:并发 1. 线程基础知识复习 1.1 进程(process) 进程是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程(生命周期…

【数据集7】全球人类住区层GHSL数据详解

全球人类住区层Global Human Settlement Layer 官网地址-GHSL - Global Human Settlement Layer 1 全球人类住区层GHS-SMOD Global human settlement layer-settlement model grid (GHS-SMOD):描述 epoch时段: 1975-2030年 5年一个周期resolution空间分辨率: …

Codeforces Round #833 (Div. 2)E. Yet Another Array Counting Problem(笛卡尔树+树形DP)

题目链接:Problem - E - Codeforces 样例输入: 4 3 3 1 3 2 4 2 2 2 2 2 6 9 6 9 6 9 6 9 9 100 10 40 20 20 100 60 80 60 60样例输出: 8 5 11880 351025663题意:给定一个长度为n的数组a[],对于每一个区间[l,r],这个…

[Python从零到壹] 六十一.图像识别及经典案例篇之基于纹理背景和聚类算法的图像分割

祝大家新年快乐,阖家幸福,健康快乐! 欢迎大家来到“Python从零到壹”,在这里我将分享约200篇Python系列文章,带大家一起去学习和玩耍,看看Python这个有趣的世界。所有文章都将结合案例、代码和作者的经验讲…

尚医通-查询删除科室接口-添加查询删除排班接口实现(二十)

目录: (1)数据接口-查询和删除科室接口-功能实现 (2)数据接口-排版接口-功能实现 (1)数据接口-查询和删除科室接口-功能实现 查看医院系统中查询科室的对应的方法 查询条件需要用的类&#…

【数据结构】链式存储:链表

目录 🥇一:初识链表 🎒二、链表的实现(单向不带头非循环) 📘1.创建节点类 📒2.创建链表 📗3.打印链表 📕4.查找是否包含关键字key是否在单链表当中 📙…

Webpack核心概念

1. 核⼼概念 Entry Entry ⽤来指定 webpack 的打包⼊⼝。 依赖图的⼊⼝是 entry,对于⾮代码⽐如图⽚、字体依赖也会不断加⼊到依赖图中。 Entry 的⽤法: 1. 单⼊⼝:entry 是⼀个字符串; module.exports {entry: ./path/to/my…

若依框架-补充篇:Vuex全局状态管理Axios二次封装

在上一篇《若依框架:前端登录组件与图像验证码|用户登录逻辑》中的篇末,对Vuex全局状态管理、Axios二次封装部分介绍的较为粗略,因此就有了这个补充篇。 目录 Vuex全局状态管理 Vuex是什么? 如何理解“状态管理模式”&#xf…

【Java语法】之String类练习1

目录 1.字符串中的第一个唯一字符 2. 最后一个单词的长度 58. 最后一个单词的长度 3.验证回文串 4.字符串相加 5.小结: 1.字符串中的第一个唯一字符387. 字符串中的第一个唯一字符https://leetcode.cn/problems/first-unique-character-in-a-string/ 给定一个字符…