内存管理的必要性:
内存泄漏:程序未能释放已经不再使用的内存叫内存泄漏。
悬垂指针(野指针):指针指向已经被释放或回收的对象。
管理内存的三种方式:引用计数,自动引用计数,自动垃圾回收。
引用计数:
创建对象时,引用计数的初始值为1。为防止将要使用的实例对象被释放,会先向该实例对象发送retain,使其引用计数加1。
拥有实例所有权的对象叫所有者。
不再需要某个对象时,发送release,使引用计数减1.释放内存的不是release,而是dealloc。和alloc不同,dealloc是实例方法。方法retainCount可以获得对象引用计数的当前值。
在重写dealloc的方法中,在释放自身之前,首先要做好善后工作(释放所有需要释放的资源)。这些善后工作包括通过使用release放弃自身的实例变量的所有权。
自动释放机制:
自动释放池的典型用法如下:
id pool = [[NSAutoreleasePool alloc] init];
/*
在此进行一系列操作
给临时对象发送autorelease消息。
*/
[pool release];//销毁自动释放池,自动释放池中的所有的对象也被销毁。
在自动释放池中登录的实际上相当于放弃了所有权的对象,被称为临时对象。
使用自动释放池需要注意的地方:
autorelease虽然是NSObject类的方法,但必须和类NSAutoreleasePool一起使用。
临时对象的生成:
除了使用alloc,init方法创建一个对象这种标准的创建方法外,还有一种创建临时对象的方法。通过这种方法创建的对象都是临时对象,生成之后会被加入到内部的自动释放池。例如:Cocoa里用于处理字符串的类NSString,由UTF-8编码的c风格字符串生成NSString对象的方法有两个:
- (id)initWithUTF8String:(const char *)bytes
alloc生成的实例对象的初始化方法,生成的实例对象的初始引用计数为1
+(id)stringWithUTF8String: (const chat *) bytes
生成临时变量的类方法,生成的实例对象会被自动加入到自动释放池中。
这种类方法的命名规则是不以init开头,而以要生成的对象的类型(类名)作为开头。
同综合使用alloc init创建对象的方法相比,通过这种方法生成的对象的所有者不是调用stringWithUTF8String类方法的对象。
这种生成临时对象的类方法,在OC中称为便利构造函数或便利构造器。在面向对象的语言中,会把生成对象的函数叫做构造函数,把在内部调用别的构造函数而生成的构造函数叫做便利构造函数。在OC中,便利构造函数指的就是这种利用alloc,init和autorelease生成的临时对象的类方法。
常量对象:
内存中的常量对象(类对象,常量字符串对象等)的空间分配与其他对象不同,他们没有引入计数机制永远不能释放这些对象。给这些对象发送消息retainCount后,返回的是NSUIntegerMax(其值为0xffffffff,被定义为最大的无符号整数)。
单例模式:某个类仅能生成一个实例,程序中访问到这个类的对象时使用的都是同一个实例对象,在设计模式中这种情况被称为单例模式。Cocoa框架中有很多单例模式的应用,这些类通过以shared开头的类方法返回唯一的实例对象。
ARC概要:
ARC是一个编译期技术,利用此技术可以简化OC在内存管理方面的工作量。现在ARC只能管理OC对象,不能管理通过malloc申请的内存。
利用ARC编程要遵守的规则:
禁止调用引用计数相关的方法(retain,release,autorelease,retainCount)。
管理自动释放池的新语法:
禁止使用NSAutoreleasePool,而是使用新语法@autoreleasepool来管理自动释放池。格式如下:
@autoreleasepool {
}
在旧的写法中,在自动释放池被初始化至被释放期间,不可以使用break,return,goto等跳转语句,否则对象有可能无法被重新释放。
而在新的语法中,因为要运行到@autoreleasepool块外的时候进行对象的释放,所以可以使用跳转语句。
另外,@autoreleasepool在非ARC模式下也能用,并且使用@autoreleasepool比使用NSAutoreleasePool性能更好效率更高。
方法族:
同对象生成相关的方法集合叫作方法族。
采用引用计数方式管理内存时,如果不使用alloc/init/new/copy/mutableCopy 这些方法,或者不使用retain来保留一个对象,就不能成为对象的所有者。另外,只有使用release或autorelease,才能放弃这个对象的所有权。这些规定被叫做所有权策略。
由于ARC允许混合链接手动内存管理和自动内存管理的代码,所以需要定义能够让编译器明确区分的方法。
一个方法要属于某个方法族除了需要满足返回值和方法的类别方面的要求外,也需要满足以下命名规则:
即选择器的名字由方法族的名字加上非小写字母开头的字符串构成,或选择器通方法族名相同(开头的_可忽略 eg:_init:locale属于init方法族)
目前为止一共定义了五个方法族:alloc方法族,copy方法族,mutableCopy方法族,init方法族,new方法族。这些方法族均表示调用者对被创建的对象拥有所有权。
init开头的方法必须被定义为实例方法,其他四个开头的方法既可以是类方法,也可以是实例方法。切记严守内存管理相关的函数命名规则。不严格遵守可能会造成无法释放,或误释放。
ARC基本注意事项:
禁止调用引用计数相关的方法(retain,release,autorelease,retainCount)。
禁止使用NSAutoreleasePool,而是使用新语法@autoreleasepool来管理自动释放池
切记严守内存管理相关的函数命名规则
不用在dealloc中释放实例变量(但可以释放资源),也不需要调用[ super dealloc ];
编译代码时使用clang编译器,并加上编译选项-fobjc-arc
循环引用和弱引用:
像这种两个对象互相引用,或者像A持有B、B持有C、C持有A这样多个对象的引用关系并
成了环的现象,叫作循环引用或循环保持(retain cyele)。循环引用会造成内存泄漏,只有打破循环
引用关系才能够释放内存。
为了避免循环引用的出现,我们还需要另外一种类型的变量,这种变量能够引用对象,但不会成为对象的所有者,不影响对象本身的回收,所以ARC引入了弱引用的概念。弱引用是通过存储一个指向对象的指针创建的,且不保留对象。OC中用
_ _weak修饰符来定义弱引用。
通常声明的未加_ _weak修饰符的变量都是强引用(strong reference)类型的变量,声明时也可以通过加上_ _strong修饰符来明示变量是强引用类型。函数和方法的参数也是强引用类型。声明变量的时候,_ _weak或者_ _strong可以出现在声明中的任意位置,如下面的例子所示。但有一点要注意的是,最后一个声明的变量f前不可省略_ _weak。
_ _weak Nsobject *a,*b;
NSObject _ _weak *c,*d;
NSObject * _ _weak e, * _ _weak f;
这种用于修饰指针类型变量的修饰符被叫作生命周期修饰符或所有权修饰符。生命周期修饰符一共有四种:
_ _weak
_ _strong
_ _autoreleasing
_ _unsafe_unretained
弱引用会在其指向的实例对象被释放后自动变成nil,所以不用担心变成野指针。虽然弱引用好处有很多,但也不能滥用。下面这行代码是一个极端的例子:
_ _weakPeople*w = [[Peoplealloc]initWithName:"chenzhen”];
因为赋值了一个弱引用,所以生成的对象会被立刻释放。
对象之间引用关系的基本原则:
对象图中的环路就是循环引用产生的原因,使用ARC的时候应该尽量保证对象之间的关系呈树形结构避免同一个对象同时被两处引用。这样的话,当一个对象被释放的时候,这个对象的引用对象也会被自然的释放。
使用ARC编程时其他一些注意事项:
使用ARC的时候,如果既不想保持赋值的对象,也不想赋值的对象在释放后被自动的设为nil,可以使用生命周期修饰符:
_ _unsafe_unretained。该修饰符所修饰的变量称为非nil化的弱指针,也就是说,如果所指向的内存区域被释放了,这个指针就是一个野指针了。
还有一点要注意的是,手动内存管理时也有可能在赋值时没对变量进行retain操作,这就相当于在ARC中用_ _weak、 _ _unsafe_unretained修饰了变量的行为。因此,将这种代码迁移到ARC环境的时候一定要小心,需要重新考虑所有权方面的问题。
有一些类的实例不能使用自动化nil的弱引用,但可以使用 _ _unsafe_unretained来代替_ _ weak。
在使用ARC的程序中,id类型和void*类型之间不能进行转型。就算加了 _ _unsafe_unretained修饰符,转型操作在编译时也会报错。这是因为,iOS世界中主要有两种对象:Objective-C对象和Core Foundation对象。其中,Core Foundation类型的对象不在ARC的管理范畴内。因此,当转换这两种类型(一种有ARC管理,一种没有ARC管理)时,就需要告诉编译器怎样处理对象的所有权。为了解决这一问题,可以使用_ _bridge修饰符来实现id类型与void*类型的相互转换(更多详细内
容请参考附录B)。
有一种减少野指针出现的方法是,当不再使用传入的对象时,将其赋值为nil
通过函数的参数返回结果对象:
当一个函数或方法有多个返回值时,我们可以通过函数或方法的参数传入一个指针,将返回值写入指针所指向的空间。C语言中把这种方法叫作按引用传递(pass-by-reference ).Objective-C 的ARC中也有类似的方法,但采用了和C语言不同的实现方式,叫作写回传(pass-by-writeback),写回传经常被用于当一个方法在处理过程中出现错误时,通过指向NSError的二重指针返回错误的种类和原因(关于错误处理的详细内容,请参考18.5节)。下面这个声明是NSString的一个初始化函数,它会从指定的文件读入内容来完成NSString的初始化。如果初始化失败,则通过error返回错误的种类和原因。error是二重指针类型,*error指向的是NSError*类型的变量。
只可以把nil或临时变量的指针用于写回传,不可以把静态变量的指针,数组首地址的指针或内部变量的指针用于写回传。
将二重指针用于方法的参数时,可以给二重指针加上out修饰符。out修饰符原本时被用于提高调用分布式对象的效率的,使用写回传的情况下,方法调用和方法返回的时候都会发生值传递,而通过使用out修饰符,就可以使函数只在返回的时候发生值传递,从而省略调用函数时的值传递。
c语言数组保存objective-c对象:
ARC有效的程序中可以用c语言数组保存oc对象。
动态分配内存可以使用calloc函数。ARC中不可以使用memset(),bzero(),memcpy()等操作内存的函数,因为ARC会监视这些函数的行为,所以使用后就有可能造成内存段错误。
ARC有效的情况下,不可以在c语言结构体(或共用体)中定义oc对象,原因是编译器不能自动释放结构体内的oc对象。如果非要用的话,可以使用_ _unsafe_unretained来修饰结构体中的oc变量。这样一来编译器就不会管理这个内存的变量,完全需要手动管理内存。(引用计数也不可用)。