1. 概述

What —— 什么是内存管理?

内存管理是程序设计中常见的资源管理(resource management的一部分,主要功能是控制应用程序中对象的生命周期,确保它们在需要时存在,而不再需要时能够及时释放内存。

Why —— 为什么要进行内存管理?

内存管理是确保应用程序高效运行并避免资源浪费的关键环节,具体表现为:

  • 防止内存泄漏(leak memory):如果应用程序未能及时释放不再使用的内存,从而导致内存占用不断增加,最终可能致使应用程序崩溃。良好的内存管理可以防止这种情况的发生。
  • 避免悬挂指针(dangling pointers):悬挂指针是指某块内存已被释放,但其指针仍然指向该内存,这可能会导致应用程序在后续访问时崩溃或产生未定义的行为。良好的内存管理可以确保指针在适当的时机被置为nil,从而避免这种情况。
  • 提高程序性能:在iOS开发中,设备的内存资源有限,良好的内存管理能够确保应用程序只使用必要的内存资源,精细控制内存的使用,从而保证设备的响应速度以及程序的平稳运行。

How —— 怎样进行内存管理?

在Objective-C中,内存管理的核心概念是“引用计数”(Reference Counting)。每个对象在创建时会有一个引用计数,当有对象引用它时,引用计数增加;当引用被移除时,引用计数减少。当引用计数为零时,表示该对象没有被任何地方使用,其所占用的内存可以被释放。

Objective-C的基本内存管理模型有以下3种:

  • 垃圾收集(Garbage Collection)

系统的垃圾收集器能够在后台定期检查哪些对象不再被使用,并自动回收这些对象的内存。但iOS运行环境并不支持垃圾收集,且这项功能在macOS 10.8后被完全废弃,故不做专门介绍。

  • 手工引用计数(Manual Reference Counting, MRC)

开发者手动管理对象的引用计数。

  • 自动引用计数(Automatic Reference Counting, ARC)

编译器自动管理对象的引用计数。

上图为Objective-C中的内存分布,其中,堆区内存是动态分配的,由开发者或运行时系统负责管理。每个堆区对象都有一个引用计数,对象的指针会存储在栈区,引用计数增减影响的是对象在堆区的内存释放,而不是栈区的内存。

2. 手工引用计数

2.1 MRC简介

手工引用计数(MRC)是Objective-C中早期的内存管理机制。在MRC中,开发者必须手动管理对象的引用计数,通过显式调用’retain’、’release’和’autorelease’方法来控制对象的生命周期。尽管MRC在现代开发中已经不如ARC常用,但如果需要支持一些不能够迁移到ARC上运行的代码,还是需要理解其工作原理,并且这对掌握Objective-C的内存管理非常重要。

在MRC中,每个对象都有一个与之关联的引用计数。当对象被创建或被另一个对象引用时,引用计数增加;当不再需要该对象时,引用计数减少;当引用计数减为零时,系统会自动调用对象的’dealloc’方法,释放该对象的内存。

2.2 MRC中的内存管理操作
  • retain: ’retain’方法将对象的引用计数增加1,这意味着对象有一个新的持有者或引用,表明它仍在使用中。
  • release: ’release’方法将对象的引用计数减少1。

可能会编写这样一个方法:先使用alloc创建一个对象,然后将它作为方法调用的结果返回。

这样会有一个问题:尽管方法不再使用这个对象,但是并不能释放它,因为需要将这个对象作为方法的返回值。

NSAutoreleasePool类创建的目的就是希望能够解决这个问题,自动释放池可以帮助追踪需要延迟一些时间释放的对象。

  • autorelease: ’autorelease’方法将对象添加到当前的自动释放池中,当池被销毁(池执行到末尾,或给池发送了 ’drain’消息)时,对象会自动收到 ’release’消息,通常用于临时对象的管理。

2.3 常见的内存管理模式
  • 所有权模式(Ownership Patterns)

谁分配,谁释放:对象的创建者负责释放对象,如果通过’alloc’、’new’、’copy’或’mutablecopy’创建了一个对象,那么就有责任在不再需要它时调用’release’。

传递所有权:当将对象传递给其他对象时,可能需要调用’retain’以确保它在新环境中不被释放,或者由接收方决定是否保留引用。

  • Autorelease Pool模式

将对象放入自动释放池中,系统会在适当的时候自动释放。

PS任何由’alloc’、’new’、’copy’或’mutablecopy’创建的对象都不会被自动进入自动释放池,这种情况下可以说你拥有这个对象,需要在使用完这些对象后负责释放这些对象的内存。可以主动给这些对象发送’release’消息,或者发送 ’autorelease’消息将对象加入到自动释放池中。

2.4 Objective-C的内存管理规则
  1. 当使用’alloc’、’new’、’copy’或’mutablecopy’方法创建一个对象时,该对象的引用计数为1。当不再使用该对象时,应该向该对象发送一条’release’或’autorelease’消息。这样,该对象将在其使用寿命结束时被销毁。

  2. 当通过其他方法获得一个对象时,假设该对象的引用计数为1,而且已经被设置为自动释放,那么不需要执行任何操作来确保该对象得到清理。如果打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它。

  3. 如果保留了某个对象,就需要(最终)释放或自动释放该对象。必须保持’retain’方法和’release’方法的使用次数相等。

获得途径

临时对象

拥有对象

alloc/new/copy/mutablecopy

不再使用时释放对象

在dealloc方法中释放对象

其他方法

不需要执行任何操作

获得对象时保留,在在dealloc方法中释放对象

2.5 MRC的优缺点

✅优点:

  1. 提供了对内存管理的完全控制
  2. 对于特定性能需求,可以进行更细粒度的优化

❌缺点:

  1. 开发者需要花费额外的精力管理内存,增加了代码的复杂性
  2. 易于出错,增加了出现内存泄漏、悬挂指针等问题的风险

3. 自动引用计数

3.1 ARC简介

自动引用计数(ARC)是Objective-C内存管理中的一种现代化机制,旨在简化内存管理操作,降低手动管理内存时出错的风险。其核心思想是ARC会在编译时自动为代码插入合适的内存管理指令,如’retain’、’release’和’autorelease’,从而确保对象的生命周期被正确管理,减轻开发者的负担。

3.2 ARC中的内存管理操作

在ARC下,开发者不再显式调用’retain’和’release’,而是通过属性和关键字来控制对象的内存管理。

  • 强引用(Strong References):

默认情况下,ARC将对象的引用视为强引用,确保对象在被引用期间不会被释放。

  • 弱引用(Weak References):

使用’__weak’修饰符声明的对象引用不会增加引用计数。

  • 无主引用(Unowned References):

使用’__unsafe_unretained’或’__unowned’修饰符,这些引用不会增加引用计数,但在对象被释放时,其引用不会被置为’nil’,可能导致悬挂指针问题。

  • __autoreleasing:

表示对象将在当前的自动释放池中释放,常见于错误处理或返回值传递。

3.3 循环引用问题

循环引用(Retain cycle)是指在内存管理中,两个或多个对象相互持有对方的强引用,导致它们的引用计数都无法降为零,从而无法释放内存。是一个经典的内存泄漏问题。

这里给出图示来解释和解决循环引用问题:

解决后的代码示例如下:

在代码块(Block)中如果引入了self对象也可能会出现循环引用问题:self对象持有代码块的强引用,块持有self对象的强引用。

解决方法:在代码块外使用@weakify(self),解除强引用;在代码块内使用@strongify(self)确保在块执行期间self不会被释放。

3.4 使用ARC时需注意的场景
  • 批量创建临时对象时:

在循环中创建大量临时对象时,可能需要手动管理释放,以避免过高的内存峰值。

  • 多线程环境下:

在子线程中,系统不会自动创建自动释放池,因此需要手动创建。

3.5 ARC的优缺点

✅优点:

  1. 自动化管理:ARC负责插入内存管理方法的调用,开发者无需手动管理对象的引用计数
  2. 提高安全性:通过减少手动操作,ARC降低了出现内存泄漏和悬挂指针的风险
  3. 代码简明化:无需手动调用'retain'、'release'和'autorelease',代码更加简洁易读

❌缺点:

  1. 灵活性降低:在某些性能敏感的场景下,ARC可能会插入不必要的内存管理代码,影响性能

4. 常见内存管理问题

  • 内存泄漏

解决方案:

1)检查是否出现循环引用、存在未释放的对象等

2)使用XCode工具:XCode -> Open Developer Tool -> Instruments -> Leaks

  • 僵尸对象:指已经被释放的对象仍然被访问,导致未定义的行为或应用崩溃

解决方案:

1)检查’release’操作和’weak’、’unsafe_unretained’指针,确保不会访问释放后的对象

2)使用XCode工具:XCode -> Open Developer Tool -> Instruments -> Zombies

  • 循环引用

解决方案:

1)避免相互持有的对象之间使用强引用

2)使用XCode工具:XCode -> Open Developer Tool -> Instruments -> Allocations

  • 高内存占用:通常由未及时释放的大量对象或频繁创建的临时对象引起

解决方案:

1)识别内存占用较高的对象,检查它们是否可以被更早释放。

2)使用autorelease pool控制大量临时对象的生命周期,避免内存峰值。

3)使用XCode工具:XCode -> Open Developer Tool -> Instruments -> Allocations

5. 总结

实际工程应用中,一般直接用ARC就好了,更加快捷、安全。但ARC底层原理——引用计数,是与MRC相通的,因此了解相关知识,对于掌握iOS的内存管理技术大有裨益。 

Logo

欢迎加入西安开发者社区!我们致力于为西安地区的开发者提供学习、合作和成长的机会。参与我们的活动,与专家分享最新技术趋势,解决挑战,探索创新。加入我们,共同打造技术社区!

更多推荐