IOS 内存管理

一、前言

对于大多数从C++或者JAVA转过来学习Object-C(以下简称OC)的人来说,OC这门语言看起来非常奇怪,用起来也有点麻烦。

OC没有像JAVA一样的垃圾回收机制,也就是说,OC编程需要程序员手动去管理内存。这就是为什么它烦的原因,苹果却一直推崇开发者在有限硬件资源内写出最优化的代码,使用CPU最少,占用内存最小。

二、基本原理

对象的创建:

OC在创建对象时,不会直接返回该对象,而是返回一个指向对象的指针,因此出来基本类型以外,我们在OC中基本上都在使用指针。

ClassA  *a = [[ClassA  
alloc]  init];

在[ClassA  
alloc]的时候,已经发送消息通知系统给ClassA的对象分配内存空间,并且返回了指向未初始化的对象的一个指针。

未初始化的ClassA对象接手到init消息,init返回指向已初始化后的ClassA对象的一个指针,然后将其赋值给变量a。

在创建并使用完一个对象的时候,用户需要手动地去释放该对象。

[a   dealloc];

如果指针a和b同时指向堆中同一块内存地址

ClassA  *a = [[ClassA  
alloc]  init];

ClassA  *b = a;

[a   dealloc];

当执行到第三行的时候,指针b就成了无头指针。这是一个在C++中也是常见的错误,我们需要避免这类错误,因为无头指针是危险的。

引用计数:

OC在内存管理上采用了引用计数(retain
count),在对象内部保存一个数字,用来表示被引用的次数。init、new和copy都会让retain
count加1。当销毁对象的时候,系统不会直接调用dealloc方法,而是先调用release,让retain count 减1,当retain
count等于0的时候,系统才会调用dealloc方法来销毁对象。

在指针赋值的时候,retain count
是不会自动增加的,为了避免上面所说的错误,我们需要在赋值的时候手动retain一次,让retain count 增加1。

ClassA  *a = [[ClassA  
alloc]  init];  // retain count = 1

ClassA  *b = a;

[b   retain];  // retain count
= 2

[a   dealloc];

这样在执行到第四行的时候,对象的retain count只是减了1,并没有被销毁,指针b仍然有效。

内存泄露:

就如上面列子所示,当生成ClassA对象时,指针a拥有对该对象的访问权。如果失去了对一个对象的访问权,而又没有将retain
count减至0,就会造成内存泄露。也就是说,分配出去的内存无法回收。

ClassA  *a = [[ClassA  
alloc]  init];

a  =  nil;

三、Autorelease Pool

为了方便程序员管理内存,苹果在OC中引入了自动释放池(Autorelease
Pool)。在遵守一些规则的情况下,可以自动释放对象。但即使有这么一个工具,OC的内存仍需要程序员时刻关注(这个自动释放池跟JAVA的垃圾回收机制不是一回事,或者说,骑马都追不上JAVA的机制,可能连尘都吃不到)。

ClassA  *a = [[[ClassA  
alloc]  init]  autorelease];

//retain count = 1,但无需release

Autorelease Pool 的原理:

autorelease pool
全名叫做NSAutoreleasePool,是OC中的一个类。autorelease pool并不是天生就有的,你需要手动的去创建它

NSAutoreleasePool  *pool =
[[NSAutoreleasePool  alloc]  init];

一般地,在新建一个iphone 项目的时候,xcode会自动地为你创建一个autorelease pool,这个pool就写在Main函数里面。

在NSAutoreleasePool中包含了一个
可变数组,用来存储被声明为autorelease的对象。当NSAutoreleasePool自身被销毁的时候,它会遍历这个数组,release数组中的每一个成员(注意,这里只是release,并没有直接销毁对象)。若成员的retain
count 大于1,那么对象没有被销毁,造成内存泄露。

默认的NSAutoreleasePool
只有一个,你可以在你的程序中创建NSAutoreleasePool,被标记为autorelease的对象会跟最近的NSAutoreleasePool
匹配。     NSAutoreleasePool  *pool =
[[NSAutoreleasePool  alloc]  init];

//Create some objects

//do something…

[pool  release];

你也可以嵌套使用NSAutoreleasePool  ,就像你嵌套使用for一样。

即使NSAutoreleasePool 
看起来没有手动release那么繁琐,但是使用NSAutoreleasePool 
来管理内存的方法还是不推荐的。因为在一个NSAutoreleasePool 
里面,如果有大量对象被标记为autorelease,在程序运行的时候,内存会剧增,直到NSAutoreleasePool  
被销毁的时候才会释放。如果其中的对象足够的多,在运行过程中你可能会收到系统的低内存警告,或者直接crash。

Autorelease Pool   扩展:

如果你极具好奇心,把Main函数中的NSAutoreleasePool 
代码删除掉,然后再自己的代码中把对象声明为autorelease,你会
发现系统并不会给你发出错误信息或者警告。用内存检测工具去检测内存的话,你可能会惊奇的发现你的对象仍然被销毁了。

其实在新生成一个Run
Loop的时候,系统会自动的创建一个NSAutoreleasePool  ,这个NSAutoreleasePool 无法被删除。

在做内存测试的时候,请不要用NSString。OC对字符串作了特殊处理

NSString  *str  =[ [NSString
alloc]  stringWithString:@”123”];

在输出str的retain count 的时候,你会发现retain count 大于1。

四、手动管理内存

使用alloc、new、copy创建一个对象,该对象的retain count
都等于1,需要用release来释放该对象。谁创建,谁去释放。在这3钟方法以外的方法创建的对象,都被系统默认的声明为autorelease。

ClassA  *a = [[ClassA  
alloc]  init];

ClassA  *b = a;

[b   retain];

//do smoething

[b release];

b  =  nil;

把一个指针赋值给另外一个指针的时候,a 指针所指向的对象的引用次数并没有增加,也就是说,对象的retain
count依然等于1。只有在retain了之后,retain count 才会加1。那么,如果这时候执行[a 
release],只是a指针放弃了对对象的访问权,对象的retain count
减1,对象没有被销毁。只有当b也执行了release方法之后,才会将对象销毁掉。因此,谁retain了,谁就要release。

在对象被销毁之后,指针依然是存在的。所以在release了之后,最好把指针赋为空,防止无头指针的出现。顺便一说,release一个空指针是合法的,但是不会发生任何事情。

如果你在一个函数中创建并返回一个对象,那么你需要把这个对象声明为autorelease

(ClassA  *)Function()

{

ClassA *a =
[[[ClassA   alloc]  init]  autorelease];

return a;

}

不这样做的话,会造成内存泄露。

五、属性与内存管理

苹果一直没有强调的一点是,关于属性中的retain。事实上,属性中带有retain的,在赋值的时候可能已经在合成的setter中retain了一次,因此,这里也需要release。

@property实际上是getter和setter,@synthesize是合成这2个方法。为什么在声明了属性之后可以用“.”来直接调用成员变量呢?那是因为声明属性以后系统根据你给的属性合成了一个set方法和一个get方法。使用“.”与属性并没有直接关联,如果你不嫌麻烦,在你的程序里面多写一个set和get方法,你也可以使用“.”来调用变量。

@property(),如果你里面什么都不写,那么系统会默认的把你的属性设置为:

@property(atomic, assign)…..

关于nonatomic:

这个属性没有对应的atomic关键字,即使我上面是这么写,但atomic只是在你没有声明这个特性的时候系统默认,你无法主动去声明这一特性。

如果你的程序只有一个主线程,或者你确定你的程序不会在2个或者以上线程运作的时候访问同一个变量,那么你可以声明为nonatomic。指定nonatomic特性,编译器合成访问器的时候不会去考虑线程安全问题。如果你的多个线程在同一时间会访问到这个变量的话,可以将特性声明为atomic(通过省略关键字nonatomic)。在这种特性的状态下,编辑器在合成访问器的时候就会在访问器里面加一个锁(@synchronized),在同一时间只能有一个线程访问该变量。

但是使用锁是需要付出代价的,一个声明为atomic的属性,在设置和获取这个变量的时候都要比声明为nonatomic的慢。所以如果你不打算编写多线程代码,最好把变量的属性特性声明为nonatomic。

关于assign、retain和copy:

assign是系统默认的属性特性,它几乎适用于OC的所有变量类型。对于非对象类型的变量,assign是唯一可选的
特性。但是如果你在引用计数下给一个对象类型的变量声明为assign,那么你会在编译的时候收到一条来自编译器的警告。因为assign对于在引用计数下的对象特性,只创建了一个弱引用(也就是平时说的浅复制)。这样使用变量会很危险。当你release了前一个对象的时候,被赋值的对象指针就成了无头指针了。因此在为对象类型的变量声明属性的时候,尽量少(或者不要)使用assign。

关于assign合成的setter,看起来是这样的:

-(void)setObjA:(ClassA  *)a

{

objA 
=  a;

}

在深入retain之前,先把声明为retain特性的setter写出来:

-(void)setObjA:(ClassA  *)a

{

If(objA !=
a)

{

[objA  release];

objA  =  a;

[objA  retain];  //对象的retain count 加1

}

}

明显的,在retain的setter中,变量retain了一次,那么,即使你在程序中

self.objA  =  a;

只写了这么一句,objA仍然需要release,才能保证对象的retain count 是正确的。但是如果你的代码

objA  =  a;

只写了这么一句,那么这里只是进行了一次浅复制,对象的retain count 并没有增加,因此这样写的话,你不需要在后面release objA。

这2句话的区别是,第一句使用了编译器生成的setter来设置objA的值,而第二句只是一个简单的指针赋值。

copy的setter看起来是这样的:

-(void)setObjA:(ClassA  *)a

{

ClassA  * temp  =  objA;

objA  =  [a   copyWithZone:nil];

[temp  release];

}

复制必须通过实现copyWithZone:这个方法,因次copy这个特性只适用于拥有这个方法的类型,也就是说,必须这个类支持复制。复制是把原来的对象release掉,然后让指针指向一个新的对象的副本。因此即使在setter里面release了原来的对象,你仍然需要在后面release新指向的对象(副本)。

六、尾声

IOS开发现在唯一能用的内存管理方式就是引用计数,无论你喜欢还是不喜欢。在一个内存紧缺的机器上,你编写程序的时候也只能步步为营,尽可能的让你的程序腾出内存空间,并保证系统不会给你一个警告。即使苹果在Mac
OS X 雪豹(v10.5)系统里面添加了另外一种内存管理方式(垃圾收集),但目前不适用于IOS。

IOS 内存管理,布布扣,bubuko.com

时间: 2024-10-15 04:30:45

IOS 内存管理的相关文章

IOS内存管理

原文链接:http://blog.csdn.net/weiqubo/article/details/7376189 1.  内总管理原则(引用计数)    IOS的对象都继承于NSObject,   该对象有一个方法:retainCount ,内存引用计数. 引用计数在很多技术都用到: window下的COM组件,多线程的信号量,读写锁,思想都一样.       (一般情况下: 后面会讨论例外情况)    alloc      对象分配后引用计数为1    retain    对象的引用计数+1

iOS内存管理(一)

最近有时间,正好把iOS相关的基础知识好好的梳理了一下,记录一下内存相关方面的知识. 在理解内存管理之前我觉得先对堆区和栈区有一定的了解是非常有必要的. 栈区:就是由编译器自动管理内存分配,释放过程的区域,存放函数的参数值,局部变量等.栈是内存中一块连续的区域,它的大小是确定的. 堆区:需要我们来动态的分配,释放,也就是我们内存管理的主角. 我们通过一个简单的例子来看看. NSString *string = [NSString alloc] init]; 我们声明了一个NSString类型的变

谈谈ios内存管理--持续更新

本文主要谈谈ios内存管理的发展脉络,不足之处,还请指教,相互学习交流.做ios开发,永远无法避开内存管理,无论我们是否有意识去考虑这个事情,但是只要我们写了OC程序,那么就与内存管理有关. 一.内存管理是做什么的? 二.内存管理方式一:MRC (一)引用计数器 (二)原则 (三)alloc.new.copy.mutableCopy.retain.release.dealloc alloc内部实现 引用计数表 (四)autorelease 三.内存管理方式二:ARC (一)__strong (二

iOS内存管理个人总结

一.变量,本质代表一段可以操作的内存,她使用方式无非就是内存符号化+数据类型 1.保存变量有三个区域: 1>静态存储区 2>stack 3>heap 2.变量又根据声明的位置有两种称呼: 1>全局变量 2>局部变量 3.三种存储区分别存储那种变量 1>静态存储区 - 在编译分配空间的时候初始化,程序运行时存在 全局变量.静态局部变量 2>stack 栈存放局部变量(这个变量是引用变量或编辑器负责自动释放的变量,例如:int,long,double基础类型,她们并没

iOS内存管理 ARC与MRC

想驾驭一门语言,首先要掌握它的内存管理特性.iOS开发经历了MRC到ARC的过程,下面就记录一下本人对iOS内存管理方面的一些理解. 说到iOS开发,肯定离不开objective-c语言(以下简称OC).OC的内存管理机制叫做引用计数,就是一块内存地址可以同时被多个对象引用,每引用一次,引用计数都会递增1,当对象每解除一次引用,引用计数就会递减1,直到引用计数为0时,系统才会讲这块内存地址回收释放掉,这与C/C++语言有些不同,但是它们都遵守同一个内存管理法则:谁申请,谁释放. 在早些时候,iO

【Bugly干货分享】iOS内存管理:从MRC到ARC实践

Bugly 技术干货系列内容主要涉及移动开发方向,是由Bugly邀请腾讯内部各位技术大咖,通过日常工作经验的总结以及感悟撰写而成,内容均属原创,转载请标明出处. 对于iOS程序员来说,内存管理是入门的必修课.引用计数.自动释放等概念,都是与C语言完全不同的.搞明白这些,代码才有可能不 crash.然而就是这么牛逼的内存管理,着实让我这个从 C 转过来的老程序员头疼了一段时间. [C++ 程序员的迷惑和愤怒] iOS 内存管理的核心是引用计数.与众多五年甚至更多以上开发经验的程序员一样,笔者当初是

IOS内存管理retain,assign,copy,strong,weak

IOS内存管理retain,assign,copy,strong,weak IOS的对象都继承于NSObject, 该对象有一个方法:retainCount ,内存引用计数. 引用计数在很多技术都用到: window下的COM组件,多线程的信号量,读写锁,思想都一样. (一般情况下: 后面会讨论例外情况)alloc 对象分配后引用计数为1retain 对象的引用计数+1copy copy 一个对象变成新的对象(新内存地址) 引用计数为1 原来对象计数不变 release 对象引用计数-1 如果为

IOS内存管理学习笔记

内存管理作为iOS中非常重要的部分,每一个iOS开发者都应该深入了解iOS内存管理,最近在学习iOS中整理出了一些知识点,先从MRC开始说起. 1.当一个对象在创建之后它的引用计数器为1,当调用这个对象的alloc.retain.new.copy方法之后引用计数器自动在原来的基础上加1(ObjC中调用一个对象的方法就是给这个对象发送一个消息),当调用这个对象的release方法之后它的引用计数器减1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象. [e

浅谈iOS内存管理机制

iOS内存管理机制的原理是引用计数,引用计数简单来说就是统计一块内存的所有权,当这块内存被创建出来的时候,它的引用计数从0增加到1,表示有一个对象或指针持有这块内存,拥有这块内存的所有权,如果这时候有另外一个对象或指针指向这块内存,那么为了表示这个后来的对象或指针对这块内存的所有权,引用计数加1变为2,之后若有一个对象或指针不再指向这块内存时,引用计数减1,表示这个对象或指针不再拥有这块内存的所有权,当一块内存的引用计数变为0,表示没有任何对象或指针持有这块内存,系统便会立刻释放掉这块内存. 其