obj-c编程11:内存管理和ARC(自动引用计数)
乖乖隆地洞,这篇文章内容可是不得了,内存管理哦!首先,这个要是搞不明白,你就等着进程莫名其妙的挂死,或是疯狂申请内存却不释放,结果被OS杀死,不管是“自杀”还是“他杀”,都不是那么好玩的哦。其次要记住这可不是windows 中的内存管理(Win32 api),也不是linux中C like的内存管理方法。这个比他们都“高级”的多啊!但是没有ruby的高级,也没有ruby的简单,如果mac编程用ruby的就好了,这不搞出一个雨燕(SWFIT)来啊!
在Xcode4.2发布前,内存管理的确是令人恐怖的主题,都把细节推给我们码农了啊。随着4.2的发布,加入了新的自动应用计数特性(Automatic Reference Counting,ARC),码农门不再脱发着思考内存管理问题鸟。
obj-c基本内存管理模型有以下3种:
1 自动垃圾收集
2 收工引用计数和自动释放池
3 自动引用计数(ARC)
我们依次来看一下吧。
【1】自动垃圾收集
存在于obj-c 2.0中,iOS里不支持垃圾收集(不知现在是否如此哦),仅os x支持。当进程运行到某个低内存的临界点时,自动开始清理垃圾,这是一个计算密集过程,可能导致进程挂起,所以不推荐使用该特性鸟。
【2】手动管理内存计数
一般的当对象创建时,初始引用计数为1,以后每当引用一次该对象需要为该对象的引用计数加1,可以给该对象发送retain消息:
[myobj retain];
当不在需要该对象时,给其发送release消息使其引用计数减1:
[myobj release];
当对象引用计数为0时,理论上该对象不会再被使用,因为没有东西引用到它,他彻底沦为“宅对象”喽。所以可以释放其内存,通过给该对象发送dealloc消息完成这个操作。大多数情况下对象会继承NSObject的dealloc方法,当然如果类覆写了dealloc方法完成自己的析构,则当仁不让的会调用新的方法。
在手工管理引用计数时,要注意F库中一些方法会隐式的增加对象的引用计数,同样一些方法会隐式减少引用计数。
引用计数为0的引用称为悬挂指针(dangling pointer)的引用。如果给已释放的对象发送release消息,则会引起过度释放对象,会导致程序崩溃。
2.1自动释放池:设想下这种情况:方法返回一个在其中alloc的对象。这时虽然该方法不再使用这个对象,但不能释放它,因为该对象会作为该方法的返回值。NSAutoreleasePool类创建的目的就是希望能解决这个问题。通过给自动释放池发送drain消息,自动释放池中的对象会被清理和释放。
通过给对象发送autorelease消息,可以将其手动添加到自动释放池维护的对象列表中:[my_obj autorelease];
并不是所有新创建的对象都会被自动添加到自动释放池中,任何以alloc,copy,mutableCopy和new为前缀的方法创建的对象都不会被自动添加到池中。在这种情况下,我们说你拥有这个对象。当你拥有一个对象时,你必须自己负责这个对象,so你需要在使用完后自己释放这些对象的内存:主动给对象发送release消息或发送autorelease将其加入到自动释放池中,下面上代码:
#import <Foundation/Foundation.h> int main(int argc,char *argv[]) { NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; NSString *str0 = [[NSString alloc] init]; //alloc方法不会自动加到池中,所以要手动释放 [str0 release]; [pool drain]; return 0; }
此代码表明在代码开始位置创建自动释放池,在进程返回前,代码结束位置清理自动释放池。值得注意的是以上代码编译时不可以加 -fobjc-arc选项,也就是不能与ARC机制同时使用哦,否则编译会出错的:
apple@kissAir: objc_src$clang -fobjc-arc -framework Foundation 5.m -o 5
5.m:5:2: error: 'NSAutoreleasePool' is unavailable: not available in automatic
reference counting mode
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
^
/System/Library/Frameworks/Foundation.framework/Headers/NSAutoreleasePool.h:8:12:note:
declaration has been explicitly marked unavailable here
@interface NSAutoreleasePool : NSObject {
^
在遇到将一个对象作为方法的返回时,我们可以用明确的代码来表示将其放入自动释放池中,确保当起引用计数为0时且pool收到drain消息时能够自动释放:
//in some method:
Some_class *ret = [[Some_class alloc] init] autorelease];
或者在实际返回时再加入:
return [ret autorelease];
2.2 事件循环和内存分配:Cocoa和ios进程运行在事件循环中,类似于windows的事件循环,比如按下一个按钮事件。每当一个新事件发生时,系统会创建一个新的自动释放池,然后可能会调用你代码中的一些方法来处理该事件。当处理完事件,并从你的方法返回后,系统在等待下一个事件发生的间隙,会清理自动释放池哦。这意味着除非对象使用retain否则无法在清空自动释放池的过程中存活下来啦。下面看一个例子:
#import <UIKit/UIKit.h> @interface myView:UIView @property (nonatomic,retain)NSMutableArray *data; @end
假设在实现内部已经用synthesize对data属性进行了同步,再假定viewDidLoad方法会在视图载入内存是被系统调用,则在该方法中有:
data = [NSMutableArray array];
可是这样有个问题,就是前面提到的F库得方法会默认创建自动释放的对象,array创建的是一个自动释放的数组,该数组会在当前事件结束后被立即释放。为了保证数组在事件循环中还能存在,可以使用下面3中方法中的任何一种:
1. data = [[NSMutableArray array] retain]; //用retain后引用不会为0,所以不会被释放掉哦
2. data = [[NSMutableArray alloc] init]; //前面说过的alloc开头的方法不会加入自动释放池中哦
3. self.data = [NSMutableArray array];
注意最后一种方法,没有直接使用实例变量data,而是通过data属性赋值的,而data属性前面使用了retain特性,所以自动释放的数组会被保持。但是不管用3种方法的哪一种,都需要覆盖dealloc方法用于在实际销毁myView对象时释放数组哦:
-(void)dealloc{ [data release]; [super dealloc]; }
还是要记得调用父类中的dealloc啊!在手工引用计数环境中,可以为属性添加atomic(默认)或nonatomic特性,也可以添加assign(默认),retain和copy特性。当使用设置方法为属性赋值时我们来看看assign,retain和copy 3种特性实现上的不同:
self.property = new_value; //assign way: property = new_value; //retain way: if(property != new_value){ [property release]; property = [new_value retain]; } //copy way: if(property != new_value){ [property release]; property = [new_value copy]; }
最后对手工内存管理(即不使用垃圾回收或ARC特性)规则做一个总结:
1 如果需要保持一个对象不被销毁,可以使用retain,但在使用完对象后需要手动release进行释放;
2 给对象发送release不一定会销毁该对象,除非该对象的引用计数等于0,这时系统会发生dealloc消息给该对象;
3 对于使用了retain,copy,mutableCopy,alloc或new方法的任何对象,以及具有retain和copy特性的属性进行释放时,需要覆写dealloc方法;
4 如果在方法中返回一个对象(该方法不需要该对象),则可以给该对象发送autorelease消息标记这个对象延迟释放,autorelease消息不会影响到对象的引用计数;
5 当进程终止时,该进程内存中的所有对象都会被释放(貌似是废话);
6 当开发cocoa或iOS程序时,自动释放池会随着每次事件发送而创建和清空,在此情况下,如果要使自动释放池被清空后自动释放的对象还能够存在,对象需要使用retain方法。只要对象的引用计数大于发送autorelease消息的数量,就能够在池清理后生存下来。
【3】ARC
自动引用计数ARC可以避免收工引用计数的一些潜在陷阱,但原来的引用计数仍然被维护和跟踪。然而系统会检测出何时需要保持对象,合适需要释放对象,这些你都不用担心鸟。你也不必担心返回了方法内创建的对象,编译器会管理好对戏的内存,编译器会通过生成正确的代码去自动释放或保持返回的对象(对于其他对象也是类似)。我们首先要引出强变量和弱变量2个概念:
强变量:通常在ARC中所有指针变量都是强变量。将对象引用obj_new赋值给obj_old会使obj_new对象自动保持,同时旧对象obj_old会在被赋值千被释放。强变量默认会被初始化为0,无论他是实例变量、局部变量还是全局变量这都成立。我们看以下代码:
Some_class *obj0 = [[Some_class alloc] init]; Some_class *obj1 = [[Some_class alloc] init]; obj0 = obj1;
当手工管理内存时上述代码会导致obj0对象的引用丢失,随后他的值被覆盖,从而产生内存泄露,即一个变量不再被引用,但又不能够释放。如果使用ARC,obj0和obj1都是强变量,前面赋值其实会是这样:
[obj1 retain]; [obj0 release]; obj0 = obj1;
但你不会在代码中实际看到上述代码,编译器在后台帮你做了这些,你只要写赋值语句就行啦。
因为所有对象默认都是强变量,所以不需要先声明,但你仍然可以使用关键字__strong:
__strong Some_class *obj;
但是默认属性不是strong,而是unsafe_unretained(相当于assign),所以如果有必要你需要这样为属性声明strong特性:
@property(strong,nonatomic) Some_class *obj;
编译器会保证事件循环中通过强属性对赋值执行保持操作,从而属性对象可以存活下来。带有unsafe_unretained(相当于assign)或weak的属性不会执行这些操作。
弱变量:在一些情况下,2个对象都持有彼此的强引用时,会产生循环保持(retain cycle),这样2个对象都不可以被销毁,即使其中一个已经不再被使用了。解决这个问题可以通过在2个对象间建立弱引用的方法解决。我们可以在父对象到子对象间使用强引用,而子对象到父对象间使用弱引用,这样就没有循环保持,弱变量也不能阻止引用的对象被销毁,在这里即是子对象无法阻止父对象被销毁。此时子对象会被系统自动设置为nil,这也避免了无意给它发送消息引起崩溃的问题。因为给nil对象发送消息不会有反应哦。
可以使用__weak关键字声明一个弱变量:
__weak Some_class *obj;
或者为属性指定weak特性:
@property(weak,nonatomic) Some_class *obj;
需要注意的是,在ios4和os x 10.6中不支持弱变量,此时你仍然可以为属性使用unsafe_unretained(或assign)特性,或者将变量声明为__unsafe_unretained,然而这时当引用的对象被销毁时,变量不再被清零喽(即被置为nil)。
细心地看客或许已经注意到,本系列到目前为止几乎所有代码都会在main中有@autoreleasepool指令,该指令围住的语句块定义了自动释放池的上下文,在自动释放池块结束的时候,任何在这个上下文中创建的对象都会被自动销毁。(除非编译器在自动释放块结束后还需要保证这个对象存在)我们可以在产生大量临时对象的地方使用这一机制:
for(i = 0;i < n;++i){ @autoreleasepool{ //处理大量临时变量 } }
在本文开始处提到,cocoa和ios应用运行在事件循环中,为了处理新的事件,系统会创建一个新的自动释放池上下文,在事件结束的时候,自动释放池上下文已经结束,意味着自动释放对象可能被销毁。使用ARC,这些都会在“底层”发生,你无需为此担心。
ARC很好,但其与ARC之前的代码兼容性如何呢?我们说只要非ARC代码与标准的cocoa命名规则一致,都会运行良好。当ARC遇到方法调用时会检查方法名,如果名字以alloc、new、copy、mutableCopy或init开头时,它会假定这些方法返回对象的所有者给方法的调用者。(即意思为这些返回的对象需要自己手动释放?ARC不会自动释放这些对象?)除非你使用的方法不符合标准的命名规则,此时,需要使用隐性通知编译器该方法会返回对象的拥有者(以便让编译器不会自动释放返回的方法?)。最后要注意的的是,如果你试图合成属性,而属性的名字是以上面提到的特殊词开头的话,编译器会提示一些错误哦。
obj-c编程11:内存管理和ARC(自动引用计数)的更多相关文章
- Swift ARC 自动引用计数
1.ARC 引用类型在堆上的内存分配过程中有 8 字节的地址长度用来保存对象的引用计数,堆上的内存并不像栈上那样立即进行回收,系统会定时对堆上的内存进行检查,当某个实例不再被使用时,引用计数会变为 0 ...
- ARC————自动引用计数
一.内存管理/引用计数 1.引用计数式内存管理的方式(下面四种) 对象操作 OC方法 生成并持有对象 alloc/new/copy/mutableCopyd等方法 持有对象 retain方法 释放对象 ...
- OC - ARC(自动引用计数)
1.什么是自动引用计数? 顾明思义,自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术. 在OC中采用ARC机制,让编译器来进行内存 ...
- OC基础15:内存管理和自动引用计数
"OC基础"这个分类的文章是我在自学Stephen G.Kochan的<Objective-C程序设计第6版>过程中的笔记. 1.什么是ARC? (1).ARC全名为A ...
- swift:自动引用计数ARC
Swift自动引用计数:ARC 原文链接:https://numbbbbb.gitbooks.io/-the-swift-programming-language-/content/chapte ...
- Swift基础语法-内存管理, 自动引用计数
1. 工作机制 Swift和OC一样,采用自动引用计数来管理内存 当有一个强引用指向某一个对象时,该对象的引用计数会自动+1 当该强引用消失时,引用计数会自动-1 当引用计数为0时,该对象会被销毁 2 ...
- 自动引用计数(ARC)
1.1什么是自动引用技术 顾名思义,自动引用计数(ARC, Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术.以下摘自苹果官方说明: 在Objectiv ...
- Swift5 语言指南(二十五) 自动引用计数(ARC)
Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况.在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理.当不再需要这些实例时,ARC会自动释放类实 ...
- Swift 自动引用计数(ARC)
Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存 通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存. 但在有些时候我们还是需要在 ...
随机推荐
- 【移动开发】Service类onStartCommand()返回值和参数
Android开发的过程中,每次调用startService(Intent)的时候,都会调用该Service对象的onStartCommand(Intent,int,int)方法,然后在onStart ...
- 查看linux的进程到底用了多少内存
1. 在linux下,查看一个运行中的程序, 占用了多少内存, 一般的命令有 (1). ps aux: 其中 VSZ(或VSS)列 表示,程序占用了多少虚拟内存. ...
- Java Math的 floor,round和ceil
floor 返回不大于的最大整数 round 则是4舍5入的计算,入的时候是到大于它的整数 round方法,它表示"四舍五入",算法为Math.floor(x+0.5),即将原来的 ...
- Android四大组件之一Service介绍-android学习之旅(十二)
基本概念: service是android四大组件之一,运行在后台执行耗时操作,并不提供用户界面.其他组件如acticity可以通过startService启动该组件,也可以通过bindService ...
- 给EditText的drawableRight属性的图片设置点击事件
这个方法是通用的,不仅仅适用于EditText,也适用于TextView.AutoCompleteTextView等控件. Google官方API并没有给出一个直接的方法用来设置右边图片的点击事件,所 ...
- 关于bootstrap-fileinput
最近搞了一个很简单的项目,里面需要文件上传.当然文件上传也是很简单的,不过做出成品之后发现,卧槽,火狐和谷歌两个浏览器显示的内容不一致. 如下图,左边的是谷歌显示,右边是火狐显示. 其实,作为一个后台 ...
- const引用
在C++中可以声明const引用 const Type& name = var: const引用让变量拥有只读属性 const int &a = b const int &a ...
- 1052. Linked List Sorting (25)
题目如下: A linked list consists of a series of structures, which are not necessarily adjacent in memory ...
- HttpClient 解决中文乱码
public static String httpGet(String url) { try { HttpGet httpGet = new HttpGet(url); HttpClient clie ...
- 12.2、Libgdx的图像之清屏
(官网:www.libgdx.cn) 在Libgdx中的清屏操作不同于普通的OpenGL应用.唯一的不同是访问OpenGL context. 代码如下: @Override public void r ...