06OC之内存管理
在高级语言中,例如C#是通过垃圾回收机制(GC)来解决这个问题,但是在OC并没有类似的垃圾回收机制,因此必须由程序员手动去维护。今天就讲讲OC中的内存管理:
一、内存管理原理
在Xcode4.2之后的版本,由于引入了ARC(Automatic Reference Counting)机制,程序编译Xcode会自动给你的代码添加内存释放代码,如果编写手动释放代码Xcode会报错,因此在今天的内容,如果使用Xcode4.2以上的版本,必须手动关闭ARC,这样有助于理解OC的内存机制。关闭的步骤如下:
项目设置->Building-setting->搜索garbage找到Objective-C Automatic Reference Counting,设置成No即可。
在C#中有GC给我们自动管理内存,当我们实例化一个对象之后,通常会有一个变量引用这个对象,变量是存储对象的内存地址,当不再需要引用这个对象时,GC就会自动回收这个对象,简单的说,当一个对象没有任何对象引用的时候,就会被回收。
举个简单的例子:
using System;
namespace GC
{
class Program
{
private static void Test()
{
object o=new object();
} static void Main(string[] args)
{
Test();
}
}
}
上述的代码,在Test方法中,通过new Object()创建了一个对象,o是一个对象的引用,它是一个局部变量,作用域为Test方法体内。当在Main方法执行完test,O就会被释放掉,因为没有变量在引用new Object()这个对象,因此GC会自动回收这个对象所占用的空间。
二、引用计数器
在OC中没有GC,那么它的内存是怎么管理呢?其实在OC中内存管理是依赖对象引用计数器来进行的,何为计数器?在OC中每个对象内部都有一个与之对应的整数(retainCount),叫“引用计数器”,一个对象在创建之后它的引用计数器为1,当调用这个对象的时候(alloc,retain,new,copy)的时候,引用计数器自动在原来的基础上加1,当调用这个对象的release方法之后,它的引用计数器减1,如果一个对象的引用计数器为0,则系统会自动调用这个对象的dealloc方法来销毁这个对象。
下面我们简单做个例子:
Person.h
Person.m
main.m
执行结果如下:
在这个例子中,我们通过重写了dealloc方法,可以知道一个对象是否已经被回收,如果没有被回收则有可能造成内存泄漏。如果一个对象被释放之后,那么最后引用它的变量我们需要手动式何止为nil,否则有可能造成野指针错误,因为访问了不属于这个程序的内存地址。另外要注意一点,在OC中像空指针发送消息是不会引起任何错误的。
产生野指针报错一般如下:
三、内存释放的原则
手动管理内存有时候并不容易,因为对象的引用错综复杂,可能对象之间又相互引用,这时候就要遵循一个原则:谁创建,谁引用。
假设现在有一个Person类,每个Person都可能买一辆汽车,通常买汽车这个行为我们会封装成一个方法中,同时买车的过程中,我们可能会多看几辆车来确定最终理想的车,代码如下:
Car.h
Car.m
Person.h
Person.m
Main.m
执行结果如下:
需要注意的有:
car1和car2都被回收了,然而我们执行p.car run却不会报错,这就达到了我们的目的。首先我们要先解释下,为什么我们的setCar不是写成这种形式?
-(void)setCar:(Car *)car{
_car=car;
}
因为我们之前讲的属性赋值都是采用这种方式的啊。根据前面所说的内存释放原则,getCar方法完全符合,在方法中定义的两个对象car1、car2也都是在这个方法中释放的,main函数的P也是在main函数中定义个释放的。
然而我们是写成这个样子:
-(void)setCar:(Car *)car{
if (_car!=car) { //首先判断要赋值的变量和当前成员变量是不是同一个变量
[_car release]; //释放之前的对象
_car=[car retain];//赋值时重新retain
}
}
在这个方法中我们通过调用car retain可以保证每次属性赋值的时候对象的引用计数器+1,这样以来调用过getCar方法可以保证人员的Car属性不会被释放,同时呢,也可以保证上一次的赋值对象Car1能够正常的释放,因为我们在赋新值的时候,进行了release操作。最后在Person的dealloc方法中对_car进行了一次realse操作(因为在setCar时候做了一次的retain操作),就可以保证_car正常回收。
四、Propety(属性参数)
如上,setCar方法的情况是比较多的,那么如何使用@propety自动实现呢?答案是使用属性参数,例如上面的setter方法,可以通过@property定义如下:
@property (nonatomic,retain) Car *car;
这样子我们不需要手动去实现car的setter方法,setter方法也不会导致内存泄漏,其实大家可以看到前面Person的name属性也加上了(nonatomic,copy)属性,这些参数是什么意思呢?
如下所示,property的参数分为三类,也就是说参数最多只能有三个,中间用逗号隔开,每类的参数可以从上表中的参数中任选一个,如果不进行设置,会取以上三类的默认参数,默认参数为(atomic,readwrite,assign)
一般情况下,如果在多线程开发中一个属性可能会被两个或者两个以上的线程同时访问,这个时候就要考虑atomic,相当于加锁,否则的话,建议使用nonatomic,不用加锁,效率够高。readwrite方法会生成getter、setter两个方法,如果只是readonly只会生成getter方法,关于set方法处理需要特别说明一下,假如我们定义一个属性a,这里列出以上三种生成方案:
1、assign,用于基本类型
-(void)setA:(int)a{
_a=a;
}
2、retain,常用于非字符串对象
-(void)setA:(Car *)a{
if(_a!=a){
[_a release];
_a=[a retain];
}
}
3、copy,常用于字符串对象,block,NSArray,NSDictonary
-(void)setA:(NSString *)a{
if(_a!=a){
[_a release];
_a=[a copy];
}
}
备注:
这是基于MRC进行介绍,ARC的情况不一样,例如ARC的基本数据类型默认的属性参数为(atomic,readwrite,assign),对象类型的默认属性参数为(atomic,readwrite,strong)
五、自动释放池
在OC中,也有一种内存自动释放的机制,叫做:“自动引用计数”或者“自动释放池”,然而,与C#的GC不一样的是,它是一个半自动的,有些操作需要我们手动去设置。自动内存释放我们使用@autoreleasepool关键字来声明一个代码块,如果一个对象在初始化的时候调用了autorelease方法,那么当代码块的代码执行完毕之后,在块中调用过autorelease方法对象都会自动调用一次release方法,这样一来就起到了自动释放的作用,同时对象的销毁过程也得到了延迟,因为统一调用了release方法。
Person.h
Person.m
Main.m
让我们看下执行结果:
如上结果所示,三个对象都得到了释放,但是person4并没有释放,原因很简单,因为我们手动retain了一下,计数器加1,当自动释放池统一release对象,但是Peron4没有释放,这就造成了内存泄漏。再说Person1,autorelease方法只是将对象的内存释放延迟到了自动释放池销毁的时候,因此person1,调用完autorelease之后,它还存在的,因此给它赋值不会有任何问题。在OC中通常一个静态方法返回一个对象本身的话,在静态方法我们需要调用autorelease方法,因为按照内存释放原则,在外部使用不应该对操作这个静态对象的release方法或者autorelease方法,因此这个调用需要在静态方法内部完成。
总结下内存管理:
1、autorealease方法不会改变对象的引用计数器,只是将这个对象放在自动释放池中。
2、自动释放池的本质是当自动释放池销毁后调用对象的release方法,不一定就能销毁对象,只是-1的操作,如果计数器>1,该对象则无法销毁。
3、由于自动释放池最后统一销毁对象,因为如果一个操作比较占用内存,对象比较多,或者对象占用资源比较多,最好不要放在自动释放池中,或者考虑放在多个自动释放池中。
4、OC中类库的静态方法一般都不需要手动释放,因为内部已经调用了autorelease方法。
06OC之内存管理的更多相关文章
- .NET基础拾遗(1)类型语法基础和内存管理基础
Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...
- PHP扩展-生命周期和内存管理
1. PHP源码结构 PHP的内核子系统有两个,ZE(Zend Engine)和PHP Core.ZE负责将PHP脚本解析成机器码(也成为token符)后,在进程空间执行这些机器码:ZE还负责内存管理 ...
- linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址)
Linux系统中的物理存储空间和虚拟存储空间的地址范围分别都是从0x00000000到0xFFFFFFFF,共4GB,但物理存储空间与虚拟存储空间布局完全不同.Linux运行在虚拟存储空间,并负责把系 ...
- linux2.6 内存管理——概述
在紧接着相当长的篇幅中,都是围绕着Linux如何管理内存进行阐述,在内核中分配内存并不是一件非常容易的事情,因为在此过程中必须遵从内核特定的状态约束.linux内存管理建立在基本的分页机制基础上,在l ...
- Objective-C内存管理之引用计数
初学者在学习Objective-c的时候,很容易在内存管理这一部分陷入混乱状态,很大一部分原因是没有弄清楚引用计数的原理,搞不明白对象的引用数量,这样就当然无法彻底释放对象的内存了,苹果官方文档在内存 ...
- Quartz2D内存管理
p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px "PingFang SC"; color: #239619 } p.p2 ...
- 浅谈Linux内存管理机制
经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...
- linux内存管理
一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分: 1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...
- cocos2d-x内存管理
Cocos2d-x内存管理 老师让我给班上同学讲讲cocos2d-x的内存管理,时间也不多,于是看了看源码,写了个提纲和大概思想 一. 为什么需要内存管理 1. new和delete 2. 堆上申 ...
随机推荐
- PHP对文件数据的存储和检索
@(主要是对文件的操作) 文件处理:php操作文件主要是写入和读取这两种.执行的步骤都是一样的. 1.打开这个文件.如果打不开就先创建它.2.将数据写入这个文件,或者将数据读出这个文件.3.关闭文件. ...
- Hibernate中Java对象的三种状态
Hibernate中Java对象的三种 ...
- H5框架之Bootstrap(一)
H5框架之Bootstrap(一) 接下来的时间里,我将和大家一起对当前非常流行的前端框架Bootstrap进行速度的学习,以案例的形式.对刚开始想学习Bootstrap的同学而找不着边的就很有帮助了 ...
- iTextSharp生成pdf的一个简单例子
效果图: 参考:http://www.cnblogs.com/CareySon/archive/2011/11/09/2243496.html http://www.cnblogs.com/julyl ...
- C 语言学习:班级同学的博客地址列表
这里整理出本班级同学的博客地址.请各位同学之间相互帮助,尽快将博客都建立起来. 编号 学号 姓名 Cnblogs昵称 博客地址 Coding.net 地址 1 160809401 付胤 付胤 http ...
- git远程仓库
git远程仓库: 目前我们使用到的 Git 命令都是在本地执行,如果你想通过 Git 分享你的代码或者与其他开发人员合作. 你就需要将数据放到一台其他开发人员能够连接的服务器上. 添加远程仓库: gi ...
- [MAVEN]二、常用命令
mvn eclipse:eclipse :生成 Eclipse 项目文件,生成后可以导入到eclipse中使用 mvn install :在本地 Repository 中安装 jar ,若是Web项目 ...
- sp_executeSql 用法
今天遇到了一个难题 就是把 一个拼接sql语句 的返回值 赋值给一个变量 经研究 要用sp_executeSql这个存储过程 据说是从sql 2005才开始有的 代码如下: declare @str ...
- 为网站文字前面添加图标 在线调用 Font Awesome 字体icon小图标 美化网站
一.如何开始 1.将下面的代码复制粘贴到HTML页面面的 <head> 下面 <link rel="stylesheet" href="https:// ...
- Thinking in java学习笔记之迭代器
Map<String,String> map = new HashMap<String,String>(); List list = new ArrayList(); list ...