OC学习10——内存管理

时间:2022-10-08 15:03:27

1、对于面向对象的语言,程序需要不断地创建对象。这些对象都是保存在堆内存中,而我们的指针变量中保存的是这些对象在堆内存中的地址,当该对象使用结束之后,指针变量指向其他对象或者指向nil时,这个对象将称为无用对象,因为没有指针指向它了,这种情况称为内存泄漏。当内存泄漏非常严重时,会导致内存不够用,程序就会崩掉。因此,内存管理是学习面向对象语言中非常重要也是非常头疼的一个问题。在Java、C++、OC等语言中都涉及到这些问题,Java的内存管理是非常轻松的,因为这些内存管理的工作都由虚拟机自动去完成,不需要程序员自己管理,C++就苦逼了,需要程序员时时刻刻注意内存管理,防止内存泄漏。而对于我们学习的OC语言,内存管理已经从最开始需要我们进行手动管理发展倒现在可以自动管理了,尽管现在已经不需要我们过多地关心内存管理问题,但是还是有必要了解一些基本概念和思想,这也是本篇文章的出发点。

2、内存管理其实主要就是两个方面的内容:内存分配和内存回收。

  • 内存分配:当程序创建对象时需要为对象分配内存,采用合理的实际,尽量减少对象的创建,并减少创建过程中的内存开销。一般而言,内存的分配工作相对而言对程序的影响小一些,即使程序在一段时间内创建了过多的对象,造成了较大的内存开销,但是只要这些对象占用的内存得到了及时回收,程序也依然可以正常运行。而且,内存的分配操作相对而言非常简单,当程序创建对象时,系统会自动为这些对象分配内存。
  • 内存回收:当程序不再需要对象时,系统必须及时回收这些对象所占用的内存,以便程序可以再次使用这些内存。相比之下,内存回收操作就复杂得多,管理起来也非常困难。因此,人们开发了多种内存回收机制,典型的回收机制有两种:自动回收(系统自动跟踪对象进行适时回收对象)和混合回收(系统和程序员共同完成回收工作)。

3、在Xcode4.2之前,OC的内存回收需要程序员花费大量的精力去理解内存回收相关的理论知识,并且程序中必须通过retain、release、autorelease等方法去管理对象的引用计数,这样才能让程序正常回收内存。Xcode4.2引入了新特性:自动引用计数(Automatic Reference Counting,ARC),ARC机制将会自动释放对象所占用的内存,通过启用ARC特性,我们不再需要重点关注内存回收相关的内容。

4、程序创建一个对象之后,怎么知道该对象什么时候应该被回收呢?OC中采用的是引用计数的机制来跟踪对象的状态:每个对象都有一个与之关联的整数,这个证书被称之为引用计数。在正常情况下,当一段代码需要使用某个对象时,应将该对象的引用计数加1,当这段代码不再需要该对象时,应该将该对象的引用计数减1,表示这段代码不再访问该对象了。当对象的引用计数为0时,表明没有任何程序需要该对象了,系统就会回收该对象所占用的内存。系统在销毁该对象之前,会自动调用该对象的dealloc方法(该方法继承自NSObject)来执行一些回收操作。如果该对象还持有其他对象的引用,此时必须重写dealloc方法,在该方法中释放该对象所持有的其他对象(通常就是将所持有对象的引用计数减1)。

  • 手动引用计数回收的标准就是:当一个对象的引用计数为0时,就表明程序不再需要该对象了,从而通知系统回收该对象所占用的内存,系统调用dealloc方法执行回收操作,然后销毁该对象。手动引用计数的难点就说程序员要保证每个对象具有正确的引用计数。
  • 程序员千万不要主动地去掉用对象的dealloc方法!当一个对象的引用计数为0 时,系统会自动取调用该对象的这个方法。
  • 当对象被销毁之后,此时该对象已经不存在,如果有一个指针指向这个被销毁的对象,这个指针就被称为悬空指针,调用悬空指针指向对象的方法时,程序会出现未知结果,甚至导致程序崩溃。

5、在手动引用计数中,改变对象引用计数的方式如下:

  • 当程序员调用方法以alloc、new、copy、mutableCopy开头的方法来创建对象时,该对象的引用计数加1.(方法名一定要是驼峰格式才可以)
  • 程序员调用retain方法时,该对象引用计数加1.
  • 程序员调用release方法时,该对象的引用计数减1.

  NSObject中提供了有关引用计数的如下方法:

  • - retain:该对象引用计数加1.
  • - release:该对象的引用计数减1.
  • - autuorelease:不改变该对象引用计数的值,只是将该对象放入到自动释放池中
  • - retainCount:返回该对象的引用计数的值
     Person *person = [[Person alloc] init];//引用计数为1
    NSLog(@"引用计数:%ld",[person retainCount]); //引用计数加1,为2
    [person retain];
    //引用计数减1,为1
    [person release]; //打印结果是1
    NSLog(@"引用计数:%ld",[person retainCount]);
    //再次释放后引用计数减1,为0 ,系统将自动销毁对象
    [person release];

5、手动引用计数的基本思路就是谁(包括对象、函数等)把对象的引用计数加1,谁就要负责在“临死”前把该对象的引用计数减1,也就是说任何实体(包括对象、函数)在结束签都应该把其他对象的引用计数回复到开始前的状态。从这个角度来看,如果一个函数方法中返回一个对象,那么这个对象在方法结束后由于计数减1为0会被销毁,无法有效地将有效的指针对象返回给调用者,但是如果不按照上面的思路则又破坏了手动引用计数的基本原则。因此,我们需要一种有效而又优雅的解决方案——自动释放池。

  所谓自动释放池(OC中有一个专门管理的类NSAutoreleasePool),就说一个存放对象的容器(比喻集合),自动释放池会保证延迟释放该池中所有的对象。所谓的自动释放,其实只是依次调用池中的每一个对象的release方法,将池中所有对象的引用计数减1.那么什么时候会自动释放呢?其实自动释放池(NSAutoreleasePool)的本质是采用MSMutableArray集合作为容器,当把对象添加到池中时实际上是把这些对象添加到MSMutableArray集合中,然后程序重写了MSMutableArray的release方法,并在该方法中依次调用容器中的每个对象的release方法。所以,当自动释放池(NSAutoreleasePool对象)调用release方法时我们加入自动释放池中的对象也就进行了释放。

  如何将一个对象加入自动释放池呢?在前面第四点的时候降到了NSObject方法中有一个autorelease方法,任何对象调用该方法就可以将该对象加入到自动释放池中。

6、临时对象:对于Foundation中的类而言,当调用方法创建对象时,只要不是以alloc、new、copy、mutableCopy开头的方法创建出来的对象,系统就会默认创建自动释放的对象,这些对象称之为临时对象。

7、手动内存管理的规则总结如下:

  • 调用对象的release方法并不是销毁该对象,只是将该对象的引用计数减1,当一个对象的引用计数为0时,系统会自动调用该对象的dealloc方法来销毁该对象。
  • 当自动释放池被回收时,自动释放池会依次调用该池中的每一个对象的release方法。如果该对象调用release方法后引用计数为0 ,那么该对象将被回收,否则该对象可以从自动释放池中活过来。
  • 当程序使用alloc、new、copy、mutableCopy开头的方法创建对象后,该对象的引用计数为1,当不再使用该对象时,需要调用该对象的release或autorelease方法
  • 如果使用retain方法为对象增加过引用计数,则用完该对象后需要调用release方法来减少该对象的引用计数,并保证retain次数和release次数相等。
  • 如果通过其他方式获取了对象,且对象是一个临时对象,若是在自动释放池上下文中使用该对象,那么使用完成之后无需理会对象的回收,系统会自动回收该对象。如果程序需要保留该临时对象,可以手动调用retain方法来增加该对象的引用计数,或者将该临时对象赋值给retain、strong或copy指示修饰符的属性。
  • 在Cocoa或IOS的事件循环中,在每个事件处理方法执行之前会创建自动释放池,方法执行完后曾之后会回收自动释放池。

8、在IOS 5 引入ARC之后,OC编程就不再需要过多地关注内存管理这一块的内容了。对于IOS开发者而言,最新的Xcode在创建IOS项目时已经默认已经开启ARC机制了,当然,你也可以通过项目属性选择关闭或开启ARC。"Building Setting"---> “Language Objective C” ---> "Objective - C Automatic Reference Counting"

OC学习10——内存管理

9、现在最新的Xcode在代码中基本上会自动定义@autoreleasepool块,这其实就是自动释放池的上下文,任何在该上下文中创建的对象都由喜用的ARC来自动进行管理释放,并在释放结束后消除这些对象。合理使用@autoreleasepool相当于创建了一层自动释放区域:所有在@autoreleasepool范围内创建的变量,都会在@autoreleasepool结束时执行一次release,这样就可以保证这些对象在@autoreleasepool释放时获得提前释放的机会,从而降低了内存的占用率。

10、在正常情况下,如果某个函数很长,且在该函数运行过程过程中出现很多中间变量,占据了大量的内存,或者程序在执行过程中创建了大量的临时对象(比喻在循环中创建对象),程序可能需要多次释放这些临时对象,这次程序可以考虑将@autoreleasepool块放在循环体内。

 for(int i =  ; i <  ; i++)
{
@@autoreleasepool{
//创建临时对象
//调用临时对象的方法
。。。
}
}