现在是求职季,OC的面试,肯定少不了内存管理,尽管现在基本都是ARC的项目,应该没有谁还在折腾MRC吧,不过这两天貌似有看到一个开源LeagueofLegends,还真是MRC,那会我看着,我只想说,哥们,你怎么如此蛋疼,没有别的意思,纯属闲扯,好吧,进入今天的话题。
记得从Xcode4.x版本后,具体是不是也不是特别记得了,Xcode新建立的工程,都默认勾选了支持ARC,不过我们可以在Build Settings里面将Objective-C Automatic Reference Counting 设为NO就可以切换成MRC了。
由于移动设备的内存极其有限,再加上,苹果为了让系统流畅,所以每个app所能占用的内存也是有限的,当app占用的内存较多时,就会收到内存警告,以代码的角度来说,程序里面的didReceiveMemoryWarning方法将会被调用,这时,你就需要考虑回收一些不需要使用的内存空间了,比如回收一些不需要的使用对象,清空一些不需要的缓存。如果不处理,当超过一定限度时,系统就会强制杀死你的APP,也就是我们常见的闪退现象。
在OC里面,任何继承了NSObject的对象都需要进行内存管理,因为他们是存放于堆里面的,而对于基础数据类型(int、double、float…)则不在此范围,因为他们是放在栈里面的,栈内存会被系统自动回收,而堆内存则需要由程序员来自己管理,后来苹果为了程序员能把更多精力放在业务上,所以堆出了ARC,但并不表示就不会有内存问题,只是苹果工程师为我们处理了大部分的,如block循环引用啥的,还是要我们自己处理的。
现在举下例子,定义了一个Person类,然后在main中调用:
|
|
|
|
|
|
在内存中,上面的代码如下图所描述的,
i和k由于是基本数据类型,所以他们是会放在栈里面的,而person则为指针变量,当执行完[[Person alloc]init]后,会在堆内存里面分配内存空间,如0xff11,而*person里面则指向了0xff11的地址空间,此时,person的引用计数为1,所以屏蔽了[person release]时,运行结果则不会打印Person delloc,原因是当对象的引用计数不为0时,对象不会被释放,自然也就不会调用Personr dealloc()方法了。
引用计数器:OC对象都有自己的引用计数器一般为4个字节的存储空间,它是一个整数,它表示对象自己正在被多少人引用或者使用的计数,当使用alloc,new或者copy创建一个对象时,对象的引用计数为1,每执行一次retain操作,引用计数加1,每执行一次release操作,则引用计数减1,所以不要字面上理解当执行了release后,对象就释放了,它仅仅是让计数器做-1的操作。所以当引用计数器为0时,系统会自动地给对象发送一条dealloc消息,也就是对象的dealloc方法会被调用,此时系统会回收这个对象,反之它将会一直存在,直到整个程序退出。因此我们可以通过重写对象的dealloc方法用来验证对象是否被销毁,但这并不意味着但我们能直接调用dealloc方法。
注意事项:当对象的引用计数为0时,对象将被回收,内存不再可用,如果我们还继续试图访问其内存空间,将会导致程序崩溃,报野指针错误(即我们常见的EXC_BAD_ACCESS)。
|
|
如上面这段代码,person的引用计数为1,却release3次,当第一次release后,此时,person已经被释放,当再次调用时,就会报出如下错误:
|
|
此时person已经是僵尸对象了,我们可以通过勾选如下选项来捕获:
再次运行时,你会看到,程序会定位到第二个release的地方,并给出错误原因
所以为了避免野指针错误的常见方法为:当对象被销毁后,将指向对象的指针变成空指针,即将person = nil;
苹果官方规定的内存管理原则:
- 谁创建谁release : 如果你通过alloc、new或[mutable]copy来创建一个对象,那么你必须调用release或autorelease
- 谁retain谁release :只要你调用了retain,就必须调用一次release
现在我们将程序改进一下,Person.h
|
|
Person.m
|
|
Cat.h
|
|
Cat.m
|
|
在main文件中调用:
|
|
结果输出是:
|
|
为什么cat在这里的引用计数为1,release了,却没有调用cat的dealloc方法,原因是在于retain,因为Person的变量cat的修改关键字为retain,那会他自动生成set方法时,实际上是这样子的,它会比较传入的cat是不是和本身的一致,如果不一致就先执行一次release,再进行retain操作
|
|
所以我们需要在Person对cat进行release,如下所示:
|
|
再运行程序时,你会看到如下结果:
|
|
可能有人会有疑问,为什么,retain关键字修饰时自动生成的是代码是那样子的,原因在于调用person.cat = cat时,所以我为了防止继续执行 [cat release]后,cat的引用计数为1,release一次后就被释放了,所以我们必须在set方法时,执行一次retain操作,为的是保住这只cat,如果person.cat = cat这句被反复调用时,就会导致cat被retain多次,所以为了保证引用计数的正确性,我们需要对cat进行比较,如果和_cat是相同对象就跳过,反之,则release旧值,retain新值。
另外对于会改变引用计数的另一个是autorelase,是苹果继release后ARC前推的另一个内存管理,当初始化一个对象后,调用autorelease,会返回对象本身,同时会将对象放到一个自动释放池中,但对象的计数器不变,当自动释放池销毁时,会对池子里面的操作都做一次release操作。
autorelease
好处 :关心对象的释放时间,不用关心什么时候调用release
注意 :占用内存较大对象,不要随便使用autorelease,因为可以导致单位时间内,内存无法及时被释放导致内存占用过大而引发系统警告或者崩溃,占用内存较小的对象则没有多少影响
至此,内存管理的相关内容已介绍完毕,仅是个人理解,写作仅是做为自己的笔记,为后续复习之用,如有不对之处,还望指正,谢谢。
注:版权声明:本文为博主原创文章,未经博主允许不得转载。