在高级语言中,例如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之内存管理的更多相关文章

  1. .NET基础拾遗(1)类型语法基础和内存管理基础

    Index : (1)类型语法.内存管理和垃圾回收基础 (2)面向对象的实现和异常的处理 (3)字符串.集合与流 (4)委托.事件.反射与特性 (5)多线程开发基础 (6)ADO.NET与数据库开发基 ...

  2. PHP扩展-生命周期和内存管理

    1. PHP源码结构 PHP的内核子系统有两个,ZE(Zend Engine)和PHP Core.ZE负责将PHP脚本解析成机器码(也成为token符)后,在进程空间执行这些机器码:ZE还负责内存管理 ...

  3. linux2.6 内存管理——逻辑地址转换为线性地址(逻辑地址、线性地址、物理地址、虚拟地址)

    Linux系统中的物理存储空间和虚拟存储空间的地址范围分别都是从0x00000000到0xFFFFFFFF,共4GB,但物理存储空间与虚拟存储空间布局完全不同.Linux运行在虚拟存储空间,并负责把系 ...

  4. linux2.6 内存管理——概述

    在紧接着相当长的篇幅中,都是围绕着Linux如何管理内存进行阐述,在内核中分配内存并不是一件非常容易的事情,因为在此过程中必须遵从内核特定的状态约束.linux内存管理建立在基本的分页机制基础上,在l ...

  5. Objective-C内存管理之引用计数

    初学者在学习Objective-c的时候,很容易在内存管理这一部分陷入混乱状态,很大一部分原因是没有弄清楚引用计数的原理,搞不明白对象的引用数量,这样就当然无法彻底释放对象的内存了,苹果官方文档在内存 ...

  6. Quartz2D内存管理

    p.p1 { margin: 0.0px 0.0px 0.0px 0.0px; font: 14.0px "PingFang SC"; color: #239619 } p.p2 ...

  7. 浅谈Linux内存管理机制

    经常遇到一些刚接触Linux的新手会问内存占用怎么那么多?在Linux中经常发现空闲内存很少,似乎所有的内存都被系统占用了,表面感觉是内存不够用了,其实不然.这是Linux内存管理的一个优秀特性,在这 ...

  8. linux内存管理

    一.Linux 进程在内存中的数据结构 一个可执行程序在存储(没有调入内存)时分为代码段,数据段,未初始化数据段三部分:    1) 代码段:存放CPU执行的机器指令.通常代码区是共享的,即其它执行程 ...

  9. cocos2d-x内存管理

    Cocos2d-x内存管理 老师让我给班上同学讲讲cocos2d-x的内存管理,时间也不多,于是看了看源码,写了个提纲和大概思想 一.   为什么需要内存管理 1. new和delete 2. 堆上申 ...

随机推荐

  1. 第3章 Linux常用命令(5)_网络命令和挂载命令

    7. 网络命令 7.1 给用户发信息,以ctr+D保存结束 (1)write命令 命令名称 write 命令所在路径 /user/bin/write 执行权限 所有用户 语法 write <用户 ...

  2. canvas事件处理机制

    可以查看demo:http://sandbox.runjs.cn/show/hjb2hzzx(建议查看console查看点击后的改变) 具体原理是每次点击的时候去判断当前的鼠标坐标是属于哪一个路径下的 ...

  3. 关于Repository、Autofac、DbContext简单例子

    运行环境:Visual Studio 2012  Mvc4 数据库备份:Sql Server 2008 r2 解决方案图片: 简单介绍:此示例供初学者了解基本的Repository模式,并结合依赖注入 ...

  4. 再读《C++ Primer》——变量和基本类型

    刚上大学那时,几个室友一块买了本<C++ Primer>第4版,看了一遍后就没怎么碰了,偶尔拿出来翻翻,当作工具书使用.后来知道有第5版了,一直觉得内容差不多吧.直到最近,再读其中的一些内 ...

  5. JS组件系列——封装自己的JS组件,你也可以

    前言:之前分享了那么多bootstrap组件的使用经验,这篇博主打算研究下JS组件的扩展和封装,我们来感受下JQuery为我们提供$.Extend的神奇,看看我们怎么自定义自己的组件,比如我们想扩展一 ...

  6. Swift学习(一):自定义运算符 operator

    自定义运算符仅能包含这些字符: / = - + * % < >!& | ^.~ 运算符位置: 前置运算符 prefix 中间运算符 infix 后置运算符 postfix 运算符其 ...

  7. angularjs + fis +modJS 对于支持amd规范的组建处理(PhotoSwipe 支持,百度webUpload支持)

    这不是很好的处理方式,但是能够解决问题,希望有大神推荐更好的方式. 前端模块使用angularjs + fis +modJS 开发前端应用有两个月了.总结了以下的优点: fis 自动构建,自动发布,功 ...

  8. 招聘前端、Java后端开发、测试、Mysql DBA

    公司介绍: http://www.lagou.com/gongsi/43095.html http://www.yamichu.com 简历发到: zhuye@yamichu.com 招聘职位: JA ...

  9. 教你一招:Excel中使用vlookup函数查询序列所对应的值

    以一个简单的例子做示范,列数相对较少,看起来也比较清楚:在奥运会或其他比赛上我们可以看到各个国家的奖牌数的变化:那么我们如何查询国家对应的总奖牌数. 我们用到的函数是vlookup,它是一个纵向查询函 ...

  10. IIS注册.netframework4.0指令

    C:\Windows\Microsoft.NET\Framework\v4.0.30319\aspnet_regiis.exe -i