3. objC内存管理(<ios4.0)

来源:互联网 发布:网络调试助手 编辑:程序博客网 时间:2024/06/05 04:49

          在 objC中,实例对象的内存管理是靠引用计数来进行的,当前上下文用此对象时,就加一,否则就减一,当减到零时,就析构该对象。

     1 实例对象引用计数加1

     1.1 在一个生命周期中,如果用alloc, new, copy来创建类对象后,那么retain的个数就会变为1,而且此生命周期要负责对此对象发送release或者autorelease消息。这是一个ObjC中默认的规则,因为,客户程序要默认你写的代码是遵循这条规则的。

     1.2 在一个对象生成后,只要向其实例发送retain消息,那么,同样retainCount的个数会增加1

此条规则说起来,非常简单,实际上在编程中,存在诸多陷阱。(c++也同样存在这种情况,见Effective C++)

首先,我们来讲@property,和C#一样,ObjC编译器也同样为属性@property <porpertyA> 生成了-(void) set<propertyA>:<propertyA*> ;-(<propertyA*>) <propertyA>;两个函数。那么,set<propertyA>:<propertyA*>函数具体的生成情况,又按照@property的相关属性进行生成,这些属性可按照下表进行理解

retain/assign/copy
控制生成相应版本的set<propertyA>:<propertyA*>函数体的代码生成readwrite/ readonly控制是否要生成set<propertyA>:<protertyA*>函数体atonicity/nonatomic是否在两个函数中,要添加线程同步锁

从上边的属性的描述来看,属性的生成绝对不是想当然那样的简单,如果,程序员没有十足的把握,那么,请把着看似平淡无奇实则高胜莫测的代码交给编译器去作罢。

     因为,这里我们讲解retainCount,可以简单的用代码解析一下简单的retain属性。请看一下代码:

@interface Patient:NSObject@property(retain, nonatomic) NSString* Name;@end@implementation Patient@end@interface WorkContext:Patient{    Patient* patient;}@property(retain, nonatomic) Patient* patient;/*-(void) setPatient:(Patient *)patient;//属性默认生成的两个函数,-(Patient*) patient;*/-(void) assign2Patient:(Patient*) pat;//模拟(void) setPatient:(Patient*) patient;@end@implementation WorkContext@synthesize patient;/*-(void) setPatient:(Patient *)patient{    int a = 0;}-(Patient*) patient{    return nil;}*/-(void) assign2Patient:(Patient *)pat//如果自己不是很擅长维护类对象的次数,请交给编译器吧{    Patient* ptemp = patient;//防止自己赋值自己    patient = [pat retain];    [ptemp release];//和C++中不同,无需判断是否为空。}@end/**调用*/    Patient* patient1 = [[Patient alloc]init];    NSLog(@"%ld", [patient1 retainCount]);    WorkContext* workcontext = [[WorkContext alloc]init];    NSLog(@"%ld",[workcontext retainCount]);    workcontext.patient = patient1;//属性的赋值    NSLog(@"%ld", [patient1 retainCount]);      [workcontext setPatient:patient1];//属性默认生成的set参数,同一个对象,赋值两次    NSLog(@"%ld", [patient1 retainCount]);    [workcontext setPatient:[workcontext patient]];//自己向自己赋值    NSLog(@"%ld", [[workcontext patient] retainCount]);            //练习2    Patient* patient2 = [[Patient alloc]init];    WorkContext* workcontext2 = [[WorkContext alloc]init];    [workcontext2 assign2Patient:patient2];//模拟赋值函数    NSLog(@"%ld",[patient2 retainCount]);


2 实例对象引用计数减1

            在Objective-C和C++中,都存在一个不成文的规定,谁申请的内存还是有谁释放。这里的谁实际就是作用域,在当前的作用域中申请了内存空间,必须要在此作用域中进行释放。

         这样就出问题了。

         因为,在编写函数的过程中,常常会出现在一个函数中申请内存,而此内存要在函数体之外进行应用的情况,然后由调用函数体对内存进行释放。这就有悖上文提到的不成文的规定。

        这样autorelease就产生了。

         函数调用release或autorelease的方法来向运行时表示对象在当前的上下文中不在需要。其中,release的方法是直接减一,并判断,如果retainCount现在为零,那么就释放该对象。

         autorelease是表示该实例对象,在当前的上下文中不再需要了,retainCount保持不变。待到 NSAutoreleasePool进行清理的时候,要对所有登记过的对象进行判断。如果对象的retainCount的值和登记次数相等,那么就对该对象进行释放。

         实际上这两种方式没有本质上的区别,都是表示该对象在当前上下文中,不再需要。只有释放内存的时机不同。

         autorelease在编程中,非常之有用。因为程序员可以用它可以表述当前的实例对象,在当前的执行上下文中,已完全没有用处了。但在其他的函数或模块,可能有用。例如,上个章节《2.objC动态绑定》代码中的@implementation PaitentCreator工厂类的作用,用来生成一个实例对象。这一点,要比C++做的要好,C++中工厂类所生成的对象,常常是由调用者去负责销毁,直接delete或调用服务代码提供的函数,无论以哪种方式,把销毁对象的任务交给客户代码,都太危险了,大大增加内存泄漏的风险。下边是对内存管理进行的测试代码

NSAutoreleasePool* pool= [[NSAutoreleasePoolalloc] init];    Patient* tempPat = [[PaitentCreatorCreatePatient] retain];    NSLog(@"the tempPat instance object retain count = %ld", [tempPat retainCount]);//2    /**     */    NSMutableString* pString = [[NSMutableStringalloc]initWithString:@"Zhao^Tiegui^^^"];    NSLog(@"the current name retain count = %ld",[pString retainCount]);//1    tempPat.name = pString;    NSLog(@"the current name retain count = %ld",[pString retainCount]);//2    [pString release];    NSLog(@"the current name retain count = %ld",[pString retainCount]);//1    [tempPat printOut];    /**     */    NSString* tempSex = [[NSStringalloc] initWithCString:"Zhao^Tiegui"];    NSLog(@"the sex sex retain count = %ld",[tempSex retainCount]);//1    tempPat.sex = tempSex;    NSLog(@"the sex sex retain count = %ld",[tempSex retainCount]);//2    [tempSex release];    NSLog(@"the sex sex retain count = %ld",[tempSex retainCount]);//1    [tempPat printOut];      NSLog(@"the tempPat retain count = %ld",[tempPat retainCount]);//2    [tempPat release];    NSLog(@"the tempPat retain count = %ld",[tempPat retainCount]);//1    [pool release];    NSLog(@"the pool retain count = %ld",[pool retainCount]);//1    NSLog(@"the tempPat retain count = %ld",[tempPat retainCount]);//1152921504606846975无效的值    [tempPat printOut];//产生异常

         这里要特别的注意,在新建的类中,一定要重载dealloc函数。并且,super的dealloc要放在子类释放之后,这里和C++析构的顺序是相同的。具体原因一样,子类在析构时候,可能会调用父类的函数。所以,父类一定要后析构于子类。