码迷,mamicode.com
首页 > 其他好文 > 详细

object-C 手动内存管理(MRC)

时间:2015-11-05 22:23:05      阅读:222      评论:0      收藏:0      [点我收藏+]

标签:

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

1:基本铺垫

oc采用引用计数来表示对象的状态,比如通过init创建出来的一个对象引用计数为1,如果想让它释放则对这个对象发送一条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]);          //1

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

[stu1 retain];                     //2
NSLog(@"%lu",[stu1 retainCount]);  //2

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

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

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

[stu1 release];  //应该为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];   //崩溃
NSLog(@"%lu",[stu1 retainCount]);

2:自定义对象

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

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

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

以下是大致思路:

(1)创建两个类Student , Book 。Student中有一个成员变量-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];  //1
Book * book1 = [Book new];       //1
[stu1 setBook:book1];
    
[stu1 read];
    
[book1 release];                //0
[stu1 release];                 //0

上面的写法复合内存管理:谁创建,谁释放(在main中创建,在main中释放)、谁+1,谁-1。可是这么做很依赖书写顺序,比如book1 release方法在read方法之前,那么对象已经被释放就不能调read方法。这样就不能防止意外释放的情况发生。那怎么办呢?

咱们可以在read方法中对book1对象发送retain消息,防止book1对象被释放。

- (void)read
{
    [self->book retain];
    NSLog(@"我正在读书:%@",self->book1);
}

这样做可行,可是如果我有很多个方法呢?难道每个方法都要写一遍book release?

这时可以把book release方法写到Student初始化方法setter中。进行+1操作.(问题:可以写到student的init方法中吗?)

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

这样就可以解决book1对象被意外释放的问题,即使book1的release方法在read之前也没有关系,有多个属性也没关系,我都可以写到Student的setter方法中。

Student * stu1 = [Student new];  
Book * book1 = [Book new];         //1
[stu1 setBook:book1];              //2
    
[book1 release];                  //1
[stu1 read];                      //1
    
[stu1 release];

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

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

显然这么做没有意义,那该怎么办呢?

解决办法是book1的release方法在student的重写的dealloc方法中执行。(这样的设计很巧妙有木有?)当student释放的时候,它自身的属性也就没有了存在的必要。OK,问题解决!=。=

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

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];
}

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

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

 (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的写法已然不合适,:还是原来的那个对象执行下面语句则

[self->book release];                         执行到这里的时候,book1就为0了,不合适。

self->book = [abook retain];

现在如果还是老对象则希望它的引用计数不发生改变

那么现在需要做判断:

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,而老的还是它,所以抵消。引用计数不发生改变,而这也是我们想要的。

 

O了。。。。。。。。。。。。。。。。。

 

 

 

 

 

object-C 手动内存管理(MRC)

标签:

原文地址:http://www.cnblogs.com/yingu/p/4940214.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!