iOS内功篇:内存管理

465 查看

前言

现在iOS开发已经是arc甚至是swift的时代,但是内存管理仍是一个重点关注的问题,如果只知盲目开发而不知个中原理,踩坑就跳不出来了,理解好内存管理,能让我们写出更有质量的代码。

内存管理是程序设计中很重要的一部分,程序在运行的过程中消耗内存,运行结束后释放占用的内存。如果程序运行时一直分配内存而不及时释放无用的内存,会造成这样的后果:程序占用的内存越来越大,直至内存消耗殚尽,程序因无内存可用导致崩溃,这样的情况我们称之为内存泄漏。

ObjC的内存管理比较简洁,然而要深刻理解也不是一件易事,本文将介绍如何使用ObjC进行内存管理。

1 引用计数

  在ObjC中,对象什么时候会被释放(或者对象占用的内存什么时候会被回收利用)?

答案是:当对象没有被任何变量引用(也可以说是没有指针指向该对象)的时候,就会被释放。

  那怎么知道对象已经没有被引用了呢?

ObjC采用引用计数(reference counting)的技术来进行管理:
1)每个对象都有一个关联的整数,称为引用计数器
2)当代码需要使用该对象时,则将对象的引用计数加1
3)当代码结束使用该对象时,则将对象的引用计数减1
4)当引用计数的值变为0时,表示对象没有被任何代码使用,此时对象将被释放。

  与之对应的消息发送方法如下:

1)当对象被创建(通过alloc、new或copy等方法)时,其引用计数初始值为1
2)給对象发送retain消息,其引用计数加1
3)給对象发送release消息,其引用计数减1
4)当对象引用计数归0时,ObjC給对象发送dealloc消息销毁对象

  下面通过一个简单的例子来说明:

场景:有一个宠物中心(内存),可以派出小动物(对象)陪小朋友们玩耍(对象引用者),现在xiaoming想和小狗一起玩耍。
新建Dog类,重写其创建和销毁的方法

在main方法中创建dog对象,給dog发送消息

输出结果为

可以看到,引用计数帮助宠物中心很好的标记了小狗的使用状态,在完成任务的时候及时收回到宠物中心。

  思考几个问题:

1)NSString引用计数问题
如果我们尝试查看一个string的引用计数

会发现引用计数为-1,这可以理解为NSString实际上是一个字符串常量,是没有引用计数的(或者它的引用计数是一个很大的值(使用%lu可以打印查看),对它做引用计数操作没实质上的影响)。

2)赋值不会拥有某个对象

这里仅仅是指针赋值操作,并不会增加name的引用计数,需要持有对象必须要发送retain消息。

3)dealloc
由于释放对象是会调用dealloc方法,因此重写dealloc方法来查看对象释放的情况,如果没有调用则会造成内存泄露。在上面的例子中我们通过重写dealloc让小狗被释放的时候打印日志来告诉我们已经完成释放。

4)在上面例子中,如果我们增加这样一个操作

会发现获取到的引用计数为1,为什么不是0呢?

这是因为对引用计数为1的对象release时,系统知道该对象将被回收,就不会再对该对象的引用计数进行减1操作,这样可以增加对象回收的效率。
另外,对已释放的对象发送消息是不可取的,因为对象的内存已被回收,如果发送消息时,该内存已经被其他对象使用了,得到的结果是无法确定的,甚至会造成崩溃。

2 自动释放池

现在已经明确了,当不再使用一个对象时应该将其释放,但是在某些情况下,我们很难理清一个对象什么时候不再使用(比如xiaoming和小狗玩耍结束的时间不确定),这可怎么办?

ObjC提供autorelease方法来解决这个问题,当給一个对象发送autorelease消息时,方法会在未来某个时间給这个对象发送release消息将其释放,在这个时间段内,对象还是可以使用的。

  那autorelease的原理是什么呢?

原理就是对象接收到autorelease消息时,它会被添加到了当前的自动释放池中,当自动释放池被销毁时,会給池里所有的对象发送release消息。

这里就引出了自动释放池这个概念,什么是自动释放池呢? 顾名思义,就是一个池,这个池可以容纳对象,而且可以自动释放,这就大大增加了我们处理对象的灵活性。

  自动释放池怎样创建?

ObjC提供两种方法创建自动释放池:
方法一:使用NSAutoreleasePool来创建