zoukankan      html  css  js  c++  java
  • 手动内存管理(MRC)

    object-C的内存管理和javascript的垃圾回收不一样,今天总结下手动内存管理,ARC的后边补上。

    1:基本铺垫

    oc采用引用计数来表示对象的状态,比如通过init创建出来的一个对象引用计数为1,引用计数为0时对象被废弃。如果想让它释放则对这个对象发送一条release消息,则引用计数-1,那怎么+1呢,给这个对象发送retain消息。

    对象操作 Object-C方法
    生成并持有对象 alloc/new/copy/mutableCopy方法
    持有对象 retain方法
    释放对象 release方法
    废弃对象 dealloc方法

    其中dealloc方法是对象内存被释放时执行的方法,可以对它进行重写。

    (1)新建一个对象其引用计数为1;

        Student * stu1 = [[Student alloc] init];   //1
        NSLog(@"%lu",[stu1 retainCount]);

    (2)发送一条retain消息,引用计数+1;

        [stu1 retain]; //+1 现在为:2 
        NSLog(@"%lu",[stu1 retainCount]);

    (3)发送一条release消息,引用计数-1;

        [stu1 release]; //-1   此时为1   
        NSLog(@"%lu",[stu1 retainCount]);

    (4)引用计数为0时,对象的内存被释放。注意:自己主动通过retainCount方法,获取不到0那个状态(向引用计数为1的对象发送release消息),此时对象已销毁,内存回收,执行dealloc方法(已重写);

        [stu1 release]; //-1,现在应该为0 
        NSLog(@"%lu",[stu1 retainCount]); //打印出来的是1

    (5)已经成为0的引用计数,不允许再释放;

        Student * stu1 = [[Student alloc] init]; //1
        [stu1 retain]; //2
        [stu1 release]; //1
        [stu1 release]; //0
        NSLog(@"%lu",[stu1 retainCount]); //1
        [stu1 release]; //崩溃

    2:属性是自定义对象

    先祭上内存管理三个法则:

      一:new、alloc、copy均看成1(+1),此时意味着创建者拥有这个对象,创建者应该将其释放(-1);

      二:谁+1,谁-1,要保证+1和-1的操作是平等的。(retain和release的次数保持平等)

    如果一个对象a是另外一个对象b的实例变量,则成为b拥有a对象。接下来看下属性(或成员变量)是自定义对象的内存管理。

    创建两个类Student , Book 。Student中有一个成员变量-Book对象。book中有一个方法read,read打印book对象,因是成员变量不是属性,所以重写了getter和setter方法,看一下book对象的内存管理。

    文件结构:

    Student.h文件:

    复制代码
    #import "Book.h"
    @interface Student : NSObject
    {
        Book * book;
    }
    - (void)setBook:(Book *)abook;
    - (Book *)book;
    - (void) read;
    @end
    复制代码

    Student.m文件:

    复制代码
    @implementation Student
    - (void)setBook:(Book *)abook
    {
        self->book = abook;
    }
    - (Book *)book
    {
        return book;
    }
    - (void)read
    {
        NSLog(@"我正在读书:%@",self->book);
    }
    @end
    复制代码

    main函数:

    复制代码
        Student * stu1 = [Student new];
        Student * stu2 = [Student new];
        Book * book1 = [Book new];  //1
        
        [stu1 setBook:book1];
        [stu2 setBook:book1];
        
        [stu1 read];
        [book1 release];  //0
        [stu2 read];      
    复制代码

    上面的写法中stu1 read之后就把book1释放了,这样stu2再去掉read方法的时候,book1已经没有了。可能有崩溃的情况发生(book1已经被释放,可是内存不一定被覆盖,如果被覆盖则会崩溃),这样写肯定不行。这样就不能防止意外释放的情况发生。那该怎么办呢?

    在read方法中写book1 retain?这只是一个方法,如果有很多个方法呢?

    在stu1或者stu2调用read方法的时候,最好能保证book1的引用计数最少为1,不用的时候再把多出来的+1减去。这样多出来的这个1。不管stu1或者stu2在掉read方法的时候book1的引用计数最少为1.那这个引用计数+1操作应该放到哪呢?放到设置值的setter方法中。先让book+1,这样的的话,stu1,stu2在setBook的时候book1引用计数都是+1.即使book1 release放到了stu1 read或stu2 read之前都没有关系,因为stu1和stu2每个对象上的book引用计数有额外的1:

    @implementation Student
    - (void)setBook:(Book *)abook
    {
        [abook retain];// +1
        self->book = abook;
    }

    这样的话,book1的release放哪都没有问题了:

    复制代码
        Student * stu1 = [Student new];
        Student * stu2 = [Student new];
        Book * book1 = [Book new];  //1
        [stu1 setBook:book1];
        [stu2 setBook:book1];
        
        [book1 release];
        
        [stu1 read];
        [stu2 read];
    复制代码

    可是这样也产生了一个问题,即stu1和stu2每个对象上的book1的引用计数多了1。怎么办?按照内存管理原则:谁创建谁释放,setter中的book1并不是在main函数中+1,所以并不应该在main中执行release操作。那么这么说难道是在setter中执行release方法?像这样?

    - (void)setBook:(Book *)abook
    {
        [abook retain];
        self->book = abook;
        [abook release];
    }

    显然这么做没有意义,那该怎么办呢?每个对象都有一个dealloc方法,在对象被释放的时候会执行。所以可以把book的release操作放在对象的dealloc方法中。这样当Student对象:stu1或stu2释放的时候,它自身的属性也就没有了存在的必要。这样stu1或stu2上book多的1可以减掉。此时释放很合适。OK,问题解决!=。=

    Student.m文件中重写dealloc:

    复制代码
    - (void)dealloc
    {
        //dealloc方法中,需要将那些通过setter曾经retain过的成员变量,在此要进行release。
        [self->book release];
        NSLog(@"atudent dealloc.....");
        [super dealloc];
    }
    @end
    复制代码

    NSLog(@"***************我是分割线*********************")

    3:多对象问题

    上面的问题似乎解决的很好,接下来看一下情况:

    (1)第一种情况:只有一个学生对象,但是有两个书对象:

    复制代码
        Student * stu1 = [Student new];
        Book * book1 = [Book new]; //1
        [stu1 setBook:book1]; //2
        [book1 release]; //1
        [stu1 read];
        //现在看第二本书
        Book * book2 = [Book new]; //1
        [stu1 setBook:book2]; //2
        [book2 release]; //1
        [stu1 read];
        [stu1 release]; //book2:0,book1:1
    复制代码

    现在两个书本对象经过setter方法,引用计数都是+1,在stu1释放前引用计数都为1,可是stu1的dealloc方法中只是把当前它上边的属性release掉,而stu1当前的属性为book2。也就是说只有book2正常释放了,而book1没有释放。那该怎么办?

    解决办法:改写setter

    - (void)setBook:(Book *)abook 
        { 
        [self->book release]; 
        self->book = [abook retain];   //这句=[abook retain]; self->book = abook;
        }  

    这个时候让旧的属性release,然后让新的属性retain。以上边的为例:当book1负值的时候,book1成员变量为空,所以[self->book1 release]不执行。然后book1上赋值并且retain,引用计数+1,和之前一样,最后遗留引用计数1。

    当book2走setter方法的时候,此时book1上有book1这个对象,那么第一条语句就会执行,那么book1的引用计数就会-1。下一条语句book1的成员变量的指针指向book2那块内存。完成赋值并且retain。引用计数+1.和先前一样。最后也可以正常释放。book1由于引用计数-1所以也能将遗留的1减去。最后也能正常释放。=。=问题解决

    (2)第二种情况:还是那本书。(还是同样的对象,只是用了不同的指针去指向它)

    复制代码
        Student * stu1 = [Student new]; 
        Book * book1 = [Book new]; //1 
        [stu1 setBook:book1]; //2 
        [book1 release]; //1 
        [stu1 read]; 
        //现在书坏了,修理下(用新的指针指向这本书)
        Book * bookOld = book1; //还是1 
        [stu1 setBook:bookOld]; //为0了 
        [stu1 read]; 
        [stu1 release];
    复制代码

    那么现在的情况用之前setter的写法已然不合适,这样在设置bookOld的时候,先将老的release,再将新的retain。可是这里新的老的是同一个对象,将老的release掉之后,引用计数为0了,被释放。不合适。现在我们希望新的对象还是原来的那样写法,但是如果是老对象,我们不希望它的引用计数发生改变。那么现在需要做判断:

    1:普通写法

    复制代码
    - (void)setBook:(Book *)abook
    {
        if(self->book != abook){
                [self->book release];
                self->book = [abook retain];
        }
    }
    复制代码

    2:文艺写法:

    - (void)setBook:(Book *)abook
    {
        [abook retain];//先将新的retain //2
        [self->book release];//将旧的释放 //1
        self->book = abook;//将新的赋给成员变量,不需要再retain
    }

    这样的引用计数就可以正常释放了,如果是新对象则和第一种情况一样执行旧的release,新对象retain。如果还是老的对象,则还是同一块内存,则新的retain,而第二句老的release,而老的还是它,所以抵消。引用计数不发生改变,而这也是我们想要的。

  • 相关阅读:
    洛谷 P4297 [NOI2006]网络收费
    bzoj 5072: [Lydsy1710月赛]小A的树
    树形背包
    loj #2071. 「JSOI2016」最佳团体
    bzoj 2427: [HAOI2010]软件安装
    bzoj 4987: Tree
    loj #2007. 「SCOI2015」国旗计划
    loj #2006. 「SCOI2015」小凸玩矩阵
    loj #2020. 「AHOI / HNOI2017」礼物
    loj #547. 「LibreOJ β Round #7」匹配字符串
  • 原文地址:https://www.cnblogs.com/liuyingjie/p/4949119.html
Copyright © 2011-2022 走看看