1 传统内存管理

Objective-C对象的生命周期可以分为:创建、存在、消亡。

1.1 引用计数

类似Java,Objective-C采用引用计算(reference counting)技术来管理对象的生命周期。每个对象都定义有一个整数(称引用计数器)与之相关联,该数用以表示当前有多少个指针指向该对象

1.1.1 操作方法

当某段代码需要访问一个对象时,该代码就将对象的保留计数值加1;当结束访问时就减1;若引用计数器减到0时,该对象将被销毁。引用计数器的值由如下三种操作进行控制:

      1) 创建

当使用allocnew方法或者通过copy消息(接收到消息的对象会创建一个自身的副本)创建一个对象时,对象的保留计数器值就被初始化为1

      2) 增加

要增加对象的引用计数器值,可以给对象发送一条retain消息,即调用对象的retain方法。

      3) 减少

要减少对象的引用计数器值,可以给对象发送一条release消息,即调用对象的release方法。

       当一个对象因其引用计数器值为0时,将被系统销毁,从而系统自动给该对象发送一条dealloc消息。所以用户可以重载对象dealloc方法,dealloc方法相当是C++的虚构函数,可以在该函数中释放申请的内存空间。

表 11 NSObject类内存管理方法

方法

描述

- (instancetype)retain

将引用计数器的值加1,可由用户调用。

- (oneway void)release

将引用计数器的值减1,可由用户调用。

- (NSUInteger)retainCount

获取引用计数器的值,可由用户调用。

- (instancetype)autorelease

将对象添加到自动释放池中,可由用户调用。

- (struct _NSZone *)zone

复制方法。

如下所示是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操作。有两种方式创建自动释放池:

  • @autoreleasepool关键字
  • NSAutoreleasePool对象

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的使用方法,这些规则有:

  • 当使用newalloc或copy方法创建一个对象时,该对象的引用计数值将被初始化为1。当不再使用该对象时应该向该对象发送一条release或autorelease消息。这样,该对象将在其使用寿命结束时被销毁。
  • 当通过其他方法获得一个对象时,假设该对象的引用计数值为1,而且已经被设置为自动释放,那么用户不需要执行任何操作来确保该对象得到清理。如果打算在一段时间内拥有该对象,则需要保留它并确保在操作完成时释放它。

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}代码是不被允许.

ARC只对可保留的对象指针(ROPs)有效。可保留的对象指针主要有如下三种:

  • 代码块指针;
  • Objective-C对象指针;
  • 通过_attribute_((NSObject))类型定义的指针。

所有其它指针类型,比如char*和CF对象都不支持ARC特性,如果使用的指针不支持ARC,那么必须手动管理这些对象空间。

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修饰词

类型

语义

__strong

默认修饰词,当有一个strong指针指向某对象,那么该对象将一直保持"活跃"状态;

__weak

其不能让被引用对象一直保持"活跃"状态。当某个被引对象没有__strong指针指向它时,那么其它对象以__weak类型指向上述对象的指针将被自动置为nil。

__unsafe_unretained

声明一个弱应用,但是不会自动nil化,也就是说,如果所指向的内存区域被释放了,这个指针就是一个野指针了。

__autoreleasing

用来修饰一个函数的参数,这个参数会在函数返回的时候被自动释放。

其使用形式为:

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

Objective-C:内存管理的更多相关文章

  1. Objective C内存管理之理解autorelease------面试题

    Objective C内存管理之理解autorelease   Autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的A ...

  2. Objective C 内存管理[转]

    1  配对原则 alloc – release new – release retain - release copy – release 2  new和alloc-init的区别 (1)区别只在于a ...

  3. objective C 内存管理及属性方法具体解释

    oc为每一个对象提供一个内部计数器.这个计数器跟踪对象的引用计数,当对象被创建或拷贝时.引用计数为1.每次保持对象时,调用retain接口.引用计数加1.假设不需要这个对象时调用release,引用计 ...

  4. Objective -C Memory Management 内存管理 第一部分

    Objective -C Memory Management  内存管理  第一部分 Memory management is part of a more general problem in pr ...

  5. Objective-C(内存管理)

    引用计数器 每个OC对象都有一个占4个字节存储空间的引用计数器 当使用或创建一个对象时,新对象的引用计数器默认是1 retain:可以使引用计数器+1 release:可以是引用计数器-1 retai ...

  6. Objective C----手动管理内存和自动管理内存

    对象的引用计数(Reference Counting) 正常情况下,当一段代码需要访问某个对象时,该对象的引用的计数加1:当这段代码不再访问该对象时,该对象的引用计数减1,表示这段代码不再访问该对象: ...

  7. IOS学习笔记3—Objective C—简单的内存管理

    今天简述一下简单的内存管理,在IOS5.0以后Apple增加了ARC机制(Automatic Reference Counting),给开发人员带来了不少的方便,但是为了能更好的理解IOS内存管理机制 ...

  8. objective-c(内存管理)

    本文主要记录objective-c 内存管理的知识点: 1.objective-c的对象都是分配内存在堆上,与C的mallock和C++的new类似,只有int等系统变量分配内存在栈上: 2.obje ...

  9. iOS学习17之OC内存管理

    1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄 ...

  10. iOS - OC 内存管理

    1.OC 基本内存管理模型 1.1 自动垃圾收集 在 OC 2.0 中,有一种称为垃圾收集的内存管理形式.通过垃圾收集,系统能够自动监测对象是否拥有其他的对象,当程序执行需要空间的时候,不再被引用的对 ...

随机推荐

  1. php 文件下载 以及 file_exists找不到文件的解决方案

    链接:<a href="upload/file/download.php?filename=雨人工作室.doc" target="_blank" > ...

  2. 備份Sqlite DB到XML文件:

    转载请注明出处:http://blog.csdn.net/krislight 项目中遇到备份与还原App数据的需求,需要把DB数据备份到一个XML文件中,然后保存到SD卡上,还原的时候直接从XML文件 ...

  3. Excel列表部分列表隐藏与取消隐藏

    Excel列表部分列表隐藏与取消隐藏 2014-2-19 隐藏:选中需要隐藏的列(选中A.B.C....),右键单击所选部分,选择"隐藏"即可. 取消隐藏:从A选中至所见表格最后的 ...

  4. Java中int与Integer

    一般小写字母开头的是数据类型(如int double),大写字母开头的一般是封装为类(如Double),里面有很多方法,比如实行转换Integer.parseInt(arg0),可以把其他类型的数据转 ...

  5. XtraForm默认皮肤的显示

    1.新建一个XtraForm窗体 2.运行显示这个窗体,会发现没有任何变化,显示的还是winform的样式 3.在Program.cs文件的Main函数中添加 DevExpress.Skins.Ski ...

  6. wcf 远程终结点已终止该序列 可靠会话出错

    https://social.msdn.microsoft.com/Forums/office/zh-CN/9f0c76d2-85b0-4cd3-979d-ceda7947bcd1/-?forum=w ...

  7. Learning WCF Chapter2 WCF Contracts and Serialization

    So far I’ve talked about the standards behind it all,but in fact WCF hides most of this from the dev ...

  8. windows桌面添加右键环境

    1.组合键win + R,输入regedit,回车   打开注册表编辑器 2.找到目录中[HKEY_CLASSES_ROOT\Directory\Background\shell]对其右键,新建一个项 ...

  9. HDU 5961 传递 【图论+拓扑】 (2016年中国大学生程序设计竞赛(合肥))

    传递 Time Limit: 12000/6000 MS (Java/Others)    Memory Limit: 65536/65536 K (Java/Others)     Problem ...

  10. Robot Framework自动化测试环境准备(一)

    Robot framework是诺西(NSN)开源的一套自动化测试工具,在通信设备自动化测试中很实用,它基于Python开发,主要模拟NMS网管配置数据到网元NODE,并读取配置看配置是否生效. == ...