内存管理、手动内存管理、单个对象内存管理、多个对象内存管理、set方法内存管理


一、内存管理


1、为什么要进行内存管理

由于移动设备的内存极其有限,所以每个APP所占的内存也是有限制的,当APP所占用的内存较多时,系统就会发送内存警告,每个APP可用的内存是被限制的,如果一个APP使用的内存超过20M,则系统会向该APP发送Momory Warning消息,收到此消息后,需要回收一些不需要再继续使用的内存空间,比如回收一些不再使用的对象和变量等,否则程序会崩溃。


2、OC内存管理的范围

管理范围:

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

分析原因:

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

小结:

1>对象存储于 堆中

2>其他局部变量主要存放于  栈中

存储于栈中的局部变量的特性:当代码块结束时这个代码块中的所有局部变量会被 回收(指向对象的指针也被回收)

堆的存储特性:堆的管理是要  程序员来管理,系统自己不会管理

各个内存区如下:



2、内存管理的原理

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

1>对象所有权概念

如何对象 都可能拥有一个或多个所有者,如果一个对象至少还拥有一个所有者,它(对象)就会继续存在

2>Cocoa所有权策略

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


2)对象的引用计数器

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


在每个OC对象内部,都专门有 8个字节 的存储空间来存储引用计数器


3)引用计数器的作用

引用计数器是判断 对象 是否要回收的依据,就是当引用计数器从1变为0时,就回收(或引用计数器为0)。

注意:存在一种例外:对象为nil时,引用计数器为0,但不收回空间


4)对引用计数器的操作

给对象发送消息,进行相应的计数器操作

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

release消息:使计数器-1,(并不代表释放对象)

retainCount消息:获得对象当前的引用计数器值(占位符为:%ld 或 %lu)


5)对象的销毁

当一个对象的引用计数器为0时,那么它将被销毁,其占用的内存被系统收回。

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

一旦重写了dealloc方法局必须调用[super  dealloc],并且放在代码块的最后调用(不能直接调用dealloc方法)。

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


注意:

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

2>任何一个对象,刚生下来的时候,引用计数器都为1。(对象一旦创建号,默认引用计数器就是1)当使用alloc、new或者copy创建一个对象时,对象的引用计数器默认就是1


3、OC内存管理分类

Objective-C提供了三种内存管理方式:

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

automatic  reference  counting(ARC  自动引用计数,iOS4.1之后推出的)

garbage collection (垃圾回收)。iOS不支持垃圾回收

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

开发中如何使用:需要理解MRC,但实际使用时尽量用ARC


二、手动内存管理


内存管理的关键如何判断对象被回收?

重写dealloc方法,代码规范

(1)一定要[super dealloc],而且要放到最后

意义是:先释放子类占用的空间,再释放父类占用的空间

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

例如:

-(void) dealloc

{

[_car  release];

[super dealloc];

}

注意:

永远不要直接通过对象调用dealloc方法(实际上调用并不会出错)

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


下面我们通过实例程序的演示来体验 retain、release、dealloc

Person.h



Person.m



main.m



打印输出:


总结:

1>对象刚创建时,拥有者有一个就是自己,retainCount=1;

2>对象每调用一次retain,拥有者+1retainCount-1

3>对象每调用一次release,拥有者-1retainCount-1

4>dealloc方法要重写

5>当retainCount=0时,会自动调用 dealloc方法


2、内存管理的原则

1)原则:

只要还有人在使用某个对象,那么这个对象就不会被回收

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

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


2)谁创建对象,谁release(创建对象和release是成对出现)

(1)如果你通过alloc、new、copy来创建了一个对象,那么你就必须调用 release或者autorelease方法

(2)不是你创建的就不用你去负责释放


3)谁retain,谁release(retain和release是成对出现)

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

4)总结

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



三、手动内存管理之——单个对象内存管理


1、单个对象的野指针问题

思考一个问题:

对象在堆区的空间已经释放了,还能 再使用p1?

1)野指针错误:访问了一块坏的内存(已经被回收的,不可用的内存)

2)僵尸对象:所占内存已经被回收的对象,僵尸对象不能再被使用。(默认情况下Xcode为了提高编码效率,不会时时检查僵尸对象,打开僵尸对象检测)


注意:

1>空指针:没有指向任何东西的指针,给空指针发送消息不会报错

关于nil和Nil及NULL的区别

nil:A null pointer to an Objective-CObject.

nil 是一个对象值


Nil:A null pointer to an Objective-Cclass.


NULL:A null pointer to anything else.

NULL 是一个通用指针(泛型指针)


2>不能使用[释放对象  retain]让僵尸对象起死复生


3>野指针操作


下面通过实例程序来演示

Dog.h



Dog.m



main.m



打印输出:


小结:

通过上面的打印可以得到以下总结:

1>遵守在手动内存管理中的原则:

> 谁创建,谁release

> 谁retain,谁release

下面我们再通过实验来体验一下是否可以使用 僵尸对象

Dog.h和Dog.m和上面一样:

main.m




打印输出:



小结:

1》当程序运行到最后的一个[dog  release]时,存储在堆区的对象dog([Dog new])的存储空间已经释放,但是存储在栈区的指针dog、jd 并没有释放,所以用dog、jd来方法run方法、retain方法时会报错,以为指针dog、jd指向的对象空间已经不在,这些操作是违法的是在使用僵尸对象

2》当在[dog release]以后,把指针dog、jd重新赋值来指向nil,在调用run方法和retain时并不会报错,这也是nil的一种用法,但是不推荐这样使用。

注意:3》如果dog、jd重新赋值指向nil是放在[dog release]以前,那就会造成内存的泄露!


2、避免使用僵尸对象的方法

因为如果程序较为复杂时,我们也不清楚执行哪一个release时,retainCount变为0,所以就会造成使用僵尸对象


下面我们通过实例程序演示看怎样使用nil来避免使用僵尸对象

Person.h



Person.m



main.m



打印输出:


小结:

从程序可以看出在[person  release]后直接跟上一句person=nil后再继续调用run方法、retain、时不会报错。


3、对象的内存泄露

原因有:

1)没有按照内存规则来做,而造成retain和release 个数不配配,导致内存泄露

2)对象使用的过程中被赋值了nil,导致内存泄露

3)在函数或者方法中不当的使用retain或者release造成的问题


下面通过实例程序来体验上面的内存泄露情况:

1)没有按照内存规则来做,而造成retain和release 个数不配配,导致内存泄露

Person.h



Person.m



main.m



打印输出:


小结:

因为retainCount不等于0,所以不会打印dealloc中的内容


2)对象使用的过程中被赋值了nil,导致内存泄露

Person.h



Person.m



main.m



打印输出:


小结:

本来就是使用nil不当,造成内存泄露


3)在函数或者方法中不当的使用retain或者release造成的问题

Person.h



Person.m



main.m



打印输出:


小结:

从main.m来看,程序是符合程序内存管理原则的,谁创建,谁release;最后一句

NSLog(@"\n2——%lu",[personretainCount]);本应该会因为调用僵尸对象而报错,但是也没有报错。


四、手动内存管理之——多个对象内存管理


1、本节我们讨论的问题是:WR开着他的BMW汽车去兜风

分析这句话:有两个类:人:WR、汽车:BMW

实例程序演示:

Car.h



Car.m



Person.h



Person.m



main.m



打印输出:


小结:

1>上面的程序就是我们在不考虑内存泄露问题时,我们一般会这样写程序,因为在前面我们就是这样写的

2>我们从打印输出可以看出Person、Car的引用计数器是1,所以说明这样写程序造成了“内存泄露”


我们已经知道不能让程序的内存泄露,所以下面我们将讨论如何管理“多个对象的内存管理

实例程序演示如下:

Car.h



Car.m



Person.h



Person.m



main.m



打印输出:



小结:

从打印输出可以看出:程序没有造成内存泄露


但是该程序并不完善,是有缺陷的,比如改一下程序就会造成“内存泄露

Car、Person类不变

main.m改变如下:



打印输出:


小结:

上面的程序是脆弱的,当我们在[car  release]之后增加调用driver方法后,这里程序就造成了内存泄露,因为WR没有释放,所以我们在WR释放前调用该对象方法是合理的,只能说该程序太“脆弱”


分析上面的问题:

上面程序的脆弱就是因为在调用driver方法的时候,Car已经销毁了,所以:程序最好是在Person没有销毁之前Car一直存在,也就是Car的引用计数器CarRetainCount=1


下面通过实例程序来演示

Car.h



Car.m



Person.h



Person.m



main.m




打印输出:


小结:

1>在我们的main.m中还是遵守内存管理的原则:

谁创建,谁release

2>这次只要Person没有销毁前可以任意的调用 driver方法,并且没有造成内存泄露


五、set方法内存管理


1、set方法存在的问题

1)set方法写法(一)

原对象无法释放造成的内存泄露

2)set方法写法(二)

原对象讷讷公狗释放,但是引起新的问题,set自己的时候,造成野指针

3)set方法写法(三)

判断新传入的对象是否是原来的对象,如果不是原来的对象则 释放,然后再retain


下面通过实例程序来演示

1)set方法写法(一)

原对象无法释放造成的内存泄露

对于第一个set方法写法的解决方法在上面程序中已经解决了


小结:

上面的程序也只适合给人传送一次车,但给人多次传送时,必定造成内存泄漏

2)set方法写法(二)

原对象能够释放,但是引起新的问题,set自己的时候,造成野指针


下面通过实例程序演示

Car.h



Car.m



Person.h



Person.m



main.m




打印输出:



小结:

1>从这个打印输出来看,发现BMW的引用计数器等于1,直到最后也没有变为0,所以造成了内存泄露

2>在main.m中依然遵守内存管理原则,谁创建,谁release


下面我们通过修改程序来改进

分析:当我们第二次调用 setCar方法时,让前一个传来的car销毁,然后让新传来的car  retain就可以了

Car.h



Car.m



Person.h



Person.m



main.m




打印输出



其实这个程序还是有些“脆弱”,当程序第二次传入的car还是第一传入的car时,就会报错

下面通过实例演示来体验一下:

Car、Person类中的程序和上面的一样

main.m



打印输出


小结:

1>从这个打印输出来看,程序报错了,而此程序和上一个程序例子的不同仅仅是因为本程序第二次调用的setCar方法是传入的car和第一次传入的car是一样的。

2>分析报错的原因


总结:因为我们二次调用setCar方法所传入的car是相同的BMW

(1)当第二次调用setCar方法在[BMW release]前面,那么不会有任何问题

原因:当我们新建BMW时BMWCount=1,

  当第一次调用setCar方法时

-(void)setCar:(Car *)car

{

    

        [_carrelease]; 此时的_car=nil,所以[_car release]

等于[nil release]

       _car=[carretain];此时_car=[BMW retain]

  BMWCount=2

    

}

当第二次调用setCar方法时

-(void)setCar:(Car *)car

{

    

        [_car release]; 此时的_car=BMW,所以[_car release]

等于[BMW release],BMWCount=1

        _car=[car retain];此时_car=[BMW retain]

 BMWCount=2

    

}

当执行[BMW release]后BMWCount=1

当执行[WR release]后BMWCount=0

所以不会造成内存泄露

(2)当第二次调用setCar方法在[BMW release]后面,就会发生报错

原因:当我们新建BMW时BMWCount=1,

  当第一次调用setCar方法时

-(void)setCar:(Car *)car

{

    

        [_car release]; 此时的_car=nil,所以[_car release]

等于[nil release]

        _car=[car retain];此时_car=[BMW retain]

  BMWCount=2

    

}


当执行[BMW release]后BMWCount=1


当第二次调用setCar方法时

-(void)setCar:(Car *)car

{

    

        [_car release]; 此时的_car=BMW,所以[_car release]

等于[BMW release],BMWCount=0

        _car=[car retain];此时_car=[BMW retain]就是使用

僵尸对象,所以会  报错    

}


下面我们通过分析来改进程序:

报错原因就是因为我们二次传入的对象都是同一个对象而造成的,假如程序检查到是同一个对象时就不再去执行

[_carrelease];

_car=[carretain];

就不会造成使用僵尸对象的错误


下面我们通过实例程序来演示:

Car.h



Car.m



Person.h



Person.m



main.m




打印输出:



小结:

从打印输出来看Person、Car类都销毁了,没有造成内存泄露,

说明这样的set写法是没有问题的


3、set方法的内存管理

1)基本数据类型格式:直接赋值

int float doublelongstructenum

-(void)setAge:(int)age

{

    _age=age;

}


2)OC对象类型格式

-(void)setCar:(Car *)car

{

    //1.先判断是不是新传进来的对象

    //判断新传来的car是否和旧值(原来的)_car不同

   if(_car!=car)

    {

        //2.对旧对象做一次release

        //让旧值(原来的)_car release释放掉

        [_carrelease];//如没有旧对象,则没有影响

        

        //3、对新对象做一次retain

        //再让新传来来的car retain过后赋值给旧值_car

       _car=[carretain];

    }

}


3)set方法的内存管理 原则

1、如果在一个类中,有其他类的对象(关联关系)

2、set方法书写的时候,要遵守:

判断是否是同一个对象,release    旧值 , retain   新值









评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值