IOs内存管理(二)

来源:互联网 发布:java构造方法语法 编辑:程序博客网 时间:2024/05/17 03:43


一 . MRC 序


1.  举例在MRC中实现set get 方法(assign状态下,retain状态下,copy状态下)


比如有一个引擎类Engine,有一个汽车类CarCar里面有一个Engine的实例变量,一个settergetter方法。具体如下

[plain] view plaincopy

  1. #import "Car.h"    
  2. @implementation Car    
  3. -(void)setEngine:(Engine*) engine  
  4. {  
  5.      _engine=engine;  
  6. }  
  7. -(Engine*)engine  
  8. {  
  9.     return _engine;  
  10. }  
  11.   
  12. -(void)dealloc  
  13. {  
  14.     NSLog(@"Car is dealloc");                     
  15.     [super dealloc];  
  16. @end  

上面写的是一个简单的类,当让这样写是有问题,所以需要一步步的改进。

第一步改进:

先使用它看问题的所在,main方法里面如下使用:

[plain] view plaincopy

  1. //先创建一个引擎  
  2. Engine* engine1=[[Engine alloc]init];  
  3. [engine1 setID:1];  
  4. //在创建一个汽车,设置汽车的引擎  
  5. Car* car=[[Car alloc]init];//retainCount=1  
  6. [car setEngine:engine1];  
  7. /*分析:在这里,现在有两个引用指向这个Engine对象,engine1Car中的_engine,可是这个Engine对象的引用计数还为1,因为在  
  8.  set方法中,并没有使用retain。那么不管是哪个引用调用release,那么另外一个引用都会指向一块释放掉的内存,那么肯定  
  9.  会发生错误。所以需要在set方法中加以改进。*/  
  10. 第二步改进:

setter方法改进

[plain] view plaincopy

  1. -(void)setEngine:(Engine*) engine  
  2. {  
  3.      _engine=[engine retain];//多了一个引用,retainCount+1  
  4. }  

再在main中使用它

[plain] view plaincopy


  1.  //先创建一个引擎  
  2.  Engine* engine1=[[Engine alloc]init];  
  3.  [engine1 setID:1];  
  4.  //在创建一个汽车,设置汽车的引擎  
  5.  Car* car=[[Car alloc]init];//retainCount=1  
  6.  [car setEngine:engine1];//retainCount=2,因为使用了retain,所以retainCount=2  




  1.  //假设还有一个引擎  
  2.  Engine* engine2=[[Engine alloc]init];  
  3.  [engine2 setID:2];  
  4.    
  5.  //这个汽车要换一个引擎,自然又要调用settr方法         
  6.   [car setEngine:engine2];  
  7.     
  8.  /*分析:在这里,汽车换了一个引擎,那么它的_engine就不在指向engine1的哪个对象的内存了,而是换成了engine2,也就是说engine1的哪个对象指向的内存的引用只有一个  
  9. 可是它的retainCount是两个,这就是问题的所在了。所以仍然需要改进*/  


第三步改进:

[plain] view plaincopy

  1. -(void)setEngine:(Engine*) engine  
  2. {  
  3.      [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release  
  4.     _engine=[engine retain];//多了一个引用,retainCount+1  
  5. }  

再在main'中使用

[plain] view plaincopy


  1. //先创建一个引擎  
  2. Engine* engine1=[[Engine alloc]init];  
  3. [engine1 setID:1];  
  4. //在创建一个汽车,设置汽车的引擎  
  5. Car* car=[[Car alloc]init];//retainCount=1  
  6. [car setEngine:engine1];//retainCount=2,因为使用了retain,所以retainCount=2  
  7.   
  8. //如果进行了一个误操作,又设置了一次engine1       
  9.  [car setEngine:engine1];  
  10.    
  11. /*分析:那么,又要重新调用一次setter方法,这根本就是无意义的操作,浪费资源,所以要在设置之间加上判断*/  



第四步改进:

[plain] view plaincopy

  1. -(void)setEngine:(Engine*) engine  
  2. {  
  3.    if(_engine!=engine){//判断是否重复设置  
  4.            [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release  
  5.            _engine=[engine retain];//多了一个引用,retainCount+1  
  6.       }  
  7.  }  


第五步:

现在setter方法基本没有问题了,那么在当我们要释放掉一个car对象的时候,必须也要释放它里面的_engine的引用,所以,要重写cardealloc方法。

[plain] view plaincopy


  1. -(void)dealloc  
  2. {  
  3.     [_engine release]; //在释放car的时候,释放掉它对engine的引用  
  4.     [super dealloc];  
  5. }  


这还不是最好的释放的方法,下面的方法更好

[plain] view plaincopy

  1. -(void)dealloc  
  2. {  
  3.     [_engine setEngine:nil]; //在释放car的时候,对setEngine设置为nil,它不仅会release掉,并且指向nil,即使误操作调用也不会出错。  
  4.     [super dealloc];  
  5. }  

所以,综上所述,在setter方法中的最终写法是

[plain] view plaincopy

  1. <span style="color:#CC66CC;">-(void)setEngine:(Engine*) engine  
  2. {  
  3.    if(_engine!=engine){//判断是否重复设置  
  4.            [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release  
  5.            _engine=[engine retain];//多了一个引用,retainCount+1  
  6.       }  
  7.  }</span>  


然后在dealloc方法中写法是:

[plain] view plaincopy


  1. <span style="color:#CC66CC;">-(void)dealloc  
  2. {  
  3.     [_engine setEngine:nil]; //在释放car的时候,对setEngine设置为nil,它不仅会release掉,并且指向nil,即使误操作调用也不会出错。  
  4.     [super dealloc];  
  5. }</span>  

六、property中的setter语法关键字

property属性中有三个关键字定义关于展开setter方法中的语法,assgin(缺省)retaincopy。当然这三个关键字是互斥的。

1assgin展开stter的写法

[plain] view plaincopy

  1. -(void)setEngine:(Engine*) engine  
  2. {  
  3.      _engine=engine;  
  4. }  

2retain展开的写法

[plain] view plaincopy

  1. -(void)setEngine:(Engine*) engine  
  2. {  
  3.    if(_engine!=engine){//判断是否重复设置  
  4.            [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release  
  5.            _engine=[engine retain];//多了一个引用,retainCount+1  
  6.       }  
  7.  }  


3copy展开的写法

[plain] view plaincopy


  1. -(void)setEngine:(Engine*) engine  
  2. {  
  3.    if(_engine!=engine){//判断是否重复设置  
  4.            [_engine release];//在设置之前,先release,那么在设置的时候,就会自动将前面的一个引用release  
  5.            _engine=[engine copy];//多了一个引用,retainCount+1  
  6.       }  
  7.  }  

对于copy属性有一点要主要,被定义有copy属性的对象必须要符合NSCopying协议,并且你还必须实现了-(id)copyWithZone:(NSZone*)zone该方法。

可以看到,使用retain和我们上面举得例子完全相同,所以我们可以使用property和它的retain代替之前的写法。

#import <Foundation/Foundation.h>

#import "Engine.h"

@interface Car : NSObject{

    Engine * _engine;

}

@property (nonatomic,retain)Engine * engine;

@end

#import "Car.h"

@implementation Car

@synthesize engine = _engine;

//这个是retain展开的setter方法

- (void)setEngine:(Engine *)engine{

    if(_engine != engine){

        [_enginerelease]; //这里不用了要加上release

                 _engine = [engineretain];

        NSLog(@"_engine----%@",_engine);

    }

    //这里传递进来的engine的地址就不同了

    //赋值方法执行了两次_engine----<Engine: 0x100203ac0>

    //第二次  _engine----<Engine: 0x1001023e0

}

/*

 添加完release后的

 2015-12-15 10:53:17.573 MRCDeom[6560:380091] _engine----<Engine: 0x100400150>

 2015-12-15 10:53:17.575 MRCDeom[6560:380091] Car--retainCount--1---<Car: 0x100400160>

 2015-12-15 10:53:17.576 MRCDeom[6560:380091] engine1----retainCount-2----<Engine: 0x100400150>

 2015-12-15 10:53:19.776 MRCDeom[6560:380091] _engine----<Engine: 0x1002006b0>

 2015-12-15 10:53:19.777 MRCDeom[6560:380091] Car--retainCount--1---<Car: 0x100400160>

 2015-12-15 10:53:19.777 MRCDeom[6560:380091] engine1----retainCount-1----<Engine: 0x100400150>

 2015-12-15 10:53:22.592 MRCDeom[6560:380091] engine2---retainCount-2----<Engine: 0x1002006b0>

 */

- (Engine * )engine{

    return_engine

}

- (void)dealloc{

    [_enginerelease];

    [superdealloc];

}

@end

#import <Foundation/Foundation.h>

#import "testDemo.h"

#import "Car.h"

#import "Engine.h"

int main(int argc,const char * argv[]) {

    @autoreleasepool {

        Engine * engine1 = [[Enginealloc]init];

        Car * car1 = [[Caralloc]init];

        [car1 setEngine:engine1];

  NSLog(@"Car--retainCount--%ld---%@",car1.retainCount,car1);//Car--retainCount--1---<Car: 0x100605b50>

 NSLog(@"engine1----retainCount-%ld----%@",engine1.retainCount,engine1);//engine1----retainCount-2----<Engine: 0x100603a20>

        Engine * engin2 = [[Enginealloc]init];

        [car1 setEngine:engin2];

NSLog(@"Car--retainCount--%ld---%@",car1.retainCount,car1);//Car--retainCount--1---<Car: 0x100605b50>

NSLog(@"engine1----retainCount-%ld----%@",engine1.retainCount,engine1);//engine1----retainCount-2----<Engine: 0x100603a20>

 NSLog(@"engine2---retainCount-%ld----%@",engin2.retainCount,engin2);//engine2---retainCount-2----<Engine: 0x100200090>

   }

    //当超出了作用域后,test超出了作用域,强引用失效了。自动的释放了testDemo对象。

    //testDemo对象的所有者不存在了,因此废弃该对象。这个对象的obj_成员变量也就被废弃了。

    return0;

}


(2)@property 参数

    2.1)  控制set方法的内存管理

    retain:release掉旧值,retain 新值(OC对象)

    assign:直接赋值,不做任何内存管理(默认的,用于非OC对象)

    copy:release旧值,copy新值(一般用于NSString *)


    2.2) 控制需不需要生成set方法

    readwrite:同时生成set方法和get方法(默认)

   readonly:  只会生成get方法


   2.3)多线程管理

   atomic 和 nonatomic用来决定编译生成的getter和setter是否为原子操作。

   atomic  :性能低。线程安全的,多线程环境下,原子操作是必须要的。

   nonatomic:性能高(iOS开发中使用)禁止多线程,变量保护,提高性能


  2.4)区分assign,retain,copy

assign

    对基础数据类型(NSInteger,CGFloat)和 C数据类型 (int,float,double,char);简单的赋值,不更改索引计数。

 retain

    释放掉旧的对象,将旧对象的值赋予输入对象,在提高输入对象的索引计数为1.

 copy

    建立一个相同的对象,新建立的对象的索引计数为1.然后释放掉就对象;

retain是指针拷贝,copy是内容拷贝,在拷贝前都会释放旧对象;


从网上找到了一些资料,觉得讲的有道理

   copyretain

Copy其实是建立了一个相同的对象,而retain不是:

  1.比如一个NSString对象,地址为0×1111 ,内容为@”STR”Copy到另外一个NSString 之后,地址为0×2222,内容相同。

  2.新的对象retain,旧有对象没有变化retain到另外一个NSString 之后,地址相同(建立一个指针,指针拷贝),内容当然相同,这个对象的retain+1

总结:retain 是指针拷贝,copy是内容拷贝。


assignretain

    1. 接触过C,那么假设你用malloc分配了一块内存,并且把它的地址赋值给了指针a,后来你希望指针b也共享这块内存,于是你又把a赋值给(assign)了b。此时ab指向同一块内存,请问当a不再需要这块内存,能否直接释放它?答案是否定的,因为a并不知道b是否还在使用这块内存,如果a释放了,那么b在使用这块内存的时候会引起程序crash掉。


    2.
了解到1assign的问题,那么如何解决?最简单的一个方法就是使用引用计数(reference counting),还是上面的那个例子,我们给那块内存设一个引用计数,当内存被分配并且赋值给a时,引用计数是1。当把a赋值给b时引用计数增加到2。这时如果a不再使用这块内存,它只需要把引用计数减1,表明自己不再拥有这块内存。b不再使用这块内存时也把引用计数减1。当引用计数变为0的时候,代表该内存不再被任何指针所引用,系统可以把它直接释放掉。


总结:上面两点其实就是assignretain的区别,assign就是直接赋值,从而可能引起1中的问题,当数据为int, float等原生类型时,可以使用assignretain就如2中所述,使用了引用计数,retain引起引用计数加1, release引起引用计数减1,当引用计数为0时,dealloc函数被调用,内存被回收。


assign没有引用计数的概念,retain有引用计数的概念;

      

二 . ARC 自动引用计数的内存管理

     1.ARC的注意点和优点

   (1)clang(LLVM)3.0或以上版本;(2)不允许调用对象的release方法;(3)不在重写dealloc方法;(4)ARC是编译器的特性,不是运行时特性;


     2.ARC状态下 追加了所有权声明

      OC编程中为了处理对象,可将变量类型定义为id,各种对象类型;


    (1)对象类型:指向NSObject这样的OC类的指针;

      (2) id类型是用于隐藏对象类型的类名,相当于C中的void *


     在ARC有效的时候,id类型和对象类型,与C语言类型不同,这些类型必须附加上所有权修饰符

__strong;  __weak;  __unsafe_unretained; __autoreleasing;


      (1.a)__strong  是id类型和对象类型默认的所有权修饰符

     ARC有效:

     id obj =  [[NSObject alloc] init];

     

     ARC无效:

   {

        id __strong  obj =  [[NSObject alloc] init];

        [obj release];

    }

以上两种状态是等效的:__strong 修饰符的变量obj在超出变量作用域时,即在该变量被废弃时,会释放其被赋予的对象。 __strong修饰符表示对对象的“强引用”持有强引用的变量在超出其作用域时被废弃,随着强引用的失效,引用的对象会随之释放。


ARC状态下的思路:

(1)自己生成的对象,自己持有;

(2)非自己生成的对象,自己也能持有;使用了默认的强引用,可以把非自己生成的对象变成是自己的持有的。


对象所有者和对象的生存周期也是固定的(在变量的作用内,在变量赋值中都是有效的)

   eg:

 id objA = [[NSObject alloc] init];  A

 id objB = [[NSObject alloc] init];  B

 id objC = nil;

//此时  objA持有A对象; objB持有B对象;objC不持有对象;

objA = objB

//此时 objB把B赋值给了objA 此时objA持有B对象,则对A对象的强引用就失效了。A对象被废弃了;

objC = objA;

//此时objC获取了B的强引用;


注意:__strong, __weak, __autoreleasing 着几个修饰符可以保证将附有这些修饰符的自动变量初始化为nil;


总述:“自己生成的对象,自己持有”和“非自己生成的对象,自己也能持有”只需通过对带strong修饰符的变量赋值便可以达到。通过废弃带strong修饰符的变量或者对变量赋值,都可以做到“不再需要自己持有的对象时释放”。 “非自己持有的对象无法释放”由于不必再键入release,所以原本就不会执行。


(1.b)weak 修饰符

strong看似很完美,但是却无法解决引用计数式内存管理中必然会发生的“循环引用”的问题。


id test0 = [[Test alloc] init];  A

//test0持有 Test对象A的强引用;

id test1 = [[Test alloc] init]; B

//test1持有 Test对象B的强引用;

[test0 setObject:test1];

//test对象A的obj成员变量只有Test对象B的强引用;持有B的强人用的变量:Test 对象A的obj和test1;

[test1 setObject:test0];

//test对象B的obj成员变量只有Test对象A的强引用;持有A的强人用的变量:Test 对象B的obj和test0;


test0超出作用域后,强引用失效,所以自动释放Test对象A;

test1超出作用域后 ,强引用失效,所以自动释放Test对象B;

此时,持有Test对象A的强引用的变量为 Test 对象B的obj;

持有Test对象B的强引用的变量为 Test 对象A的obj;

就发生了内存的泄露。循环引用容易发生内存泄露,所谓内存泄露就是应当废弃的对象在超出其生存周期后继续存在。


解决的方案:weak修饰符,弱引用不能持有对象实例。

id __weak obj = [[NSObject allic]init];

这是为了不以自己持有的状态来保存自己生成并持有的对象,生成的对象会被立即释放掉。

id __strong obj = [[NSObject allic]init];

id __weak obj1 = obj;

obj0  变量超出作用域,强引用失效。

weak修饰符还有一个优点:在持有某对象的弱引用时,若该对象被废弃了,则次弱引用将自动失效,且处于nil(空引用)


(1.c) __unsafe_unretained修饰符

因为weak修饰符只能在ios5以上版本使用,iOS一下的可以使用__unsafe_unretained

__unsafe_unretained是不安全的所有权修饰符。尽管ARC式的内存管理是编译器的工作,但是__unsafe_unretained修饰的变量不属于编译器的内存管理对象。


__unsafe_unretained和weak一样,自己生成并持有的对象,不能继续为自己所有,所以生成的对象会立即释放掉。在使用__unsafe_unretained修饰符时,赋值给附有strong修饰符的变量时与必要确保被赋值的对象确实存在。


(1.d) __autoreleasing修饰符


在ARC有效时,autorelease无法直接使用。但是autorelease功能是起作用的。

@autoreleasepool块 来替代“NSAutoreleasePool类对象生成、持有以及废弃”

ARC有效时,要通过将对象赋值给附加了__autoreleasing修饰符的变量来替代调用autorelease方法。


(1)非显示的使用__autoreleasing修饰符也可以

a. 取得非自己生成并持有的对象时,既可以使用alloc,new,copy,mutableCopy以外的方法取得对象。但该对象已经被注册到autoreleasepool。这是因为编译器会检查方法名是否已alloc,new,copy,mutableCopy开始,如果不是就自动将返回值的对象注册到autoreleasepool。


+ (id) array{

    id obj = [[NSMutableArray alloc]init];

    return  obj;

}

这里也没有显示的指定所有权修饰符,但是return 使得变量超出其作用域,所以该强引用对应的自己持有的对象会被自动释放,作为函数的返回值,编译器会自动将其注册到autoreleasepool


(2)为什么在访问附有__weak 修饰符的变量时,必须访问注册到autoreleasepool的对象?

a. weak 修饰符只支持有对象的弱引用,而在访问引用对象的过程中,该对象有可能被废弃,如果把要访问的对象注册到autoreleasepool中,那么在@autoreleasepool块结束之前都能确保该对象存在。


继续学习吧。少年!


0 0
原创粉丝点击