Objective-c内存管理

来源:互联网 发布:梁山传奇翅膀进阶数据 编辑:程序博客网 时间:2024/06/08 08:32
1:内存管理方式 引用计数
2:关键字 release retain retainCount
首先来理解oc中的属性声明@property (nonatomic,retain)UIButton *button;

这样就生成了一个getter和setter方法,setter的方法大概如下

假设是一个MyClass中的一个方法

    -(void)setButton:(UIButton *)button{
//将传过来的值引用计数加一,注意这一行要放在第一行,因为,如果调用两次setButton并且传过来的button一样,则执行_button release的时候导致该对象被释放
         [button retain]; 
          //如果_button已经被赋值,则释放对原来值的引用(即引用计数减一)
          //_button是由oc的编译器生成的也可以使用@synthesize在.m文件中来制定这个名称(可选的) 
          [_button release];
          _button = button;
     }

在类中调用该方法

self.button = [ [UIButton alloc] init ]

这里使用了点语法执行的时候和发setButton消息是一样的

这个地方时存在有内存泄露的,因为[ [UIButton alloc] init ]生成了一个对象的指针,该指针指向的对象的在进行alloc的时候进行了retain操作,即:该指针指向的对象的retainCount属性,在alloc的时候也别初始化为1

在使用self.button进行赋值的时候,该指针指向的对象的又进行了一次retain操作

由于该属性声明成的是一个全局变量,所以一般该变量的释放操作,是在MyClass的dealloc中进行的,即在MyClass的dealloc重写方法中应该添加[ self.button release ]

所以当MyClass释放的时候该button的时候,button的引用计数依然是1,引起内存泄露

正确和规范的写法应该是在对self.button进行赋值操作的时候,先创建一个临时变量,然后再赋值之后释放该临时变量:

     UIButton *temp = [ [UIButton alloc] init];

     self.button = temp;

     [ temp release];


这样在MyClass释放的时候就可以成功的释放button;

个人认为以上的方法比较规范,也不容易出错,结构也比较清晰

也由其他的操作如:

可以直接调用默认自动生成的_button对象指针

 _button = [ [UIButton alloc] init ];

这样写之后对button的赋值是不会经过setter方法的,所以也不会对新生成的button对象retain,所以这时该button对象的引用计数还是1

但是这样做的时候有一个问题,如果button只被初始化一次,然后再MyClass的dealloc方法中使用[_button release]或者[self.button release]释放,是没有问题的

但是如果_button 在MyClass中重复的使用该方式赋值(有该语句的函数重复调用),就存在问题,以为在第二次使用_button=[ [UIButton alloc] init ]的时候,并没有对原来已经付给_button的指针指向的对象进行release操作

 所以可以这样写:

[ _button release];

_button = [ [UIButton alloc] init];

在oc中指针的初始化是nil,对nil进行release操作是允许的,但是对一个retainCount为1的对象两次release操作是错误的 

当然在MyClass的dealloc中也要对button进行release操作

_button = [ [UIButton alloc] ];

[self.view addSubview _button];

[ _button release ];

这样写也是合法的,这样写的话就不需要在MyClass的dealloc对button进行release操作,但是这样做的话应该考虑,该button是否应该作为一个全局的变量的存在

尽量不要使用全局变量,这样可以避免手动去进行retain和release操作

因为比如:[self.view addSubview _button]方法,在addSubview的时候,self.view会对button进行引用(具体的retainCount为多少由self.view来决定)在oc中addSubview方法一般继承自oc提供的UIView中

所以addSubview的具体操作我们是不需要去关心的,我们只需要知道addSubview之后self.view会对该button引用retain,在button从self.view中remove之后,会对button进行相应的release,具体多少次要看self.view

进行了多少次的retain,这就是经典的内存管理中的谁引用谁释放

当然这个地方也只是拿addSubview来举例说明:也可以看做是一个集合中添加了一个对象

一个集合中添加一个对象:

当向一个集合中添加一个对象的时候,该对象会被retain(引用计数加),当对集合执行release,remove操作的时候,集合中的所有对象或者一个对象的引用计数也会减(release)

所以当一个对象alloc之后添加到一个集合中去,之后临时就行又进行了release操作,该临时变量还是存在的,和addSubview方法操作对象一个性质

总之一个对象在执行retain之后要release,不管该release存在于那个地方,retain和release一定是相等的,否则就由内存泄露,一个对象在的引用计数为0的时候,该对象就会被释放调用dealloc方法

同样在UIViewController中,如果对controller进行retain而不release,该controller就不会调用dealloc方法,就会内存泄露,比如在controller中其他对象对self进行了引用而不释放

 如:一个UIViewController类型的对象controller,在controller有一个UITableView tableView,tableView中定义了一个委托UITableDelegate,该委托用来实现在controller中对tableView的用户事件进行具体的处理,

加入该委托由controller实现,则该委托指向的就是controller(self),这样就是一个典型的内存泄露 retain cycle,controller引用了tableVIew,tableView引用了controller,形成了一个循环,这样的循环是可以解决的,

比如可以在controller需要pop出UINavigationController的时候,让controller去通知tableView,让tableView去release这个委托,但是这不是一个好的设计,

正确的做法应该是:子对象不能引用父对象,如果子对象必须要使用到父对象,则建立一个弱引用(弱引用:就是可以使用一个对象,但不对该对象进行retain,如同_button = button一样,button本身的引用计数不变)

所以我们发现,在定义delegate的时候,属性的关键字通常是assign而不是retain,这样的赋值操作是只进行赋值操作,而不去改变等号右边对象的引用计数,即弱引用,

块block引起的内存泄露

在oc中又一个block的功能,经常用来进行异步回调

经典的内存泄露 retain cycle 

request中又一个CompletionBlock类型的block

在执行setCompletionBlock的时候,oc对该快进行拷贝,并赋给类型为CompletionBlock类型的成员

block进行了拷贝,block中的成员引用加一

 block引用了requestrequest持有blockblockrequest循环引用,不能释放  由于block中也对self retain,所以self也不能被释放  block一个对象  也要执行retain/release  使用__block之后,block可以改变该变量  使用__block之后,拷贝block的时候不会将其copy  为什么要copy  copy可以确保block形成闭包,确保变量的有效性  

__blockASIFormDataRequest *request = [ASIFormDataRequestrequestWithURL:[NSURLURLWithString:GetTel_Friends]];  [request setTimeOutSeconds:60.0];  [request setPostValue:strforKey:@"tels"];     

[request setCompletionBlock:^{

       NSString *str = [requestresponseString];

       NSDictionary *dic = [strJSONValue];

       if ([dicobjectForKey:@"data"])

       {

[selfhandleData:[dicobjectForKey:@"data"]];       

}  

}];

关于autoRelease的理解和使用

在使用一个变量的时候,改变来那个需要交由其他的函数后者是对象处理,则用到autorelease

如:函数的返回值,函数的参数,等

autorelease的变量加入到当前的Autorelease pool中,加入的时候该变量的引用计数不变,但是在pool释放的时候pool中对象的引用计数会减一

在main函数中有个一个默认的autoreleasepool,不是所有的autorelease对象都被放到这个pool中等到app接触才释放,而是,对于每一个Runloop系统会隐式创建一个AUtorelease pool,autorelease对象都被方步到当前的pool中,
什么是Runloop,一个UI事件,Timer call ,delegate call,都会是一个新的Runloop,每个线程被创建,那么在UI线程中(也就是通常编码环境中)也有一个Runloop,一个线程的Runloop结束,就是这个线程中的函数调用完毕(如一个开始又main到init,init中没有调用其他函数,则在init结束,runloop结束),这些生成的autorelease对象就会被释放

NSString *st;//一个全局变量(非arc环境下,赋值进行retain)
-(void)fun1{
     NSString *str = [NSString stringWithForm:@“%@“,”aaa”];//这个地方的对象就是一个autorelease对象
     NSLog(@“count %d”,str.retainCount);//1
     st = str;//非arc环境,不对str进行retain
     NSLog(@“count %d”,str,retainCount);//1
     [self fun2];
}
-(void)fun2{
     NSLog(@“count %d”,st.retainCount);//1,依然可以访问那个autorelease对象
}
[[[UITableViewCell alloc] init] autorelease]
这个cell在autorelease之后,retainCount依旧是1,这个对象如果不进行retain,在runloop结束之后会被释放

可见,str这个autorelease对象不是在一个函数结束就销毁了,而是在这个整个函数调用完成之后,才被释放,而是在一个loop执行结束。

所以知道这个原理之后,在使用autorelease生成的对象的时候也,进行了retain操作之后还是要进行release操作

在这里也就对为什么copy new alloc的对象要使用release要进行内存管理,这也是一个命名规范,其他的比如stringWithForm返回的对象则不需要额外的内存管理,因为他返回的是一个autorelease对象

关于copy

在对对象声明成@property (nonatomic,copy)NSStirng *string;的时候,赋值的时候赋值参数引用计数不变,但是返回的结构的引用计数依然是1,依然要进行release操作
self.button.text = @“text”;等同于[ [self getButton] setText:@“text" ]
这里使用点语法取对象


关于内存管理的总结

谁引用谁释放

在哪里执行了alloc,执行了几次的alloc,就要在那里执行几次的release操作

临时变量应该在临时变量不使用的时候执行release

全局变量要再类对象dealloc中执行release

self.obj进行统一的赋值操作(get操作无所谓),这样调理清晰便于管理,不会像_obj = [ Object alloc];混乱

在类的delloc方法中,如果使用self,则需要将使用self的语句放在super dealloc的前边




































0 0
原创粉丝点击