解铃还须系铃人

--1--内存管理的原理及分类
1.1 内存管理的原理
1.2 内存管理的分类
--2--手动内存管理
2.1 关闭ARC的方法
2.2 手动管理(MRC)快速入门
--3-- 内存管理的原则
3.1 内存管理的原则
3.2 内存管理研究的内容
--4-- 单对象内存管理
4.1 单个对象的野指针问题
4.2 避免使用僵尸对象的方法
4.3 对象的内存泄漏
--5-- 多个对象内存管理
5.1 多个对象的野指针问题
5.2 多个对象内存泄漏问题
--6-- set方法内存管理
6.1 set方法存在的问题
6.2 不同类型的setter写法
--7-- autorelease
7.1 autorelease是什么
7.2 为什么会有autorelease
7.3 autorelease基本用法
7.4 autorelease的原理
7.5 autorelease什么时候被释放
7.6 autorelease使用注意

--------------------------------------

【写在开头】

『使用这个标题,“解铃还须系铃人”好像有点不正式。但这里,只是想突出一个内存管理的原则:“谁创建,谁释放”。iOS的内存管理和Java等语言的垃圾回收机制不同,Java的垃圾回收机制是运行时的特性,由jvm去回收释放内存。这里不谈Java,回到iOS的内存管理,目前创建项目默认就是ARC机制,而以前是MRC机制,需要程序员自己手动去回收释放内存,注意ARC和MRC都是编译器的特性,一个移动设备的内存是有限的,所以学习iOS的内存管理非常必要。

OC内存管理的范围:

管理任何继承自NSObject的对象,对其他的基本数据类型无效。

本质原因是因为对象和其他数据类型在系统中的存储空间不一样,如局部变量最主要存放在栈中。而对象存储在堆中,当代码块结束时,这个代码块中涉及的所有的局部变量会被回收,指向对象的指针也被回收,此时对象已经没有指针指向,但依然存在于内存中,就会造成内存泄漏。

下面的内存管理内容主要是MRC机制』

--1--内存管理的原理及分类

1.1 内存管理的原理

1)对象的所有权及引用计数

  对象所有权概念:

  任何对象都可能拥有一个或多个所有者。只要一个对象至少还拥有一个所有者,它就会继续存在。

  Cocoa所有权策略:

  任何自己创建的对象都归自己所有,可以使用名字以“alloc"或”new“开头或名字中包含”copy“的方法创建对象,可以使用retain来获得一个对象的所有权。

  对象的引用计数器:

  每个OC对象都有自己的引用计数器,是一个整数表示对象被引用的次数,即现在有多少东西在使用这个对象。对象刚被创建时,默认计数器值为1,当计数器的值变为0时,则对象销毁。

2)引用计数器的作用

  引用计数器(retainCount):标志被引用的次数,存储当前对象有几个使用者,是判断对象是否回收的依据。

  (例外:对象值为nil时,引用计数器为0,但不回收空间,因为没有分配空间)

3)对引用计数器的操作:

  retain消息:使计数器+1,方法返回对象本身

  release消息:使计数器-1

  retainCount消息:获得当前对象的retainCount的值

4)对象的销毁:

  当retainCount为0时,对象被销毁,内存被回收。对象被销毁时,系统自动向对象发送一条dealloc消息,一般会重写dealloc消息,在这里释放相关的资源,dealloc就像是对象的“临终遗言”。

  一旦重写了dealloc方法就必须调用[super dealloc](注意不能直接调用dealloc方法)。

  一旦对象被回收了,那么他所占据的存储空间就不再可用,坚持使用会导致程序崩溃(野指针错误)。

注意:

1)如果对象的计数器不为0,那么在整个程序运行过程中,它占用的内存就不可能被回收(除非整个程序已经退出)

2)任何一个对象,刚创建的时候,引用计数器都为1,当使用alloc、new或者coppy创建一个对象时,对象的引用计数器默认是1

从此可以看出:

回收一个对象与否的标记就是引用计数器是否为0.

1.2 内存管理的分类

OC内存管理分为3类:

  1)Manual Reference Counting (MRC,手动管理,在开发iOS4.1之前的版本的项目时,需要自己负责使用引用计数来管理内存,比如要手动retain、release、autorelease等,而在其后的版本可以使用ARC,让系统自己管理内存)

  2)Automatic Reference Counting(ARC,自动引用计数,iOS4.1之后推出)

  3)garbage collection(垃圾回收)IOS不支持垃圾回收

ARC作为苹果新提供的技术,苹果推荐开发者使用ARC技术来管理内存。

--2--手动内存管理

2.1 关闭ARC

创建一个项目时,默认是ARC的(自动内存管理),手动把ARC项目改成MRC项目的方法之一如下:

a.选中项目,此时Xcode右侧会出现如图设置信息

b. 在同时选中Build Settings和Levels时在右侧搜索框搜索auto,此时会出现ARC设置

c.把ARC设置中目标target下的Yes设置为No,其他相应的Yes也变为No,此时就是MRC管理

2.2 手动管理(MRC)快速入门

内存管理的关键是判断对象是否被回收?

重写dealloc方法,

代码规范

1)要调用父类的dealloc方法[supper dealloc],而且要放到最后,意义是:先释放子类占用的空间再释放父类占用的空间

2)对self(当前)所拥有的其他对象做一次release操作

 -  (void) dealloc

{

[_car release]; //对象关联的对象

[super dealloc];

}

注意:

一旦对象被回收了,它占用的内存就不再可用,坚持使用会导致程序崩溃(野指针)为了防止调用出错,也可以将“野指针”指向nil(0);

--3-- 原则

3.1 内存管理的原则

1)原则:

  如果对象还有使用者,就不应该回收;

  如果你想使用这个对象,那么就应该让这个对象的引用计数器+1;

  当你不想使用这个对象时,应该让对象的引用计数器-1;

2)谁创建,谁release

  1.如果通过alloc, new, coppy来创建一个对象,那么久必须调用对应的release或者autorelease方法

  2.谁创建谁负责

3)谁retain,谁release

  只要你调用了retain,无论这个对象是如何生成的,你都需调用release

总结:

有始有终,有加就应该有减,让某个对象计数器加1,就应该让其在最后-1

对象如果不再使用了,就应该回收它的空间,防止造成内存泄漏

3.2 内存管理研究的内容

1)野指针(僵尸对象)

野指针:

  1)定义的指针变量没有初始化

  2)指向的空间已经被释放了

2)内存泄漏

内存泄漏:

Person *p = [Person new];

//变量p存储在栈区

//[Person new]; 对象存储在堆区

如果栈区的p已经被回收,而堆区的空间还没有释放,堆区的空间就造成了泄漏

--4-- 单对象内存管理

4.1 单个对象的野指针问题

野指针错误:访问了一块不可再用的内存

僵尸对象:所占的内存已经被回收的对象,僵尸对象不能再被使用。(默认情况下Xcode为了提高编码效率,没有开启僵尸对象检测。若要检测,需先打开僵尸对象检测)

空指针:没有指向任何对象的指针: Person *p = nil;

   给空指针发送消息不会有反应

4.2 避免使用僵尸对象的方法

为了防止不小心调用了僵尸对象,可以将对象赋值nil(对象的空值)

Dog *dog = [[Dog alloc] init];

[dog release]; //引用计数-1

dog = nil; //空指针

[dog retain]; //给nil发送任何消息,都不会有效果。所以僵尸对象检测不会报错

4.3 对象的内存泄漏

情景1:

//创建完成使用后,没有release

Dog *d = [Dog new]; //1
//如果对象没有被回收,就造成了内存泄漏

情景2:

没有遵守内存管理的原则

Dog *d = [Dog new]; //引用计数为1

[d retain]; // 计数+1 = 2

[d release]; //计数 - 1 = 1
//计数不为0,不能被回收

情景3:

不当的使用了nil

Dog *d = [Dog new];

d = nil; //指针指向nil

[d run]; //nil run不会有效果

[d relese]; //nil release

//而此处不能回收,因为Dog对象的引用计数还是1

情景4:

在方法中对传入的对象进行了retain,而调用时没有release

- (BOOL)compareColorWithOther:(Dog *)dog{

    [dog retain]; //此处对象引用计数 + 1

    return YES;

}

//而如果在后面没有相应的release,则同样会造成内存泄漏

--5-- 多个对象内存管理

5.1 多个对象的野指针问题

/**
情景:
Person对象拥有一个Car的对象属性
*/
Person *person = [[Person alloc] init]; //引用计数为1
Car *car = [[Car alloc] init]; //计数为1
        
car.speed = ; //car对象拥有速度属性
        
person.car = car; //将Person对象属性赋值为car
        
[car release]; //car对象计数释放一次 - 1 -->变成了0 [person goByCar]; //此时car变成了一个僵尸对象-->car已经是野指针。但是goByCar中还使用着car对象,这样开启检测后就会抛出运行时错误

5.2 多个对象内存泄漏问题

针对 5.1 中存在的问题。

如果在[car  release]之后,不小心再使用了car对象,就会造成野指针访问错误。所以此处解决这个问题的方法是,在Person类中重写setter方法,在setter方法中将传过来的car对象retain一次。

这样,调用时,[car release]后还可以使用car对象,并在Person的dealloc中将_car对象release一次。

只是这样的解决方法还是不严谨的,正确的写法应该是下面 6.2 中的写法。

--6-- set方法内存管理

6.1 set方法存在的问题

原对象无法释放造成了内存泄漏

根据内存管理的原则:

谁创建,谁release

Car *car = [[Car alloc] init];

Car *bmw = [[Car alloc] init]; //新对象

//谁创建,谁release
[bmw release];
[car release]; 

按照 5.2 中的写法,直接在setter方法中将对象的引用计数+1。但像这样有两个对象时,那么旧对象就不能释放了,因为旧对象在setter中之前就retain了一次,变成了2,而在Person中只是将包含的_car对象release一次,这样旧对象就又造成了内存泄漏。

所以setter中正确的写法应该是这样:

- (void)setCar:(Car *)car{

    if (_car != car){ //如果是新的对象,则先把旧的对象release一次,再将新的对象赋值_car

    [_car release]; //初次是[nil release];

    _car = [car retain]; //再让新的对象引用计数+1

    }

}

Person的dealloc中,只要让成员对象release一次就行了

- (void)dealloc{

    [_car release]; //relase成员属性

    NSLog(@"Person被回收");

    [super dealloc];

}

6.2 不同类型的setter写法

1)基本数据类型:直接赋值(因为基本数据类型由系统回收)

//基本数据类型直接赋值即可
//int float double long struct enum -(void)setAge:(int)age { _age = age; }

2)OC对象类型

//对象类型需手动释放其内存
-(void)setCar:(Car *)car { //1.先判断是不是新传进来的对象 if (car != _car){ //2.对旧对象做一次release [_car release]; //若没有旧对象,则没有影响 //3.对新对象做一次retain,并且赋值给实例变量 _car = [car retain]; } }

总结:

setter的内存管理:

原则:如果在一个类中有其他类的对象(关联关系),在setter中,要判断是否是同一个对象。如果是不同对象则先release旧值,再retain新值。

--7-- @autoreleasepool

7.1 @autoreleasepool是什么

autoreleasepool自动释放池

(1)在iOS程序运行过程中,会创建无数个“池子”,这些“池子”以栈结构的形式存在。

(2)当一个对象调用autorelease时,会将这个对象放到位于栈顶的释放池中 。

自动释放池的创建方式

1)iOS5.0以前的创建方式

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

……………….

[pool release];//[pool drain];用于mac

2)iOS5.0以后

@autoreleasepool

{ //开始代表创建自动释放池

  //

} //结束代表销毁自动释放池

autorelease

是一种支持引用计数的内存管理方式

它可以暂时的保存某个对象(object),然后在内存池自己的排干(drain)的时候对其中的每个对象发送release消息。

注意,这里只是发送release消息,如果当时的引用计数(reference-counted)依然不为0,则该对象依然不会被释放。可以用该方法来保存某个对象,但也要注意保存之后要释放该对象。

7.2 为什么会有autorelease

OC的内存管理机制中比较重要的一条规律是:谁申请,谁释放

考虑这种情况,如果一个方法需要返回一个新建的对象,该对象何时释放?

方法内部是不会写release来释放对象的,因为这样做会将对象立即释放而返回一个空对象:调用也不会主动释放该对象的,因为调用者遵循“谁申请,谁释放”的原则,那么这个时候,就发生了内存泄露。

不使用autorelease存在的问题

针对这种情况,Objective-C的设计了autorelease,既能确保对象能正确释放,又能返回有效的对象。

使用autorelease的好处

(1)不需要再关心对象释放的时间

(2)不需要再关心什么时候调用release

7.3 autorelease基本用法

基本用法

(1)会将对象放到一个自动释放池中

(2)当自动释放池被销毁时,会对池中的所有对象发送一次release

(3)返回对象本身

(4)调用完autorelease方法后,对象的计数器不受影响(销毁时影响)

在autorelease的模式下,下述写法是合理的,即可以正确返回结果,也不会造成内存泄露

ClassA *Func1(){

Class *obj=[[ClassA alloc]init autorelease];

return obj;

}
int main(int argc, const char * argv[]) {
Person *p = [[Person alloc] init];
@autoreleasepool { //注意:加入到自动释放池中以后,引用计数不会变化
[p autorelease]; //把对象p加入到自动释放池中
NSLog(@"p.retainCount = %lu", p.retainCount); //1
//[p release]; //无需手动release
}
return ;
}

重写delloc

@implementation Person

//重写dealloc
- (void)dealloc{
NSLog(@"%@对象释放", self);
[super dealloc];
}
@end

输出-->

7.4 autorelease的原理

autorelease实际上只是把对release的调用延迟了,对于每一个Autorelease,系统只是把该Object放入了当前的Autorelease pool中,当该pool被释放时,该pool中的所有Object会被调用Release。

7.5 autorelease什么时候被释放

对于autorelease pool本身,会在如下两个条件发生时候被释放(详细信息请参见第5条)

(1)手动释放Autorelease pool

(2)Runloop结束后自动释放

对于autorelease pool内部的对象

在引用计数的retain==0的时候释放。

release和autorelease pool的drain都会触发retain–事件。

7.6 autorelease使用注意

1)并不是放到自动释放池代码中,都会自动加入到自动释放池

 @autoreleasepool {
// 因为没有调用 autorelease 方法,所以对象没有加入到自动释放池
Person *p = [[Person alloc] init];
[p run];
}

2)在自动释放池的外部发送autorelease 不会被加入到自动释放池中

autorelease是一个方法,只有在自动释放池中调用才有效。

@autoreleasepool {
}
// 没有与之对应的自动释放池, 只有在自动释放池中调用autorelease才会加入释放池
Person *p = [[[Person alloc] init] autorelease]; //没有写在自动释放池内,写在了自动释放池外
[p run]; // 正确写法1
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
} // 正确写法2
Person *p = [[Person alloc] init];
@autoreleasepool {
[p autorelease];
}

自动释放池的嵌套使用

  • 自动释放池是以栈(栈结构)的形式存在的
  • 由于栈只有一个入口, 所以调用autorelease会将对象放到栈顶的自动释放池
    •   >栈顶是离调用autorelease方法最近的自动释放池
@autoreleasepool { // 栈底自动释放池

    @autoreleasepool {

        @autoreleasepool { // 栈顶自动释放池

        Person *p = [[[Person alloc] init] autorelease];
}
Person *p = [[[Person alloc] init] autorelease];
}
}
  • 自动释放池中不适宜放占用内存比较大的对象
    • 尽量要避免将大内存对象加入释放池,因为释放池的延迟释放机制,会使其一直常驻内存
    • 不要把大量循环操作放到同一个 @autoreleasepool 之间,这样会造成内存峰值的上升
    // 内存峰值会上升
@autoreleasepool {
for (int i = ; i < ; ++i) {
Person *p = [[[Person alloc] init] autorelease];
}
} //可以使用下面的方法
// 内存不会暴涨
for (int i = ; i < ; ++i) {
@autoreleasepool {
Person *p = [[[Person alloc] init] autorelease];
} //创建完一个对象就释放了
}

【写在结尾:】

『学习如逆水行舟,不进则退』

OC-内存管理的更多相关文章

  1. OC 内存管理机制总结

    OC 内存管理机制总结 一:OC内存管理机制目前分为两块,其一自动内存管理机制,其二手动内存管理机制: 1.首先我们从自动内存管理机制讲起: 1)什么是自动内存管理机制,自动内存管理机制就是程序中所创 ...

  2. OC内存管理基础

    OC 内存管理基础 一. retain和release基本使用 使用注意: 1.你想使用(占用)某个对象,就应该让对象的计数器+1(让对象做一次retain操作) 2.你不想再使用(占用)某个对象,就 ...

  3. QF——OC内存管理详解

    堆的内存管理: 我们所说的内存管理,其实就是堆的内存管理.因为栈的内存会自动回收,堆的内存需要我们手动回收. 栈中一般存储的是基本数据类型变量和指向对象的指针(对象的引用),而真实的对象存储在堆中.因 ...

  4. OC内存管理-OC笔记

    内存管理细节:http://blog.sina.com.cn/s/blog_814ecfa90102vus2.html 学习目标 1.[理解]内存管理 2.[掌握]第一个MRC程序 3.[掌握]内存管 ...

  5. OC内存管理-黄金法则

    1.内存管理-黄金法则 The basic rule to apply is everything that increases the reference counter with alloc, [ ...

  6. OC内存管理总结,清晰明了!

    <span style="font-size:18px;">OC内存管理 一.基本原理 (一)为什么要进行内存管理. 由于移动设备的内存极其有限.所以每一个APP所占的 ...

  7. 31 (OC)* 内存管理

    31 (OC)  内存管理 一:内存管理黄金法则. 如果对一个对象使用了alloc.[Mutable]copy,retain,那么你必须使用相应的realease或者autorelease 二:内存管 ...

  8. iOS学习17之OC内存管理

    1.内存管理的方式 1> iOS应用程序出现Crash(闪退),90%的原因是因为内存问题. 2> 内存问题 野指针异常:访问没有所有权的内存,如果想要安全的访问,必须确保空间还在 内存泄 ...

  9. 【0 - 1】OC内存管理

    一.内存管理概述 垃圾回收机制(GC):由系统管理内存,程序员不需要管理. OC中的垃圾回收:在OC2.0版加入垃圾回收. OC与iOS:OC有垃圾回收机制,但是iOS屏蔽了这个功能.原因:iOS运行 ...

  10. OC内存管理(ARC)

    1.什么是ARC Automatic Reference Counting,自动引用计数,即ARC,可以说是WWDC2011和iOS5所引入 的最大的变革和最激动人心的变化.ARC是新的LLVM 3. ...

随机推荐

  1. Thinking in Java——笔记(11)

    Holding Your Objects In general, your programs will always be creating new objects based on some cri ...

  2. Oracle数据库基础知识2

    字符操作相关_1 1.CONCAT关键字作用:连接字符串语法:CONCAT(字串1, 字串2)例如: CONCAT('hello','world') FROM DUAL; 注意:Oracle的CONC ...

  3. 如何编写自己的Arduino库?

    一开始写Arduino 的时候很不习惯,没有main函数,因为好多东西都被隐藏了.一直想搞清楚,以便编写自己的库文件.于是研究一下午,下面是一些总结. Arduino工程的初步认识 一.目录规范 当你 ...

  4. DataTable数据赋值给Model通用方法

    注:该文属本人原创,今后项目中发现该方法存在BUG会实时更新,转载记得附上原文出处,方便大家获得最新代码. 相信大家在做项目中,经常会根据不同的表new各种不同的Model,当需要对Model进行实例 ...

  5. windows查看端口占用以及关闭相应的进程

    开始--运行--cmd 进入命令提示符 输入netstat -ano 即可看到所有连接的PID 之后在任务管理器中找到这个PID所对应的程序如果任务管理器中没有PID这一项,可以在任务管理器中选&qu ...

  6. imx6 Image Vector Table (IVT)

    imx6开启启动之后,运行板子上的ROM程序.ROM确定启动的设备,进行一些初始化,然后读取IVT,进行寄存器初始化,最后运行uboot/cpu/arm_cortexa8/start.S中的_star ...

  7. iOS10配置说明

    1:如果你的App想要访问用户的相机.相册.麦克风.通讯录等等权限,都需要进行相关的配置,不然会直接crash掉. 要想解决这个问题,只需要在info.plist添加NSContactsUsageDe ...

  8. sprintf()函数的用法

    Visual C++ sprintf()函数用法 转:http://blog.csdn.net/masikkk/article/details/5634886 在将各种类型的数据构造成字符串时,spr ...

  9. SpringMVC源码剖析(四)- DispatcherServlet请求转发的实现

    SpringMVC完成初始化流程之后,就进入Servlet标准生命周期的第二个阶段,即“service”阶段.在“service”阶段中,每一次Http请求到来,容器都会启动一个请求线程,通过serv ...

  10. STMFD 和LDMFD指令

    http://blog.163.com/oy_mcu/blog/static/16864297220120193458892/ LDM/STM指令主要用于现场保护,数据复制,参数传送等. STMFD指 ...