内存管理

概述

无论是ios还是android中,系统对每个程序运行时内存的占用都有一个限制,默认都是几十M左右大小,当程序占用的内存的大小超过限制时,程序可能就会被强制退出。

在内存中,分为堆和栈,栈中主要存放变量,堆中主要存放对象。栈中的东西是系统自动回收的,当一个变量使用完毕后,存放在栈中的东西会立刻被回收。但堆中存储的东西是不会随便回收的。

由于移动设备的内存有限,所以我们需要对内存进行严格的管理,以避免内存泄露造成资源浪费。在OC中,只有对象才属于内存管理范围,例如int、struce等基本数据类型不存在内存管理的概念。在iOS开发中,对内存的管理实际上就是对引用计数器的管理

 

相关概念

僵尸对象:所占用的内存已经被回收的对象,僵尸对象不能再使用

野指针:指向僵尸对象的指针,给野指针发送消息会报错

空指针:没有指向任何东西的指针(存储的东西是nil、null、0),给空指针发送消息不会报错。

 

目前OC内存管理有三种方式

(1)自动垃圾收集(Automatic Garbage Collection);

(2)MRC(Manual Reference Counting,中文名:手动引用计数器)和自动释放池(autorelease);

(3)ARC(Automatic Reference Counting,中文名:自动引用计数器)。

下面对这三种方式进行分别介绍。

 

一、自动垃圾收集

在OC2.0中,有一种自动垃圾收集的内存管理形式,通过垃圾自动收集,系统能够自动检测出对象是否拥有其他的对象,当程序运行期间,不被引用的对象就会自动释放。
但是在iOS运行环境中不支持自动垃圾收集,在OS X环境才支持,不过Apple现在不建议使用该方法,而是推荐使用ARC进行替代。

 

二、MRC和自动释放池

1、引用计数器的概念:

引用计数器一个对象被引用(使用)的次数,每个对象的引用计数器占用4个字节
如下图所示,当使用A创建一个对象object的时候,object的RC(引用计数器值)默认为1,当B也指向object的时候,object的RC+1=2。然后A指针不指向object的时候,object的RC-1=2-1=1。最后当B也不指向object的时候,object的RC-1=1-1=0,此时对象object被销毁。
说明: 当一个对象被创建的时候,该对象的RC默认为1;当该对象被引用一次,需要调用retain方法,使RC的值+1;当指针失去对该对象的引用,需要调用release方法,使RC的值-1;当RC=0的时候,该对象被系统自动销毁回收。

1196535-9f2ab53cf44c8f3e

 

2、引用计数器的操作

(1)给对象发送一条retain消息,可以使RC+1(retain方法返回对象本身)

(2)给对象发送一条release消息,可以使RC-1

(3)给对象发送一条retainCount消息,可以获得当前的引用计数器值

 

3、对象的销毁

(1)当一个对象的RC为0时,那么它将被销毁,其占用的内存会被系统回收。

(2)当一个对象被销毁时,系统会自动向对象发送一条dealloc消息

(3)一般会重写dealloc方法,在这里释放相关资源,dealloc就像对象的遗言。

(4)一旦重写了dealloc方法,就必须调用[super dealloc],并且放在最后面调用。

(5)不要直接调用dealloc方法。

(6)一旦对象被回收了,它占用的内存不再可用,坚持使用就会导致程序崩溃(指针错误)。

 

4、MRC

MRC即我们通过人为的方式来控制引用计数器的增减,影响对象RC值得方法有以下几种:

(1)new、alloc、copy、mutableCopy,这几种方法用来创建一个新的对象并且获得对象的所有权,此时RC的值默认为RC=1

(2)retain,对象调用retain方法,该对象的RC+1;

(3)release,对象调用 release方法,该对象的RC-1;

(4)dealloc,dealloc方法并不会影响RC的值,但是当RC的值为0时,系统会调用dealloc方法来销毁对象。

例如:

通过上面代码知道,成员变量的设值和取值方法是手动生成的,而且setter方法中成员变量的引用计数器也是手动设置,我们也可以通过@property以及相应关键字来由编译器生成。
关于在MRC中@property关键字如下:
(1)assign和retain和copy
这几个关键字用于setter方法的内存管理,如果使用assign(一般用于非OC对象),那么将直接执行赋值操作;如果使用retain(一般用于OC对象),那么将retain新值,release旧值;如果使用copy,那么将release旧值,copy新值。不显示使用assign为默认值。
(2)nonatomic和atomic
这两个关键字用于多线程管理,nonatomic的性能高,atomic的性能低。不显示使用atomic为默认值


(3)readwrite和readonly
这两个关键字用于说明是否生成setter方法,readwrite将自动生成setter和getter方法,readonly 只生成getter方法。不显示使用readwrite为默认值。
(4) getter和setter
这两个关键字用于给设值和取值方法另外起一个名字。例如@property(getter=a,setter=b:) int age;相当于取值方法名为a,设值方法名为b:。
如果使用@property属性,那么上面代码可以改为:

 

循环引用内存管理原则

对于两个类A包含B,B包含A的循环引用情况下,看如下代码:

下面分析主函数代码,当执行Book1 *b1=[[Book1 alloc] init]后,b1指向Book1。当执行Book2 *b2=[[Book2 alloc] init]后,b2指向Book2。 当执行b1.book2 = b2后,Book1的成员变量_b2指向Book2。当执行b2.book1 = b1后,Book2的成员变量_b1指向Book1。内存中具体关系如下图所示。

此时Book1的引用计数器RC=2,Book2的引用计数器RC=2。
当执行 [b1 release]后,b1释放对Book1的控制权,此时Book1的引用计数器RC=2-1=1。
当执行[b2 release]后,b2释放对Book2的控制权,此时Book2的引用计数器RC=2-1=1。
那么由于仍有指针指向Book1和Book2,这时内存中Book1和Book2的关系如黑色椭圆内所示。所以并不会调用dealloc函数,所以Book1和Book2并不会毁销,这样就造成了 内存泄露。

1196535-6762b823bff21e60

对于上面这种情况,只需要在Book1和Book2的@property属性声明中一端使用retain,一端使用assign。即将@property(nonatomic,retain) Book1 *_book1或者@property(nonatomic,retain) Book2 *_book2中的一个retian改为assign。具体原因自己分析。

 

对于下面这种循环引用情况,只能使用assign

大家可以分析一下,如果@property(nonatomic,assign)id instance; 中将assign换为retain,那么也将造成内存泄露。

5、Autorelease Pool的使用

顾名思义,autorelease即自动释放对象,不需要我们手动释放。从上面代码我们知道,在主函数中,创建对象obj后,总要手动调用[obj release]方法,这样无疑使工作量变大,且对我们的技术增长毫无意义。为了减少这种无意义的工作,可以使用Autorelease Pool方式。

在iOS程序运行过程中,会创建无数个池子。这些池子都是以栈结构存在(先进后出),当一个对象调用autorelease方法时,会将这个对象放到栈顶的释放池。
Autorelease Pool即自动释放池,在Autorelease Pool内的对象在创建时只要调用了autorelease方法,那么在该池子内的对象的最后的release方法的调用将由编译器完成。

如下图:

snip20161216_1

 

autorelease的具体使用方法如下:

(1)生成并持有NSAutoreleasePool对象

(2)调用已分配对象的autorelease实例方法

(3)废弃NSAutoreleasePool对象

 

那到底什么时候废弃NSAutoreleasePool对象呢?

在Cocoa框架中,相当于程序主循环的NSRunLoop或者在其他程序可运行的地方,对NSAutoreleasePool对象进行生成、持有、和废弃处理。因此,开发者不一定非得使用NSAutoreleasePool对象来进行开发工作。如下图:

snip20161216_3

 

NSAutoreleasePool对象的生存周期如下:

snip20161216_2

 

 

Autorelease Pool的创建有两种方式:

例如:

在返回对象的方法中最好使用自动释放池释放对象,因为如果将新创建的对象作为返回值,由于在返回该对象之前并不能释放该对象,所以可以通过自动释放池来延迟该对象的释放。示例代码如下:

 

根据Apple文档的建议,如果在一个循环中产生了大量的临时对象,可以通过在循环内部提供一个自动释放池在下一次迭代之前来清除这些对象,以减少最大内存占用。

 

autorelease错误用法

(1) alloc之后调用了autorelease,又调用release

 

 

(2) 连续调用多次autorelease

 

 

注意:

(1)release方法不能多次调用,该调用的时候调用,否则容易造成野指针错误。

(2)创建对象时多次调用autorelease方法,容易造成野指针错误。

(3)在自动释放池中新创建的对象并不是一定会添加到释放池中,例如由new、alloc、copy、mutableCopy创建的对象并不会加到自动释放池中,并且必须手动调用release方法才能释放对象。如果想让新创建的对象加入到自动释放池中,就必须调用autorelease方法。

(4)使用autorelease方法并不会使引用计数器的值增加,只是表示将该对象加入到自动释放池中。
三、ARC

 

ARC是编译器特性,可以理解为编译器xcode的功能。它主要用来帮助用户做内存管理工作,优化开发流程。特别注意的是它与java的垃圾回收机制不是同一个东西,它只是xcode的一个功能而已!在ARC下,RC将由编译器来自动完成对象引用计数器的控制,不需要手动完成。

 

1、ARC判断准则

只要没有强指针指向对象,就会释放对象。就算此时仍有弱指针指向该对象或者该对象还指向其他对象,只要没有强指针指向它就会释放对象。系统还会根据弱指针的情况及时释放弱指针对象,避免野指针的产生。

指针分两种:

(1)强指针:默认情况下,所有指针都是强指针 (_strong), 也可将strong作为参数传给@property

(2)弱指针:定义一个指针是弱指针,只需在定义时用_weak声明即可,比如:_weak Person *p = [[Person alloc] init];也可将weak作为参数传给@property

 

2、ARC特点

(1) 不允许调用release、retain、retainCount

(2) 允许重写dealloc,但是不允许调用[super dealloc]

(3) @property的参数

* strong :成员变量是强指针(适用于OC对象类型)

* weak :成员变量是弱指针(适用于OC对象类型)

* assign : 适用于非OC对象类型

(4) 以前MRC下的retain改为用strong

【备注】在实际项目中有这种需求,某些文件需要用到release、retain方法,比如下载的第三方框架中如果有release等方法,放到支持ARC的编译环境中肯定会报错。这时我们可以设置这些文件不使用ARC而项目中其他文件继续使用ARC,方法很简单,选择项目的Bulild phases,在Compile Sources中选择不需要ARC的文件,双击或按回车,在弹出的窗口中输入-fno-objc-arc即可。同样也可以在非ARC的环境中输入-f-objc-arc使相关文件支持ARC。

ARC模式下,创建的新对象通常由以下几种关键字来限定。(1)__strong(默认值),由__strong修饰的为强指针,对象只要有强指针指向就不会被销毁;每当一个强指针指向一个对象,该对象的的RC+1;

(2)__weak,由__weak修饰的为弱指针,弱指针所指向的对象并不会改变RC值,弱指针只表示是对对象的引用;当弱指针所指向的对象销毁时,该弱指针的值变为nil;

(3)__unsafe_unretained,__unsafe_unretained修饰的对象指针所指向的对象也不会改变RC值,也只表示是对对象的引用;当所指向的对象销毁时,该指针的值不会变为nil,仍是保留原有的地址;

在ARC模式下,MRC中的retain、release等方法变的不可用,因为ARC是不需要我们手动管理内存的,一切由编译器完成。
MRC模式下,将一个对象指针赋值给另一个对象指针如下:

但是在ARC模式下,我们完全可以不关心具体怎么操作,只需要直接进行赋值即可:

 

3、ARC模式下的循环引用

在ARC模式下,@property属性关于内存管理的修饰符为strong和weak(MRC下的retain和assign不可用),表示声明为强指针还是弱指针。通常情况下都是使用strong来修饰,但是在循环引用却不是。

下面这种情况一端使用strong修饰,一端使用weak修饰。如果都使用strong修饰,那么将造成对象的循环保持,造成内存泄露。

 

下面这种循环引用情况,只能使用weak。如果使用strong修饰,那么将造成对象的循环保持,造成内存泄露。

 

注意:

(1)ARC模式下仍能使用自动释放池;

(2)MRC下的retain、release、retainCount、autorelease等方法不可使用。

(3)注意循环引用下strong和weak的选择。


本项目是一个基于SSM(Spring+SpringMVC+MyBatis)框架和Vue.js前端技术的大学生第二课堂系统,旨在为大学生提供一个便捷、高效的学习和实践平台。项目包含了完整的数据库设计、后端Java代码实现以及前端Vue.js页面展示,适合计算机相关专业的毕设学生和需要进行项目实战练习的Java学习者。 在功能方面,系统主要实现了以下几个模块:用户管理、课程管理、活动管理、成绩管理和通知公告。用户管理模块支持学生和教师的注册、登录及权限管理;课程管理模块允许教师上传课程资料、设置课程时间,并由学生进行选课;活动管理模块提供了活动发布、报名和签到功能,鼓励学生参与课外实践活动;成绩管理模块则用于记录和查询学生的课程成绩和活动参与情况;通知公告模块则实时发布学校或班级的最新通知和公告。 技术实现上,后端采用SSM框架进行开发,Spring负责业务逻辑层,SpringMVC处理Web请求,MyBatis进行数据库操作,确保了系统的稳定性和扩展性。前端则使用Vue.js框架,结合Axios进行数据请求,实现了前后端分离,提升了用户体验和开发效率。 该项目不仅提供了完整的源代码和相关文档,还包括了详细的数据库设计文档和项目部署指南,为学习和实践提供了便利。对于基础较好的学习者,可以根据自己的需求在此基础上进行功能扩展和优化,进一步提升自己的技术水平和项目实战能力。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值