介绍Objective-C如何对实例对象进行内存管理。主要对基于引用计数的内存管理方式和Xcode
4.2之后可以使用的自动引用计数(ARC)的管理方式进行说明。
ARC是MAC OSX 10.7和ios 5 引入的新特性,也是苹果公司推荐使用的内存管理方法。启用ARC后,编译器会在适当的地方自动加入retain,release,atuorelease 等语句来简化Objective-C编程在内存管理方面的工作量。
5.1 动态内存管理
5.1.1 内存管理的必要性
C语言中需要手动利用malloc()和free() 对内存进行管理,当程序运行结束时,操作系统释放为它分配的内存,如果是很小,运行时间很短的程序,就算是内存没释放也不会有问题,程序结束时操作系统会自动释放内存。而对于长时间运行的程序,则需要程序员释放不再使用的内存,否则程序就会崩溃。
如果程序没能妥善管理内存,运行过程中就不但不能释放不再使用的内存,而且还会不停的分配内存,这样内存占用就会更多,程序速度也会越来越慢,最后会因内存耗尽而崩溃。
程序未能释放已经不使用的内存叫做内存泄露,有效的管理内存,会提供程序的执行效率。
如果访问了已经被释放的内存,则会造成数据错误,严重时甚至会导致程序异常终止。在指针所指向的对象已被释放或收回的情况下,该指针就成为悬锤指针或野指针,继续使用这种指针会造成程序崩溃。
Objective-C会通过向类对象发送alloc消息来生成实例变量,alloc的作用就是分配内存。alloc方法的返回值是id类型,我们前面介绍过id其实就是指针类型,而其所指向的就是实例对象分配的内存。生成的实例对象用完之后如果不被释放的话,就会发生内存泄露;另一方面,如果给已经释放的实例变量发送消息,可能会使程序异常终止,所以在Objective-C程序中管理好内存非常重要。
在面向对象的语言中,对象是程序的核心,而对象也有生命周期,既有从头到尾一直存在的对象,也有生命周期短暂的对象。对象之间也可能相互引用,构成复杂的数据结构,同面向过程的语言相比,面向对象的语言的内存管理要更加复杂一些。
5.1.2 引用计数,自动引用计数和自动垃圾回收
Cocoa环境的Objective-C提供了一种动态的内存管理方式,称为引用计数,这种方式会跟踪每个对象被引用的次数,当对象的引用次数为0时,系统就会释放这个对象的内存。这种内存管理的方式称为引用计数的内存管理。
比引用计数内存管理更高级一些的是自动引用计数,ARC,自动引用计数不需要考虑何时使用retain,release,autorelease来管理内存,它提供了自动评估对象生存期的功能,在编译期间会自动引用合适的管理内存的方法。
除了ARC之外,Objective-C2.0还引入了另一种自动内存管理机制——垃圾回收,使用垃圾回收时,就不需要通过引用计数来管理创建的对象,系统会自动识别哪些对象仍在使用,哪些对象可以回收。
手动引用计数在Mac和IOS上都可以使用,自动引用计数在Mac和IOS都可以使用。
垃圾回收只能在Mac上使用。
5.2 手动引用计数内存管理
5.2.1 引用计数
Cocoa环境的Objective-C使用了一种叫做引用计数的计数来管理对象所占用的内存。每个对象都有一个与之相关的整数,称作它的引用计数。
当某段代码需要使用一个对象,就将该对象的引用计数器值加1,当这段代码不再使用这个对象时,就将这个对象的引用计数器减1。
使用alloc和初始化方法创建一个对象时,该对象的引用计数的初始值为1,假设有一个类A在进行某些处理的过程中需要使用到实例B,为了防止实例B被别的对象随意释放,类A会事先给实例B发送一个retain消息,每执行一个retain消息,实例B的引用计数就会加1。
反之,不需要某个对象时,发送release消息,使对象的引用计数减少1。
实际上,释放内存的并不是release方法,而是dealloc方法,同alloc不同,dealloc不是类方法,而是一个实例方法,当对象的引用计数器为0时,对象的引用计数值就会减1,dealloc都是Objective-C程序制动发送的,通常不允许直接在程序内调用dealloc方法
生成或通过给对象发送retain消息来保持对象的这种状态,都可以说是拥有这个对象的所有权,拥有实例所有权的对象叫做所有者。
5.2.2 测试引用计数的例子
5.2.3 释放对象的方法
在自定义类的时候,如果类的实例变量是一个对象类型,那么,在销毁类的对象的时,也需要给类的实例变量发送release消息。
给对象发送release消息可以放弃对这个对象的所有权,但真正释放还是要给实例变量发送dealloc消息。
释放一个类的实例对象,为了彻底释放该实例对象所保持的所有对象的所有权,需要重写dealloc方法,在其他释放其他实例对象的所有权,因为最终释放的dealloc方法起作用,所以重写dealloc而不是release。
在重写dealloc方法中,在释放自身之前,首先要做好“善后”工作(释放所有需要释放的资源)。一般情况下,“善后工作”包括通过使用release放弃自身的实例变量的所有权,销毁对象时候不允许直接用dealloc,而是使用release。
子类的“善后工作”完成后,调用父类的dealloc方法来释放父类中定义的实例变量,这样内存释放会从子类一直向上直到NSObject,最终这个对象就会被测试释放掉。
5.2.4 访问方法和对象所有权
在通过访问方法等改变拥有实例变量所有权的对象时,必须注意实例变量引用计数的变化,合理安排release和retain的先后顺序。
- (void)setMyValue:(id)obj{
[obj retain];
[myValue release];
myValue = obj;
}
- (void)setMyValue:(id)obj{
if(myValue != obj){
[myValue release];
myValue = [obj retain];
}
}
- (void)setMyValue:(id)obj{
[myValue autorelease];
myValue = [obj retain];
}
如果不考虑对象所有权,单纯赋值,则不需要保持和释放
- (void)setMyValue:(id)obj{
myValue = obj;
}
静态对象
C语言中定义一个整数或结构体时,允许编译器在栈上为变量分配内存,如下所示
struct node rootnode;
那么,Objective-C的变量能否不通过alloc来动态分配内存,而在栈上分配内存呢?
例如,我们打算生成类Volume的一个实例对象v
Volume v;
error:interface type cannot be statically allocated
实践证明,MacOSX的OC不允许在栈上分配内存。
5.2.5 自动释放
在实际编程中,我们会遇到很多只用了一次就不再使用的对象。而如果这种对象也需要释放,那将是一件很麻烦的事。
Cocoa环境的Objective-C提供了一种对象自动释放的机制。这种机制的基本思路是把所有需要发送release消息的对象记录下来,等到需要释放这些对象时,会给这些对象发送release消息。
其中类NSAutoreleasePool(自动释放池)就起到了记录作用。
当向一个对象发送autorelease消息,实际上就是将该对象添加到NSAutoreleasePool,将他标记为以后释放。这个时候,因为这个对象没有被释放,所以还可以继续使用,对象引用计数值也没有变化,但发送autorelease和发送release消息一样,相当于宣布放弃了对象的所有权,这样一来,当自动释放池销毁时,池子记录的所有对象都会被发送release消息。
池子里记录的对象都是放弃了所有权的临时对象。
id pool = [[NSAutoreleasePool alloc]init];
[pool release];
5.2.6使用自动释放池时需要注意的地方
autorelease 虽然是NSObject类的方法,但必须和类NSAutoreleasePool一起使用。
自动释放池不存在对象时,不可以给对象发送autorelease消息,否则会出现运行时错误。
某些需要长时间运行的代码或大量使用临时对象的代码段可以通过定义临时的自动释放池来提高内存的利用率。例如,一个大量使用临时变量的循环中,经常会在循环开始时创建自己的自动释放池,在循环结束时释放这个自动释放池。
while(...){
id pool = [[NSAutroreleasePool alloc]init];
[pool release];
}
但要注意的是,如果在循环过程中通过continue或break跳出循环的话,将可能导致自动释放池本身没释放掉。另外,循环外也可以使用循环内生成的临时对象,但为了防止自动释放池的对象直接dealloc掉,需要在循环内retain,然后在循环外autorelease。
可以给实例对象发送多次retain消息,同样也可以发送多次autorelease消息,这样等于在自动释放池中多次添加该对象。
5.2.7 临时对象的生成
当你使用alloc,init方法创建一个对象时,该对象的初始引用计数为1.当不在使用该对象时,你要负责销毁他。
除了这种标准的创建对象方法外,还有一种创建临时对象的方法。通过这种方法创建对象都是临时对象,生成之后会被直接加入到内部的自动释放池,你不需要关心如何去销毁他。
例如,Cocoa里用于处理字符串的类NSString,由UTF-8编码的C风格字符串生成NSString对象的方法有二个。
- (id)initWithUTF8String:(const char *)bytes
alloc 生成的实例对象的初始化方法,生成的实例对象的初始化计数引用为1、
+ (id)stringWithUTF8String:(const char )bytes
生成临时变量的类方法,生成的实例对象会被自动加入到自动释放池中。
OC中的很多类都提供了这种生成临时对象的类方法。这种类方法的命名规则不以init开头,而以要生成的对象的类型作为开头。
同使用alloc,init创建对象的方法相比,通过使用这种方法生成的对象的所有者不是调用stringWithUTF8String类方法的对象。
这种临时生成对象的类方法,在OC中称为便利构造函数或便利构造器。一些面向对象的语言中把会生成对象的函数叫做构造函数,以和普通的函数进行区分,把在内部调用别的构造函数而生成的构造函数叫做便利构造函数。
在OC中,便利构造函数指的是这种利用alloc,init和autorelease生成临时对象的类方法。在后面要介绍的垃圾回收机制中,因为不需要区分临时对象,所以alloc和init组合在一起的类方法也称为便利构造函数。另外,对于需要参数的初始化方法,有时也会提供使用具有代表性的参数来生成实例对象的类方法,这些方法也称为便利构造函数。
5.2.8 运行回路和自动释放池
典型的GUI程序往往会在休眠中等待用户操作,例如,操作鼠标或键盘,点击菜单,按钮。在用户发出动作之前,程序将一直处于空闲状态。当点击事件发生后,程序会被唤醒并开始工作,执行某些必要的操作以响应这一事件。处理完这一事件,程序又会返回到休眠并等待下一个事件的到来。这个过程被叫做运行回路。
Cocoa在程序开始事件处理之前会隐式创建一个自动释放池,并在事件处理结束后销毁该自动释放池。所以,程序员在进行Cocoa的GUI编程时,就算不手动创建自动释放池,也可以使用临时对象。
5.2.9 常量对象
内存中常量对象(类对象,常量字符串对象等)的空间分配与其他对象不同,他们没有引用计数机制,永远不能释放这些对象。给这些对象发送消息retainCount后,返回的是NSUIntegerMax(Oxffffffff,被定义为最大的无符号整数)。
常量对象的生成和释放操作和一般对象有所不同,有时需要重写retain和release,但考虑到ARC和垃圾回收中无法重写这些方法,因此,从兼容性的角度来看,不推荐重写retain和release方法、
有的时候,我们可能会需要某个类仅能生成一个实例,程序中访问到这个类的对象时使用的是同一个实例对象,在设计模式中称为单例模式。Cocoa框架中很多单例模式的应用。例如控制程序运行的NSApplication,除此之外调色板和字体设置也都只有一个实例,这些类通过一shared开头的类方法返回唯一的实例对象。
单例模式的实现如下所示,建议定义一个无论何时都只返回同一个实例对象的类方法。但要注意的是,在继承和多线程的情况下,这个实现还需要完善。
const修饰表明无法改变指针所指向的内容。
5.3 分数计算器的例子
5.4 ARC概要
5.4.1 什么是ARC
采用计数方式管理内存时需要程序员管理所有生成对象的所有权。
ARC是一个编译器技术,用来减少OC在内存管理方面上的工作量。
ARC只能管理OC的对象,不能管理通过malloc申请的内存
5.4.2 禁止调用引用计数的相关函数
不能使用retain,release autorealse, retainCount 和@selector(reatain)
5.4.3 管理自动释放池的新语法
ARC中禁止使用NSAutoReleasePool,而是使用新语法@autorelease来管理自动释放池
@autoreleasePool{
}
id pool = [[NSAutoreleasePool alloc]init];
[pool release];
旧语法不能使用break,return,goto等跳转语句,否则对象可能无法释放成功
新语法可以,因为运行到@autoreleasePool块外的时候进行对象释放,所以可以跳转语句。
@autoreleasePool在非ARC模式下也可以使用,并且使用@autoreleasePool比使用NSAutoreleasePool性能更好效率更好,所以无论是否使用ARC,都推荐使用这种新的语法。
5.4.4 变量的初始值
在ARC中,未指定初始值的变量都会被初始化为nil。
但对于用autoreleasing和unsafe unretained 修饰的变量来说,初始值是未定的,而对象以外的变量的初值则和以前是一样的。
5.4.5 方法族
采用引用计数方式管理内存时,创建对象时就会拥有这个对象的所有权。例如采用alloc开头的类方法生成对象,并且使用以init开头的类方法来初始化对象的时候,就会获得这个对象的所有权,另外,使用名称中包含new,copy或mutableCopy的方法赋值对象的时候,也会获得这个对象的所有权。
采用引用计数方式管理内存时,如果不使用alloc/init/new/copy/mutableCopy这些方法或者不适用retain来保留一个对象,就不能成为对象的所有者。另外,只有使用release或者autorelease,才能够放弃这个对象的所有权。
目前一共定义了5中方法族
alloc方法族,copy,mutableCopy,new和init方法族,
init后面接大写字母,小写字母就不符合init方法族的要求。
5.5 循环引用和弱引用
5.5.1 循环引用
5.5.2 所有权和对象间的关系
5.5.3 弱引用
目前我们介绍ARC时提到的实例变量都是拥有所有权的实例变量(强引用类型,默认),但为了避免循环引用的出现,我们还需要另外一种类型的变量,这种变量能够引用对象,但不会成为对象所有者,不影响对象本身的回收。
为了实现这个目的,ARC中引入了弱引用的概念。弱引用是通过存储一个指向对象的指针创建的,且不保留对象。OC中用_ _weak修饰符来定义弱引用。
- - weak id temp;
未加上如上修饰符默认为强引用。
这种变量能够引用对象,但不会成为对象的所有者。(男人梦想的生活)
5.5.4 自动nil化的弱引用
弱引用会在其指向的实例对象被释放后自动变成nil,这就是弱引用的自动nil化功能,这样弱引用也不会变成野指针。