在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
1 传统内存管理 Objective-C对象的生命周期可以分为:创建、存在、消亡。 1.1 引用计数 类似Java,Objective-C采用引用计算(reference counting)技术来管理对象的生命周期。每个对象都定义有一个整数(称引用计数器)与之相关联,该数用以表示当前有多少个指针指向该对象。 1.1.1 操作方法 当某段代码需要访问一个对象时,该代码就将对象的保留计数值加1;当结束访问时就减1;若引用计数器减到0时,该对象将被销毁。引用计数器的值由如下三种操作进行控制: 1) 创建 当使用alloc、new方法或者通过copy消息(接收到消息的对象会创建一个自身的副本)创建一个对象时,对象的保留计数器值就被初始化为1。 2) 增加 要增加对象的引用计数器值,可以给对象发送一条retain消息,即调用对象的retain方法。 3) 减少 要减少对象的引用计数器值,可以给对象发送一条release消息,即调用对象的release方法。
当一个对象因其引用计数器值为0时,将被系统销毁,从而系统自动给该对象发送一条dealloc消息。所以用户可以重载对象的dealloc方法,dealloc方法相当是C++的虚构函数,可以在该函数中释放申请的内存空间。 表 11 NSObject类内存管理方法
如下所示是RetainTracker对象生命周期的引用计数器值:
1 int main (int argc, const char * argv[])
2 { 3 RetainTracker * rt = [[RetainTracker alloc] init]; 4 NSLog(@"alloc:%d",[rt retainCount]); 5 6 [rt retain]; 7 NSLog(@"retain:%d",[rt retainCount]); 8 9 [rt release]; 10 NSLog(@"release:%d",[rt retainCount]); 11 return (0); 12 } // main 1.1.2 对象所有权 对象所有权是指实体的一种职责,当某个实体"拥有一个对象"时,就意味着该实体要负责对其拥有的对象进行清理。实体可能拥有对象的情况有:
1.1.3 访问方法 将类中的成员指针设置为指向一个外部对象,需通过retain和release方法来操作引用计数值,如下有3种操作方式: 1) 简单赋值 简单赋值方式,如下所示:
1 -(void) setEngine:(Engine*)newEngine
2 { 3 engine = [newEngine retain]; 4 } 5 这种方式只增加引用计数值,而未减少计数值。导致当再次调用setEngine方法时,未减少原来成员指针的引用计数值。 2) 修复赋值 这种方式是对前一种方式的修复,即修复了未对原来成员变量的引用计数值进行减少操作,但仍存在问题,如下所示:
1 -(void) setEngine:(Engine*)newEngine
2 { 3 [engine release]; //先减少引用计数器值 4 engine = [newEngine retain]; //再增加引用计数器值 5 } 6 若newEngine和engine是同一个对象,并且引用计数值为1;那么当调用setEngine方法时,在调用release后,会销毁该对象,从而当接着调用retain后会报错。 3) 正确赋值 这种方式是正确的赋值方式,修复了前两种错误方式。即修复了未对原来成员指针的引用计数值操作,也修复了可能出现同一个指针的问题,如下所示:
1 -(void) setEngine:(Engine*)newEngine
2 { 3 [newEngine retain]; //先增加引用计数器值 4 [engine release]; //再减少引用计数器值 5 engine = newEngine; 6 } 7 这种方式保障了引用计数器值一定大于1,不会出现为0的情况。
1.2 自动释放池 内存管理是一个棘手的问题,如上述所示的setter方法的各种细微问题。所以Cocoa引入了自动释放池(autorelease pool)概念,这种方式是通过自动释放池来管理引用计数值的release操作。有两种方式创建自动释放池:
1.2.1 使用方式 若要使用自动释放池来管理对象的release操作,只要在自动释放池的生命周期内调用被管理对象的autorelease方法,即可将对象的引用计数值委托自动释放池实体来管理,当自动释放池实体结束时,将会调用池中对象的release方法,并且只调用一次。如下所示:
1 -(void) autorelease
2 { 3 RetainTracker * rt = [[RetainTracker alloc] init]; 4 [rt retain]; 5 [rt retain]; 6 NSLog(@"before:%d",[rt retainCount]); 7 8 @autoreleasepool { 9 [rt autorelease]; 10 } 11 12 NSLog(@"after:%d",[rt retainCount]); 13 } 14 2016-02-07 10:52:36.920 ObjectC[725:41864] before:3 15 2016-02-07 10:52:36.921 ObjectC[725:41864] after:2 16 Program ended with exit code: 0 1) 关键字方式 @autoreleasepool方式的生命周期是从左花括号"{"开始,直到右花括号"}"结束,即执行到了右花括号时,那么将调用自动释放池中对象的release方法,来减少相应的引用计数值。 2) 对象方式 NSAutoreleasePool对象的生命周期是从调用其new方法来创建对象开始,直到调用池对象的release方法后,由系统调用释放池对象的dealloc方法后结束,即自动释放池在dealloc"虚构函数"中调用被管理对象的release方法来减少相应引用计数值。 1.2.2 释放池结构 自动释放池以栈的形式实现:当创建了一个新的自动释放池时,该池就被添加到栈顶中。所以若某个对象调用autorelease方法时,该对象将被放入最顶端(栈顶)的自动释放池中,并且栈顶下的释放池仍未被销毁,即被添加到栈顶下面释放池的对象仍未被释放。 若需要由自动释放池来管理大量对象时,用户可以手动销毁释放池,然后再创建新的池对象,如下所示:
1 NSAutoreleasePool *pool;
2 Pool = [[NSAutoreleasePool alloc] init]; 3 int i; 4 for(i = 0; i<1000000;i++) 5 { 6 id object = [someArray objetcAtIndex: i]; 7 [id autorelease]; 8 NSString *desc = [object description]; 9 if(i%1000) 10 { 11 [pool release]; 12 pool = [[NSAutoreleasePool alloc] init]; 13 } 14 } 15 [pool release]; Cocoa也使用类似的方法来管理内存,当使用AppKit时,Cocoa会定期自动地为用户创建和销毁自动释放池。通常是在程序处理当前事件(如鼠标单击或鼠标按下)以后执行这些操作。 1.3 Cocoa内存管理规则 Cocoa有许多内存管理约定,它们简化了retain、release和autorelease的使用方法,这些规则有:
2 自动引用计数 自动引用计数为Automatic Reference Counting (ARC) ,是Objective-C提供了一种自动内存管理的功能。ARC不需要用户考虑retain和release操作,而是在编译期添加代码(retain和release等方法)来保障对象的生命周期,同时可为对象自动生成合适的dealloc方法。从而让用户专注那些感兴趣的代码。如下是引用计数器手动和自动的差异:
图 21 引用计数器的手动和自动实现的差异
自动的引用计数和手动引用计数方法是互斥的,即不能同时在应用程序中使用ARC技术和手动操作retain和release。如在图 22所示,若选择YES时(默认),则启动ARC功能;若NO,则关闭ARC功能。
图 22 ARC功能启动设置 2.1 强制规则 为了ARC能工作,强制规定了一些新规则,并且这些规则不能在其它编译器使用。如果用户违反了这些规则,那么将得到一个compile-time错误。这些规则为: 1) 不能显示调用dealloc方法,同时不能调用和重载retain、release、retainCount和autorelease方法。但可以重载dealloc方法,当然在dealloc方法中不能调用引用计数器管理方法,同时在dealloc方法中不能调用 [super dealloc],父类的dealloc方法由编译器自动添加。 2) 不可使用NSAllocateObject和NSDeallocateObject,但仍可使用alloc方法创建对象。 3) 不可使用NSAutoreleasePool对象,但可以使用@autoreleasepool代码块。 4) 属性名次不能以new开头,比如说@property NSString *newString是不允许的。 5) 属性不能只有一个read-only而没有其它内存管理特性。若没有启用ARC功能,可以使用@property (readonly) NSString *title语句,但如果启用来ARC功能,就必须指定由谁来管理内存。 6) 结构体(struct)和联合体(union)不能使用ROP作为成员,如struct {NSString *str}代码是不被允许.
2.2 变量修饰词 2.2.1 保留环 使用引用计数机制时,经常要注意的一个问题是"保留环"(retain cycle),即呈环状相互引用的多个对象。这将导致内存泄漏,因为循环中的对象其保留计数不会降为0。对于循环中的每个对象来说,至少还有另一个对象引用着它。 如图 23所示,A的引用计数为2,而B的引用计数为1。当A的拥有者"云"release A后,A和B的引用计数都为1,导致两者都无法被释放。
图 23 引用计数保留环
2.2.2 修饰词 目前Objective-C提供4种修饰词(qualifier)来修饰变量,具体语义为: 表 21 Objective-C修饰词
其使用形式为:
1 ClassName * qualifier variableName;
2 eg: 3 MyClass * __weak myWeakReference; 对于图 23所示的引用环可以采用弱引用解决,因为在指向的对象释放之后,这些弱引用就会被设置为nil。如图 24所示,带有__weak的引用环结构,当"云"向A发送release消息后,A的引用计数为0,从而释放A和B对象内存空间。
图 24 带有弱引用的环 2.3 Toll-Free Bridging管理 ARC仅支持可保留对象指针,而无法自动管理Core Foundation对象的空间,必须由用户手动调用CFRetain 和 CFRelease方法来管理Core Foundation对象。如果在Objective-C 和 Core Foundation-style 对象之间进行转换时,为了让ARC便于工作,那么需要告诉编译器哪个对象是指针的拥有者。为此Objective-C使用了桥接转换(bridged cast)技术。 1) __bridge类型操作符 这种操作符是从ROP类型转换为non-ROP类型,但只传递指针并不会传递它的所有权,即指针的所有权仍在转换之前的对象上。如下所示:
1 NSString *theString = @"hello world";
2 CFStringRef cfString = (__bridge CFStringRef)theString; 3 cfString接收了指令,但指针的所有权仍然由theString保留 2) __bridge_retained类型操作符 这种操作符将指针所有权从ROP上转移到non-ROP上。因为ARC只会注意到non-ROP,所以用户需要通过non-ROP手动释放其保留计数器的值。这个转换类型会给non-ROP对象的保留计数器加1,所以需要手动让它减1,这与标准的内存管理方式相同。如下所示:
1 NSString *theString = @"hello world";
2 CFStringRef cfString = (__bridge_retained CFStringRef)theString; 3 cfString对象拥有指针并且它的保留计数为1,需要使用CFRetain和CFRelease来管理它的内存 3) __bridge_transfer类型操作符 这种转换符与上一个相反,它将所有权从non-ROP上转移到ROP上,从而ARC拥有对象并能确保它会像其它ARC对象一样得到释放。
3 参考文献 [1] Advanced Memory Management programming guide [2] Transitioning to ARC Release Notes [3] Objective-C基础教程(第2版) [4] Effective Objective-C 2.0 |
请发表评论