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 会在类的实例不再被使用时,自动释放其占用的内存. 但在有些时候我们还是需要在 ...
随机推荐
- XMPP系列(六)---创建群组
最近公司项目需要,要做一个自己的IMSDK,顺便先把之前没有记录的群聊功能记录一下. 先上资料,查看XMPP群聊相关的资料,可以去这里看协议:XEP-0045 . 创建群组 XMPP 框架里有一个类X ...
- iOS应用程序工程文件以及启动流程
转载请标明出处: http://blog.csdn.net/xmxkf/article/details/51351188 本文出自:[openXu的博客] iOS程序启动流程 完整启动流程 UIApp ...
- 18 Loader 总结
1. Loader 装载器 Android3.0以后出来的 它可以使Activity和Fragment 异步加载数据 变得简单(Loader里封装了AsyncTask) Loader特点: 1,对每一 ...
- 【java多线程系列】java内存模型与指令重排序
在多线程编程中,需要处理两个最核心的问题,线程之间如何通信及线程之间如何同步,线程之间通信指的是线程之间通过何种机制交换信息,同步指的是如何控制不同线程之间操作发生的相对顺序.很多读者可能会说这还不简 ...
- Volley,小并发网络请求的好帮手
不得不说,当不了解一件事情的时候,就会像当然的认为,其很神秘.但是当真正的接触到了这些神秘的item,就不会有这种感觉了.作为一个android开发新手的我,刚接触到了Volley这个开源的网络请求框 ...
- 最简单的基于FFmpeg的libswscale的示例(YUV转RGB)
===================================================== 最简单的基于FFmpeg的libswscale的示例系列文章列表: 最简单的基于FFmpeg ...
- 下载android4.4.2源码全过程(附已下载的源码)
今天在下载andriod源码,特来与大家分享一下我的经验.当然,网上教下载源码的教程较多,本文主要针对在GFW下下载源码出现的各种问题的解决方法. 1.首先安装下载客户端git , curl. 命令如 ...
- 打Patch实践
一.找到相应PATCH 确认系统已安装模块版本. SELECTapp.application_short_name, app.application_name, pi.patch_level FR ...
- 深入浅出EF之ModelFirst和DBFirst
在上篇博文中,小编主要简单的介绍了一下EF的一些基础知识,其中,小编蜻蜓点水的提了一下ModelFirst和DBFirst,ModelFirst先设计实体,然后根据模型生成数据库,DBFirst根据数 ...
- UNIX环境高级编程——线程同步之互斥锁、读写锁和条件变量(小结)
一.使用互斥锁 1.初始化互斥量 pthread_mutex_t mutex =PTHREAD_MUTEX_INITIALIZER;//静态初始化互斥量 int pthread_mutex_init( ...