乖乖隆地洞,这篇文章内容可是不得了,内存管理哦!首先,这个要是搞不明白,你就等着进程莫名其妙的挂死,或是疯狂申请内存却不释放,结果被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(自动引用计数)的更多相关文章

  1. Swift ARC 自动引用计数

    1.ARC 引用类型在堆上的内存分配过程中有 8 字节的地址长度用来保存对象的引用计数,堆上的内存并不像栈上那样立即进行回收,系统会定时对堆上的内存进行检查,当某个实例不再被使用时,引用计数会变为 0 ...

  2. ARC————自动引用计数

    一.内存管理/引用计数 1.引用计数式内存管理的方式(下面四种) 对象操作 OC方法 生成并持有对象 alloc/new/copy/mutableCopyd等方法 持有对象 retain方法 释放对象 ...

  3. OC - ARC(自动引用计数)

    1.什么是自动引用计数? 顾明思义,自动引用计数(ARC,Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术. 在OC中采用ARC机制,让编译器来进行内存 ...

  4. OC基础15:内存管理和自动引用计数

    "OC基础"这个分类的文章是我在自学Stephen G.Kochan的<Objective-C程序设计第6版>过程中的笔记. 1.什么是ARC? (1).ARC全名为A ...

  5. swift:自动引用计数ARC

    Swift自动引用计数:ARC    原文链接:https://numbbbbb.gitbooks.io/-the-swift-programming-language-/content/chapte ...

  6. Swift基础语法-内存管理, 自动引用计数

    1. 工作机制 Swift和OC一样,采用自动引用计数来管理内存 当有一个强引用指向某一个对象时,该对象的引用计数会自动+1 当该强引用消失时,引用计数会自动-1 当引用计数为0时,该对象会被销毁 2 ...

  7. 自动引用计数(ARC)

    1.1什么是自动引用技术 顾名思义,自动引用计数(ARC, Automatic Reference Counting)是指内存管理中对引用采取自动计数的技术.以下摘自苹果官方说明: 在Objectiv ...

  8. Swift5 语言指南(二十五) 自动引用计数(ARC)

    Swift使用自动引用计数(ARC)来跟踪和管理应用程序的内存使用情况.在大多数情况下,这意味着内存管理在Swift中“正常工作”,您不需要自己考虑内存管理.当不再需要这些实例时,ARC会自动释放类实 ...

  9. Swift 自动引用计数(ARC)

    Swift 使用自动引用计数(ARC)这一机制来跟踪和管理应用程序的内存 通常情况下我们不需要去手动释放内存,因为 ARC 会在类的实例不再被使用时,自动释放其占用的内存. 但在有些时候我们还是需要在 ...

随机推荐

  1. How to generate the complex data regularly to Ministry of Transport of P.R.C by DB Query Analyzer

    How to generate the complex data regularly to Ministry of Transport of P.R.C by DB Query Analyzer 1 ...

  2. MTK8127源码编译出现的错误及相关解决办法

    /** * date:2016/8/17 * author: Y.X .YANG */ 按照开发文档提示: 1.MTK提供的开发包目录下有若干个.aa .ab .ac ...的分压缩包.此时应当将这些 ...

  3. Python 键盘鼠标监听

    异想天开的想记录一下自己每天的键盘键位走向,于是就在网上搜索了一下相关的实现,然后就发现了一个第三方的库pyHook.封装的很好,我们只需要傻瓜式的调用里面的API就可以了. 下面是我在使用pyHoo ...

  4. Python装饰器模式学习总结

    装饰器模式,重点在于装饰.装饰的核心仍旧是被装饰对象. 类比于Java编程的时候的包装模式,是同样的道理.虽然概念上稍有不同但是原理上还是比较相近的.下面我就来谈一谈我对Python的装饰器的学习的一 ...

  5. GDAL1.11版本对SHP文件索引加速测试

    GDAL库中对于矢量数据的读取中可以设置一些过滤器来对矢量图形进行筛选,对于Shapefile格式来说,如果数据量太大,设置这个过滤器时间慢的简直无法忍受.好在GDAL1.10版本开始支持读取Shap ...

  6. 为何写flash的时候要地址左移一位?

    代码一: #define Writeflash(addr,dat) *((volatile INT16U *)(addr<<1))=(INT16U)dat #define Readflas ...

  7. Java进阶(三十二) HttpClient使用详解

    Java进阶(三十二) HttpClient使用详解 Http协议的重要性相信不用我多说了,HttpClient相比传统JDK自带的URLConnection,增加了易用性和灵活性(具体区别,日后我们 ...

  8. 关于Class文件

    什么是Class文件 Java人对class文件肯定很熟悉了,它是Java源码编译后的产物.JVM运行时负责加载class文件,并根据class定义的执行逻辑运行.java为了将硬件底层的差异屏蔽掉, ...

  9. Java进阶(二十八)SimpleDateFormat格式化日期问题

    SimpleDateFormat格式化日期问题 前言 发现一个问题,经过以下语句处理后,发现12:27:45转换后成为了00:27:45. DateFormat df = null; if(DATE1 ...

  10. 设计模式之——工厂模式(A)

    本文是在学习中的总结,欢迎转载但请注明出处:http://blog.csdn.net/pistolove/article/details/41085085 昨天看完了工厂模式,觉得在开发的过程中好多地 ...