浅谈内存管理及僵尸对象

作者: 指尖猿 | 来源:发表于2016-08-01 11:34 被阅读120次

    //--------------------内存管理

    内存管理范围:

    管理任何继承NSObject的对象,基本数据类型不需要内存管理。

    对象类型是程序运行过程中动态分配的,存储在堆区,内存管理主要是对"堆区中的对象"进行内存管理。

    //--------------------概念

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

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

    2)对象的引用计数器

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

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

    3)引用计数器的作用

    判断对象要不要回收的唯一依据

    (存在一种例外:对象值为nil时,引用计数为0,但不回收空间)就是计数器是否为0,若不为0则存在。

    //----------------------对引用计数器的操作3个方法

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

    retain消息:使计数器+1

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

    retainCount消息:获得对象当前的引用计数器值rc

    //--------------------------对象的销毁

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

    //1.对象销毁就自动执行dealloc方法

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

    //2.重写dealloc方法

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

    //3.对象销毁了存储空间不可用

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

    #注意:

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

    2)任何一个对象,刚生下来的时候,引用计数器都为1。(对象一旦创建好,默认引用计数器就是1  )

    3)当使用"alloc"、"new"或者"copy"("mutablecopy")创建一个对象时,对象的引用计数器默认就是1 ;

    对应的方法:

    -retain         +1

    -release        -1

    -retainCount当前引用技数为多少0 --->系统释放这个对象

    -dealloc

    #warning开放如何使用:需要理解MRC,但实际使用时尽量用ARC

    //------------------------MRC

    target   Build Settings   Basic   Leveis搜索auto-->NO

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

    问题:

    1.对象创建完之后默认引用计数是多少?      1

    2.当对象的引用计数为多少?     0系统就会回收这个对象的空间

    3.怎么知道这个对象空间被释放了?

    重写dealloc方法在dealloc方法中打印以下信息

    -------------------关于dealloc方法

    代码规范:

    (1)一定要[superdealloc],而且要放到最后,意义是:"先释放子类占用的空间再释放父类占用的空间"

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

    -(void)dealloc

    {

    //先释放子类拥有的对象属性

    [superdealloc];

    }

    注意:

    永远不要直接通过对象调用dealloc方法(对象的引用计数为0系统会自动调用此方法)

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

    原则

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

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

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

    记住两点:

    1,谁创建"alloc","new",谁"release";

    2,谁"retain",谁"release";

    正确的手动管理操作:

    一个alloc/new对应一个release

    一个retain对应一个release

    总结

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

    //---------------------僵尸对象与内存泄露

    区分以下2个概念:

    1)"僵尸对象"(野指针)(没有初始化的指针变量;指向的内存空间已经被释放的指针变量)

    栈区,系统会自动管理

    //空指针:没有指向任何东西的指针,给空指针发送消息不会报错对象= nil

    2)"内存泄露"(栈区的指向已经释放,堆区的空间没有释放,这时堆区的空间就被泄露了)

    堆区,需要程序员手动管理

    #不管是多个对象还是单个对象,只要我们研究它的内存管理,就两点:

    #1.僵尸对象(野指针)

    #2.内存泄露

    //---------------------------关于僵尸对象问题

    首先什么是僵尸对象:

    "僵尸对象"(野指针)(没有初始化的指针变量;指向的内存空间已经被释放的指针变量)

    栈区,系统会自动管理

    #检测野指针

    (检测僵尸对象,比较耗费性能,所以Xcode默认是不检测的)

    打开方法:

    Edit Scheme  -->  Run Debug   --->  Diagnostics  -->  Enable Zombie Objects(打勾)

    对象一旦成为了僵尸对象,就不能再使用这个僵尸对象了

    #判断是否是僵尸对象:

    person(对象)  -->看什么?

    看是否执行了dealloc方法--->重写dealloc方法打印一句话提醒

    dealloc方法一旦调用了,意味着堆区的空间已经被释放,释放就意味着这个对象是僵尸对象

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

    1)僵尸对象调用方法,会报错,访问成员变量有时是可以的(但是这个是未知的)。

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

    用nil调用方法是不会报错的。

    #关键:

    3)总而言之要合理使用release和retain

    //-----------------nil和Nil及NULL、NSNull的区别:

    了解:

    1.nil对象的值person =nil;

    2.Nil类对象的值

    3.NULLC语言的关键字通用空指针

    4.  NSNull空对象用在不能使用nil的地方

    //---------------------------关于内存泄露问题

    首先:

    什么是内存泄露:

    "内存泄露"(栈区的指向已经释放,堆区的空间没有释放,这时堆区的空间就被泄露了)

    堆区,需要程序员手动管理

    #对象的内存泄露的几种情况

    1) retain和release不匹配,retain多余release导致的内存泄露;

    2)对象使用过程中,没有被release,而被赋值为nil;

    3)在方法中不当的使用了retain;

    #关键:

    3)总而言之要合理使用release和retain

    还是记住两点:

    1.谁创建"alloc","new",谁"release";

    2.谁"retain",谁"release";

    内存管理的验证:

    1.查看retainCount的值

    2.重写dealloc方法,查看对象是否调用了dealloc方法

    /*

    一个alloc/new对应一个release

    一个retain对应一个release

    */

    //-----------------------多对象的内存管理

    #多个对象的僵尸对象问题

    假设对象A与对象B有(关联)关系,如果对象B独自销毁,会影响对象A的操作.

    {

    Car *_car;

    }

    假设:person拥有车

    {

    //创建一个人对象rc = 1

    Person *person = [Person new];

    //创建一个车对象rc = 1

    Car *car = [Car new];

    //让人拥有这辆车

    person.car = car;

    //人调用开车这个方法

    [person driver];

    [car release];

    #关键的地方在这里看这里!!

    #    [person driver]不能再调用这个方法人是拥有车的,人都还没释放,就不能调用与car有关系的driver方法是不科学的

    [person release];

    }

    #解决方案

    1.在人的实例变量_car的set方法中set方法中[_car retain];     +1

    2.在人的dealloc方法中先把自己拥有的对象先释放[_car release];  -1

    总结:

    凡事关联关系,对象A与对象B有(关联)关系,就应该在set方法中让关联的对象+1,然后在对象A的dealloc方法中让关联的对象-1.

    //------------------------------------------------

    #多个对象的内存泄露问题

    问题:

    就上面的解决方法,还是有弊端,会造成原对象泄露

    比如说人中途换车了多次调用了关于_car的set方法导致_car多次retain而没有相对应的release方法,导致rc没变为0,对象没有释放,就说内存泄露了

    1.人第"1"次使用set方法赋值第一次把byd赋值给人的_car

    2.人第"2"次使用set方法赋值第二次把byd赋值给人的_car

    3.人第"3"次使用set方法赋值第三次把bigben赋值给人的_car

    1.byd(首次赋值)  _car =nil_car != bydif执行[_car release]由于_car是nil[nilrelease]赋值赋值之后_car retain    byd+1

    2.byd(多次调用)  _car = byd  _car == bydif不执行

    3.bigBen(换车)  _car = byd  car = bigBen  _car != carif执行[_car release] --> [byd release]赋值赋值指针bigBen retain   bigBen + 1

    在dealloc方法中:

    如果只执行了第1步或者第1第2步则:

    [_car release];  _car (byd) release    -1

    如果执行了第1,2,3步则:

    [_car release];//  _car  ---> bigBen     -1

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

    if(_car != car){//  _car != car           _car实例变量!=    car参数

    [_car release];//先release旧值

    _car = car;//赋新值_car = [car retain];

    [_car retain];//再retain新值

    }

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

    }

    -(void)dealloc{

    //该对象在被释放之前,必须先把自己所拥有的对象释放,也就是release一次自己的实例变量(对象)

    [_car release];

    [superdealloc];

    }

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

    #基本数据类型: int float double long struct enum基本数据类型做实例变量,就正常写法

    -(void)setAge:(int)age{

    _age = age;

    }

    #对象类型:对于对象作为另一个类的实例变量

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

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

    if(_car != car){

    [_car release];//先release旧值

    _car = car;//赋新值

    [_car retain];//再retain新值

    }

    }

    //------------------------@property参数

    @property帮我们生成get和set方法的声明和实现

    #格式: @property (参数1,参数2)数据类型实例变量名(记住:此处不要带下划线)

    #原子性:   (习惯不加锁)

    "atomic":默认值,对属性加锁,多线程下线程安全

    "nonatomic":对属性不加锁,多线程下不安全,但速度快

    #读写属性: (用得不多)

    "readwrite":默认值,生成getter,setter方法.

    "readonly":只生成getter方法

    #  setter方法的处理

    1)基本数据类型或者C语言的构造类型:直接赋值

    intfloatdoublelongstructenum等BOOL/ Boolean

    "assign":@property的默认值,直接赋值

    //如果使用assign  set方法就会生成如下代码

    -(void)setAge:(int)age{

    _age = age;

    }

    2)OC对象类型

    "retain":先release原来的值,再retain新值

    "copy":先release原来的值,再copy新值(NSString *)

    //如果使用retain  set方法就会生成如下代码

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

    if(_car != car){

    [_car release];

    _car = [car retain];

    }

    }

    练习

    {

    Car *_car;

    int_age;

    enumSex _sex;

    Dog *_dog;

    NSString *_name;

    }

    @property(retain,nonatomic) Car *car;

    @property(nonatomic,assign)intage;

    @property(nonatomic,assign)enumSex sex;

    @property(nonatomic,retain) Dog *dog;

    @property(nonatomic,copy) NSString *name;

    @property(nonatomic,assign,setter= setIsVip:,getter= haha)BOOLvip;

    原方法名

    -(void)setVip:(BOOL)vip;

    -(BOOL)vip;

    setter = setIsVip:,getter = haha

    // setter=setIsVip:

    // getter=haha方法名改了

    如果使用中括号调用方法,要必须把方法名写正确了

    如果使用点语法,就可以.实例变量名

    相关文章

      网友评论

        本文标题:浅谈内存管理及僵尸对象

        本文链接:https://www.haomeiwen.com/subject/damxsttx.html