Objective-C:内存管理

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对象生命周期的引用计数器值:


int main (int argc, const char * argv[])

{

RetainTracker * rt = [[RetainTracker alloc] init];

NSLog(@"alloc:%d",[rt retainCount]);

?

[rt retain];

NSLog(@"retain:%d",[rt retainCount]);

?

[rt release];

NSLog(@"release:%d",[rt retainCount]);

return (0);

} // main

?

1.1.2 对象所有权

对象所有权是指实体的一种职责,当某个实体"拥有一个对象"时,就意味着该实体要负责对其拥有的对象进行清理。实体可能拥有对象的情况有:

  • 如果一个对象内由指向其他对象的实例变量,则称该对象拥有这些对象;
  • 如果一个函数创建了一个对象,则称该函数拥有这个对象。

?

1.1.3 访问方法

将类中的成员指针设置为指向一个外部对象,需通过retain和release方法来操作引用计数值,如下有3种操作方式:

  1. 简单赋值

    简单赋值方式,如下所示:


-(void) setEngine:(Engine*)newEngine

{

engine = [newEngine retain];

}


这种方式只增加引用计数值,而未减少计数值。导致当再次调用setEngine方法时,未减少原来成员指针的引用计数值。

?

  1. 修复赋值

这种方式是对前一种方式的修复,即修复了未对原来成员变量的引用计数值进行减少操作,但仍存在问题,如下所示:


-(void) setEngine:(Engine*)newEngine

{

[engine release]; //先减少引用计数器值

engine = [newEngine retain]; //再增加引用计数器值

}


若newEngine和engine是同一个对象,并且引用计数值为1;那么当调用setEngine方法时,在调用release后,会销毁该对象,从而当接着调用retain后会报错。

?

  1. 正确赋值

这种方式是正确的赋值方式,修复了前两种错误方式。即修复了未对原来成员指针的引用计数值操作,也修复了可能出现同一个指针的问题,如下所示:


-(void) setEngine:(Engine*)newEngine

{

[newEngine
retain]; //先增加引用计数器值

[engine
release]; //再减少引用计数器值

engine = newEngine;

}


这种方式保障了引用计数器值一定大于1,不会出现为0的情况。

?

1.2 自动释放池

内存管理是一个棘手的问题,如上述所示的setter方法的各种细微问题。所以Cocoa引入了自动释放池(autorelease pool)概念,这种方式是通过自动释放池来管理引用计数值的release操作。有两种方式创建自动释放池:

  • @autoreleasepool关键字
  • NSAutoreleasePool对象

1.2.1 使用方式

若要使用自动释放池来管理对象的release操作,只要在自动释放池的生命周期内调用被管理对象autorelease方法,即可将对象的引用计数值委托自动释放池实体来管理,当自动释放池实体结束时,将会调用池中对象release方法,并且只调用一次。如下所示:


-(void) autorelease

{

RetainTracker * rt = [[RetainTracker
alloc] init];

[rt retain];

[rt retain];

NSLog(@"before:%d",[rt retainCount]);

?

@autoreleasepool {

[rt autorelease];

}

?

NSLog(@"after:%d",[rt retainCount]);

}


2016-02-07 10:52:36.920 ObjectC[725:41864] before:3

2016-02-07 10:52:36.921 ObjectC[725:41864] after:2

Program ended with exit code: 0

?

  1. 关键字方式

@autoreleasepool方式的生命周期是从左花括号"{"开始,直到右花括号"}"结束,即执行到了右花括号时,那么将调用自动释放池中对象的release方法,来减少相应的引用计数值。

  1. 对象方式

NSAutoreleasePool对象的生命周期是从调用其new方法创建对象开始,直到调用池对象release方法后,由系统调用释放池对象的dealloc方法后结束,即自动释放池在dealloc"虚构函数"中调用被管理对象的release方法来减少相应引用计数值。

?

1.2.2 释放池结构

自动释放池以栈的形式实现:当创建了一个新的自动释放池时,该池就被添加到栈顶中。所以若某个对象调用autorelease方法时,该对象将被放入最顶端(栈顶)的自动释放池中,并且栈顶下的释放池仍未被销毁,即被添加到栈顶下面释放池的对象仍未被释放。

若需要由自动释放池来管理大量对象时,用户可以手动销毁释放池,然后再创建新的池对象,如下所示:


NSAutoreleasePool *pool;

Pool = [[NSAutoreleasePool alloc] init];

int i;

for(i = 0; i<1000000;i++)

{

id object = [someArray objetcAtIndex: i];

[id autorelease];

NSString *desc = [object description];

if(i%1000)

{

[pool release];

pool = [[NSAutoreleasePool alloc] init];

}

}

[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


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

?

????其使用形式为:


ClassName * qualifier variableName;


eg:

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类型,但只传递指针并不会传递它的所有权,即指针的所有权仍在转换之前的对象上。如下所示:


NSString *theString = @"hello world";

CFStringRef cfString = (__bridge CFStringRef)theString;


cfString接收了指令,但指针的所有权仍然由theString保留

?

  1. __bridge_retained类型操作符

????这种操作符将指针所有权从ROP转移到non-ROP上。因为ARC只会注意到non-ROP,所以用户需要通过non-ROP手动释放其保留计数器的值。这个转换类型会给non-ROP对象的保留计数器加1,所以需要手动让它减1,这与标准的内存管理方式相同。如下所示:


NSString *theString = @"hello world";

CFStringRef cfString = (__bridge_retained CFStringRef)theString;


cfString对象拥有指针并且它的保留计数为1,需要使用CFRetain和CFRelease来管理它的内存

?

  1. __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
  5. ?
时间: 2024-12-29 09:09:33

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

Objective C 内存管理[转]

1  配对原则 alloc – release new – release retain - release copy – release 2  new和alloc-init的区别 (1)区别只在于alloc分配内存的时候使用了zone. 这个zone是个什么呢? 它是给对象分配内存的时候,把关联的对象分配到一个相邻的内存区域内,以便于调用时消耗很少的代价,提升了程序处理速度. (2)为什么不推荐使用new 因为若用了new,则初始化方法只能是init.这样,假如你想调用initWithFram

objective C 内存管理及属性方法详解

oc为每个对象提供一个内部计数器,这个计数器跟踪对象的引用计数,当对象被创建或拷贝时,引用计数为1,每次保持对象时,调用retain接口,引用计数加1,如果不需要这个对象时调用release,引用计数减1,当对像的引用计数为0时,系统就会释放掉这块内存,释放对象调用dealloc 当对象包含其他对象时,就得在dealloc中自己释放他们 NSObject是IOS所有类的基类 有两个基本函数,alloc和dealloc alloc类似于C++的new,dealloc类似于delete 当对象的re

iOS核心语言Objective C语言 —— 内存管理

本分享是面向有意向从事iOS开发的伙伴以及苹果产品的发烧友们,或者已经从事了iOS的开发者,想进一步提升者.如果您对iOS开发有极高的兴趣,可以与我一起探讨iOS开发,一起学习,共同进步.如果您是零基础,建议您先翻阅我之前分享的iOS开发分分钟搞定C语言系列,然后在开始Objective C语言的学习,如果您遇到问题也可以与我探讨,另外将无偿分享自己整理出来的大概400G iOS学习视频及学习资料,都是干货哦!可以新浪微博私信?关注极客James,期待与您的共同学习和探讨!!由于时间有限,每天在

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

Objective -C Memory Management??内存管理??第一部分 Memory management is part of a more general problem in programming called resource management. 内存管理是资源管理的一部分. Every computer system has finite resources for your program to use. These include memory, open fi

Objective-C(内存管理)

引用计数器 每个OC对象都有一个占4个字节存储空间的引用计数器 当使用或创建一个对象时,新对象的引用计数器默认是1 retain:可以使引用计数器+1 release:可以是引用计数器-1 retainCount:获得当前的引用计数器的值 当对象被销毁时,会重写dealloc方法 -(void)dealloc { // 这句必须放在最后面 [super dealloc]; } 僵尸对象:所占内存已经被回收的对象,僵尸对象不能再使用 野指针:指向僵尸对象(不可用的内存)的指针 错误:EXC_BAD

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

今天简述一下简单的内存管理,在IOS5.0以后Apple增加了ARC机制(Automatic Reference Counting),给开发人员带来了不少的方便,但是为了能更好的理解IOS内存管理机制,还是需要对其比较了解. 1.在OC中,每个对象都有一个保留计数,创建时每个对象都有一个初始值为1的保留计数,释放时,保留计数都为0 2.创建自动释放的对象 要求以一个方法创建对象时,以自动释放的形式返回该对象是一个很好的编程实践 +(Car *)car { Car *myCar = [[Car a

iOSDay17之内存管理

1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄露:空间使用完之后没有及时释放 过度释放:对同一块存储空间释放多次,立刻crash 内存溢出:所有存储空间被占用 3> 管理内存的三种方式 垃圾回收机制:程序员只需要开辟存储空间,系统会自动回收内存.java采用的该机制 MRC:手动引用计数机制,由开发人员开辟空间,手动添加影响引用计数增加或减少的方法

Objective-C----MRC内存管理 、 自动释放池 、 面向对象三大特性及封装 、 继承 、 组合与聚合

1 MRC练习 1.1 问题 引用计数是Objective-C语言采用的一种内存管理技术,当一个对象被创建在堆上后,该对象的引用计数就自动设置为1,如果在其它对象中的对象成员需要持有这个对象时,则该对象的引用计数被加上1,此时如果该对象被释放,内存管理程序将首先把该对象的引用计数减1,然后判断该对象的引用计数是否为0,由于其它对象在持有该对象时将引用计数加了1,所以此时该对象的引用计数减1后不为0,则内存管理程序将不会释放该对象.直到持有该对象的其它对象也被释放时,该对象的引用计数再次减1,变为

&lt;Linux内核源码&gt;内存管理模型

题外语:本人对linux内核的了解尚浅,如果有差池欢迎指正,也欢迎提问交流! 首先要理解一下每一个进程是如何维护自己独立的寻址空间的,我的电脑里呢是8G内存空间.了解过的朋友应该都知道这是虚拟内存技术解决的这个问题,然而再linux中具体是怎样的模型解决的操作系统的这个设计需求的呢,让我们从linux源码的片段开始看吧!(以下内核源码均来自fedora21 64位系统的fc-3.19.3版本内核) <include/linux/mm_type.h>中对于物理页面的定义struct page,也

objective-c(内存管理)

本文主要记录objective-c 内存管理的知识点: 1.objective-c的对象都是分配内存在堆上,与C的mallock和C++的new类似,只有int等系统变量分配内存在栈上: 2.objective-c没有java这般复杂的垃圾回收机制,它用的是引用计数,可以理解为创建该对象后,指向该对象首地址的指针是否在其他地方被引用,若增加引用则引用数加一,反之减一,当引用数为零时系统清除该变量.内部应该是堆上分配的地址重新设置为有效,也就是说可以再次分配给其他对象,存储该对象首地址的指针清除,