对于任何编程语言来说,正确地管理内存是必不可少的。如果内存管理不当,就会导致内存泄露,出现野指针错误等问题

Java 的内存是由运行中 JVMGC 管理的,Java 中没有指针,不过有和指针相似的概念,叫做引用

当我们new一个Java对象的时候,我们通常会用一个引用来接受这个对象,那么这个引用的值就是new对象的内存地址,相当于这个引用指向了该内存区域。

GC检测到一块内存没有引用指向它的时候,GC会回收掉这块内存,GC的过程对于程序员来说是隐式的,开发者无需像C/C++那样编写大量内存管理代码,一切交给GC即可

所以Java的开发效率一直比较高,开发者可以将全部精力放在业务代码上

Objective-C 的内存管理

Objective-C没有GC机制,在 iOS 5,Xcode 4.2 之前,开发者写Objective-C(iOS)程序需要手动管理内存。

Objective-C使用引用计数来管理内存

Xcode4.2 引入了新功能,自动引用计数( Automatic Reference Counting )简称 ARC

ARC的特性极大地降低了开发难度,为了和以前的手动管理内存(不用ARC)做出对比

手动管理内存( Manual Reference Counting )简称为 MRC

TIPS:在使用MRC时,最好开启Xcode的内存诊断,以免导致MRC看起来失效的错误

引用计数

Objective-C中 的所有对象(继承自NSObject)内部都有一个属性,叫做引用计数

引用计数大于0时,对象的内存不会被回收

引用计数减为0时,对象的内存将会立即回收

内存回收的概念

通常我们说一块内存被回收,并不是这块内存的值被清空,而是标记为不可用

因为对内存读写是需要消耗性能,为了节约性能只需要标记不可用(无访问权限)即可

所以当对象的内存被回收时,对象的成员变量仍然存在于这块内存中

甚至可以通过指针来访问(C / C++)

存储对象内存是不可用状态,但是指向这块内存的指针仍然在栈里

这个指针指向了它不应该(或者说禁止)访问的内存,我们称这类指针为 野指针

空指针和野指针是不一样的概念,空指针是指不指向任何内存的指针(指针的值为0)

  • Java 中,使用空指针和野指针会直接报错

  • Objective-C 中,使用野指针会报错,(不能给野指针发送消息),给空指针发送消息不会报错

内存管理的规则

  • 谁创建,谁释放

一个函数中有创建,那就一定要有释放

不是你创建的,就不用你去释放

  • 谁持有,谁负责

在开发过程中,很多时候一个类需要持有不是它本身创建的对象(其他类创建的)

持有了这个对象,就要对这个对象的生命周期负责,不需要的时候进行释放

引用计数通过对象方法 retainCount 来获取

创建对象

当我们调用alloc/new/copy方法开辟内存空间创建新对象的时候,这个对象的引用计数器会加1

即使不用一个指针(或者说对象)去指向它,这个对象的内存空间也不会被回收

这一点和 Java 不一样,Java 新建对象后,如果不引用,那么内存就会被直接回收

持有对象

之前提到过,通常在开发过程中,一个类会以另一个类作为属性,通常这个属性对应的对象不是当前类创建的

另一个类创建完之后就会释放,所以我们需要在释放前持有这个对象(这个时候引用计数为2,即使释放一次减1,还有1),让这个对象的引用计数不为0,从而使用这个对象(不要让系统回收)

持有对象的方法是 retain

释放对象

当我们不需要一个对象的时候,如果是我们负责的,需要释放这个对象,否则会造成野指针错误

释放对象会让引用计数减1,不是减为0

释放对象的方法是 release

销毁对象

当一个对象的引用计数减为0时,OC将自动给这个对象发送一条dealloc消息, 调用对象的dealloc方法

重写dealloc方法时,要在最后调用父类的dealloc

如何正确管理内存 (MRC 举例)

假设现在有一个学生类,书类

学生可以选一本书读,书作为学生的属性,学生有读书的方法

全部使用@property 自动生成的settergetter

只重写Book 的 dealloc

main.m

stu持有了book,book的引用计数变为2

可以看到释放stu之后,book并没有被释放

改进一下Student的dealloc方法,就可以解决

改进之后运行

可以看到book 成功释放

由此我们可以得出一个结论

  • 在Xcode 13关闭ARC的情况下,@property 自动生成的setter,getter方法内部是有内存管理相关代码的,不仅仅是标准实现
  • 正确管理内存还需要重写dealloc方法,将成员变量释放掉

底层实现

这次我们不使用@property,@synthesize全部手写来实现

首先看一下默认实现的问题

main.m 中 这个函数实现本身没有问题,因为遵循 谁创建,谁释放 的规则

第14行调用了 setBook方法,没有对book进行任何处理

16行结束后,book的引用计数变为0,被立即释放

导致后面访问book就会报错(野指针错误)

分析原因

为什么会出现野指针错误?setBookForStu()这个函数本身没有任何问题,book对象是它创建的,所以在这个函数执行完毕时,必须要将book释放(假设没有其他人使用)

但问题在于,函数内有个stu它需要持有book对象,持有是调用stu的 setBook方法

setBook方法内部并没有对book 进行retain操作,引用计数没有增加,第16行执行完后,book被回收,就变成了一个野指针

怎样保证book不被回收呢,只需要在持有的时候调用一次retain,让引用计数+1 变成 2,这样即使原调用者释放一次引用计数还有1

改进的setter实现

版权声明:本文为clutchpowers原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://www.cnblogs.com/clutchpowers/p/16791659.html