Object-C 内存管理

一基本原理


Objective-C的内存管理机制与.Net/Java那种全自动的垃圾回收机制是不同的,它本质上还是C语言中的手动管理方式,只不过稍微加了一些自动方法。


1           Objective-C的对象生成于堆之上,生成之后,需要一个指针来指向它。


ClassA *obj1 = [[ClassA alloc] init];




2           Objective-C的对象在使用完成之后不会自动销毁,需要执行dealloc来释放空间(销毁),否则内存泄露。


[obj1 dealloc];


这带来了一个问题。下面代码中obj2是否需要调用dealloc?


ClassA *obj1 = [[ClassA alloc] init];


ClassA *obj2 = obj1;


[obj1 hello]; //输出hello


[obj1 dealloc];


[obj2 hello]; //能够执行这一行和下一行吗?


[obj2 dealloc];


不能,因为obj1和obj2只是指 针,它们指向同一个对象,[obj1 dealloc]已经销毁这个对象 了,不能再调用[obj2 hello]和[obj2 dealloc]。obj2实际上是个无效指针。


如何避免无效指针?请看下一条。




3           Objective-C采用了引用计数(ref count或者retain count)。对象的内部保存一个数字,表示被引用的次数。例如,某个对象被两个指针所指向(引用)那么它的retain count为2。需要销毁对 象的时候,不直接调用dealloc,而是调用release。release会 让retain count减1,只有retain count等于0,系统才会调用dealloc真正销毁这个对象。


ClassA *obj1 = [[ClassA alloc] init]; //对象生成时,retain count = 1


[obj1 release]; //release使retain count减1,retain count = 0,dealloc自动被调用,对象被 销毁


我们回头看看刚刚那个无效指针的问题,把dealloc改成release解 决了吗?


ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1


ClassA *obj2 = obj1; //retain count = 1


[obj1 hello]; //输出hello


[obj1 release]; //retain count = 0,对象被销毁


[obj2 hello];


[obj2 release];


[obj1 release]之 后,obj2依然是个无效指针。问题依然没有解决。解决方法见下一条。




4           Objective-C指针赋值时,retain count不会自动增加,需要手动retain。


ClassA *obj1 = [[ClassA alloc] init]; //retain count = 1


ClassA *obj2 = obj1; //retain count = 1


[obj2 retain]; //retain count = 2


[obj1 hello]; //输出hello


[obj1 release]; //retain count = 2– 1 = 1


[obj2 hello]; //输出hello


[obj2 release]; //retain count = 0,对象被销毁


问题解决!注意,如果没有调用[obj2 release],这个对象的retain count始终为1,不会被销毁,内存泄露。(1-4可以参考附件中的示例程序memman-no-pool.m)


这样的确不会内存泄露,但似乎有点麻烦,有没有简单点的方法?见下一条。




5           Objective-C中引入了autorelease pool(自动释放对象池),在遵守一些规则的情况下,可以自动释放对象。(autorelease pool依然不是.Net/Java那种全自动的垃圾回收机制)


5.1新生成的对象,只要调用autorelease就行了,无需再调用release!


ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1但无需调用release




5.2对于存在指针赋值的情况,代码与前面类似。


ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1


ClassA *obj2 = obj1; //retain count = 1


[obj2 retain]; //retain count = 2


[obj1 hello]; //输出hello


//对于obj1,无需调用(实际上不能调用)release


[obj2 hello]; //输出hello


[obj2 release]; //retain count = 2-1 = 1




细心的读者肯定能发现这个对象没有被销毁,何时销毁呢?谁去销毁它?(可以参考附件中的示例程序memman-with-pool.m)请看下一条。




6           autorelease pool原理剖析。(其实很简单的,一定要坚持看下去,否则还是不能理解Objective-C的内存管理机制。)


6.1          autorelease pool不是天生的,需要手动创立。只不过在新建一个iphone项目时,xcode会 自动帮你写好。autorelease pool的真名是NSAutoreleasePool。


NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];


6.2          NSAutoreleasePool内部包含一个数组(NSMutableArray),用来保存声明为autorelease的所有对象。如果一个对象声明为autorelease,系统所做的工作就是把这个对象加入到这个数组中去。


ClassA *obj1 = [[[ClassA alloc] init] autorelease]; //retain count = 1,把此 对象加入autorelease pool中


6.3          NSAutoreleasePool自身在销毁的时候,会遍历一遍这个数组,release数组中的每个成员。如果此时数组中成员的retain count为1,那么release之后,retain count为0,对象正式被销 毁。如果此时数组中成员的retain count大于1,那么release之后,retain count大于0,此对象依然没有被销毁,内存泄露。


6.4默认只有一个autorelease pool,通常类似于下面这个例子。


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


{


NSAutoreleasePool *pool;


pool = [[NSAutoreleasePool alloc] init];




// do something




[pool release];


return (0);


} // main


所有标记为autorelease的对象都只有在程序退出时(pool销毁时)才被销毁。如果你有大量的对象标记为autorelease,这显然不能很好的利用内存,在iphone这种内存受限的程序中是很容易造成内存不足的。例如:


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


{


NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];


int i, j;


for (i = 0; i < 100; i++ )


{


 for (j = 0; j < 100000; j++ )


[NSString stringWithFormat:@"1234567890"];//产 生的对象是autorelease的。


}


[pool release];


return (0);


} // main


(可以参考附件中的示例程序memman-many-objs-one-pool.m,运行时通过监控工具可以发现使用的内存在急剧增加,直到pool销毁时才被释放)你需要考虑下一条。




7           Objective-C程序中可以嵌套创建多个autorelease pool。在需要大量创建局部变量的时候,可以创建内嵌的autorelease pool来及时释放内存。


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


{


NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];


int i, j;


for (i = 0; i < 100; i++ )


{


 NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];


 for (j = 0; j < 100000; j++ )


[NSString stringWithFormat:@"1234567890"];//产 生的对象是autorelease的。


 [loopPool release];


}


[pool release];


return (0);


} // main


(可以参考附件中的示例程序memman-many-objs-many-pools.m,占用内存的变化极小)


二口诀与范式





1口诀。


1.1谁创建,谁释放(类似于“谁污染,谁治 理”)。如果你通过 alloc、new或copy来创建一个对象,那么你必须调用release或autorelease。换句话说,不是你创建的,就不用你去释放。

例如,你在一个函数中alloc生成了一个对象,且这个对象只在这个函数中被使用,那么你必须在这个函数中调用release或autorelease。如果你在一个class的某个方法中alloc一个成员对象,且没有调用autorelease, 那么你需要在这个类的dealloc方法中调用release;如果调用 了autorelease,那么在dealloc方法中什么 都不需要做。


1.2除了alloc、new或copy之外的方法创建的 对象都被声明了autorelease。


1.3谁retain,谁release。只要你调 用了retain,无论这个对象是如何生成的,你都要调用release。有时候 你的代码中明明没有retain,可是系统会在默认实现中加入retain。不知道为什 么苹果公司的文档没有强调这个非常重要的一点,请参考范式2.7和第三章。


2范式。

范式就是模板,就是依葫芦画瓢。由于不同人有不同的理解和习惯,我总结的范式不一定适合所有人,但我能保证照着这样做不会出问题。


2.1创建一个对象。


ClassA *obj1 = [[ClassA alloc] init];


2.2创建一个autorelease的对象。


ClassA *obj1 = [[[ClassA alloc] init] autorelease];


2.3          Release一个对象后,立即把指针清空。(顺便说一句,release一个空指 针是合法的,但不会发生任何事情)


[obj1 release];


obj1 = nil;


2.4指针赋值给另一个指针。


ClassA *obj2 = obj1;


[obj2 retain];


//do something


[obj2 release];


obj2 = nil;


2.5在一个函数中创建并返回对象,需要把这个 对象设置为autorelease


ClassA *Func1()


{


  ClassA *obj = [[[ClassA alloc]init]autorelease];


  return obj;


}


2.6在子类的dealloc方法中调用基类的dealloc方法


-(void) dealloc


{


         …


[super dealloc];


}


2.7在一个class中创建和使用property。


2.7.1声明一个成员变量。


ClassB *objB;


2.7.2声明property,加上retain参数。


@property (retain) ClassB* objB;


2.7.3定义property。(property的默认实 现请看第三章)


@synthesize objB;


2.7.4除了dealloc方法以外,始终用.操作符的方式来调用property。


self.objB或者objA.objB


2.7.5在dealloc方法中release这个成员变 量。


[objB release];


示例代码如下(详细代码请参考附件中的memman-property.m,你需要特别留意对象是在何时被销毁的。):


@interface ClassA : NSObject


{


         ClassB* objB;


}




@property (retain) ClassB* objB;


@end




@implementation ClassA


@synthesize objB;


-(void) dealloc


{


         [objB release];


         [super dealloc];


}


@end


2.7.6给这个property赋值时, 有手动release和autorelease两 种方式。


void funcNoAutorelease()


{


         ClassB *objB1 = [[ClassB alloc]init];


         ClassA *objA = [[ClassA alloc]init];


         objA.objB = objB1;


         [objB1 release];


         [objA release];


}




void funcAutorelease()


{


         ClassB *objB1 = [[[ClassB alloc]init] autorelease];

三@property (retain)和@synthesize的默认实现





在这里解释一下@property (retain) ClassB* objB;和@synthesize objB;背后到底发生了什么(retain property的默认实现)。property实际上是getter和setter,针对有retain参数的property,背后的实现如下(请参考附件中的memman-getter-setter.m,你会发现,结果和memman-property.m一样):


@interface ClassA : NSObject


{


         ClassB *objB;


}




-(ClassB *) getObjB;


-(void) setObjB:(ClassB *) value;


@end




@implementation ClassA


-(ClassB*) getObjB


{


         return objB;


}




-(void) setObjB:(ClassB*) value


{


         if (objB != value)


         {


                   [objB release];


                   objB = [value retain];


         }


}


在setObjB中,如果 新设定的值和原值不同的话,必须要把原值对象release一次,这样才能保证retain count是 正确的。


由于我们在class内部retain了一次(虽然 是默认实现的),所以我们要在dealloc方法中release这个成员变 量。


-(void) dealloc


{


         [objB release];


         [super dealloc];


}




补充说明


在研究retain count的 时候,我不建议用NSString。因为在下面的语句中,


NSString *str1 = @”constant string”;


str1的retain count是个很大的数字。

Objective-C对 常量字符串做了特殊处理。


当然,如果你这样创建NSString,得到的retain count依然为1




NSString *str2 = [NSString stringWithFormat:@”123”];








当你使用new、alloc 或 copy 创建对象时,对象的 count retain 到 1。你一定要负责把这个对象 release 或 autolease 掉。这样当它的生命周期结束时,它才能清空。

When you create an object using new, alloc, or copy, the object has a retain count of 1. You are responsible for sending the object a release or autorelease message when you’re done with it. That way, it gets cleaned up when its useful life is over.

当你使用其他方法获得一个对象时,你可以认为它已经retain了一个 count,并且 autolease 掉了。你不用考虑和它相关的清理问题。但是如果你想保留这个对象,那么你需要 retain 它,并且要确保之后你 release 了这个对象。

When you get hold of an object via any other mechanism, assume it has a retain count of 1 and that it has already been autoreleased. You don’t need to do any fur- ther work to make sure it gets cleaned up. If you’re going to hang on to the object for any length of time, retain it and make sure to release it when you’re done.

如果你retain一个对象,你最终总是需要 release 或者 autolease 它。

If you retain an object, you need to (eventually) release or autorelease it. Balance these retains and releases.

这三条规则在写代码的时候一定要遵守,一旦遵守了一般也就不会有内存泄露的问题。


创建对象


Objective-C中创建对象分为 alloc 和 init 两步,alloc 是在堆(heap)上初始化内存给对象变量,把变量(指针)设为 nil。每个类可以很多 init 方法,且每个方法都以 init 开头,但每个类只有一个特定(designated)的 init 方法,NSObject 是 init;,UIView 是 - (id)initWithFrame:(CGRect)aRect;。在子类的 designated 方法中一定要调用父类的 designated 方法,子类其他的 init 方法只能调用子类自己的 designated 方法,不能调用父类的(即使用 self 而不是 super)。


下面是一些小知识点:


当你想暂时保留对象时,使用autolease

    - (Money *)showMeTheMoney:(double)amount {

      Money *theMoney = [[Money alloc] init:amount];

      [theMoney autorelease];

      return theMoney;

    }

集合类的autolease,一种方法是像对象一样调用 autolease,另外也可以调用 [NSMutableArray array],最好的方式 return [NSArray arrayWithObjects:@“Steve”, @“Ankush”, @“Sean”, nil];,其他类似的方法返回的对象都是 autolease 的对象。

[NSString stringWithFormat:@“Meaning of %@ is %d”, @“life”, 42];

[NSDictionary dictionaryWithObjectsAndKeys:ankush, @“TA”, janestudent, @“Student”, nil];

    [NSArray arrayWithContentsOfFile:(NSString *)path];

集合类对新增的对象拥有ownership

@"string"是 autorelease 的

NSString一般是 copye 而不是 retain

你应该尽快release你拥有的对象,越快越好。建议创建完对象后就写好 release 的代码

当最后一个对象owner release后,会自动调用 dealloc 函数,在类中需要重载 dealloc,但永远都不要自己去调用 dealloc

@property一般直接返回对象变量,我们可以把它理解为返回的是 autolease 的对象

使用@synthesize实现时,@property 可以指定 setter 函数使用 retain,copy 或 assign。assign 一般用在属性一定会随着对象的消亡而消亡的,比如 controller 的view,view 的 delegate

Protocols可以理解为抽象接口,delegat 和 dataSource 基本都是用 protocol 定义的



Objective-C的内存管理机制是比较灵活的,即可以拿来像C/C++一样用,也可以加个AutoreleasePool让它升级为半自动化的内存管理语言。当然,也不能拿JAVA虚拟机中的全自动化GC来比?


一,引用计数是实例对象的内存回收唯一参考

引用计数(retainCount)是Objective-C管理对象引用的唯一依据。调用实例的release方法后,此属性减一,减到为零时对象的dealloc方法被自动调用,进行内存回收操作,也就是说我们永不该手动调用对象的dealloc方法。


它的内存管理API老简单老简单了,下面就是它主要操作接口:


1,alloc, allocWithZone,new(带初始化)

为对象分配内存,retainCount为“1”,并返回此实例


2,release

retainCount减“1”,减到“0”时调用此对象的dealloc方法


3,retain

retainCount加“1”


4,copy,mutableCopy

复制一个实例,retainCount数为“1”,返回此实例。所得到的对象是与其它上下文无关的,独立的对象(干净对象)。


5,autorelease

在当前上下文的AutoreleasePool栈顶的autoreleasePool实例添加此对象,由于它的引入使Objective-C(非GC管理环境)由全手动内存管理上升到半自动化。



二,Objective-C内存管理准则

我们可以把上面的接口按对retainCount的操作性质归为两类,

A类是加一操作:1,3,4

B类是减一操作:2,5(延时释放)


内存管理准则如下:

1,A与B类的调用次数保持一制

2,为了很好的保障准则一,以实例对象为单位,谁A了就谁B,没有第二者参与


例:


NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSObject *o = [[NSObject alloc] init];    //retainCount为1

[o retain];    //retainCount为2

[o release]; //retainCount为1

[o autorelease]; //retainCount为1

[pool release]; //retaincount为0,触发dealloc方法





三,对象的拥有者

面向对象领域里有个引用的概念,区别于继承,引用常被用来当做偶合性更小的设计。继承是强依赖,对吧。我们要降偶软件的设计,就要尽量减少对它的使用。但没有任何偶合的模块或功能是没有用的?对吧,那我们只能多用引用了吧。一个实例拥有另一个实例的时候,我们称它为引用了另一个实例。


比如ClassA类的一个属性对象的Setter方法:



- (void)setMyArray:(NSMutableArray *)newArray {

    if (myArray != newArray) {

        [myArray release];

        myArray = [newArray retain];

    }

}




假设这个类的一个实例为'a',调用setMyArray后,我们就可以说a拥有了一个新的myArray实例,也可以说a引用了一个新的myArray实例。其中调用的retain方法,使myArray的retainCount加一,我们需要注意以下两个地方:

1,setMyarray方法中,在retain之前先release了旧实例一次

2,在本实例的dealloc方法中,本应该是要再次release当前实例的,但回头看看参考内存管理准则。它并不合理,对吧。。。多了一次release。这里比较推荐的做法是:

[myArray setMyArray:nil];

这样可以巧妙的使当前实例release而不出错(我们可以向nil发送消息?其实它本身就是个整数0),并符合我们的内存管理准则。更主要的是,很简单,你不需要考虑过多的事情。


另外一个比较容易忽略而又比较经典的问题是实例变量的循环引用,Objective-C为此区分了,其实也相当相当的简单:

1,强引用,上面讲的就是强引用,存在retainCount加一。

2,弱引用,但凡是assign声明并直接用指针赋值实现的被称之为弱引用,不存在retainCount加一的情况。


四,AutoreleasePool使Objective-C成为内存管理半自动化语言。

如果仅仅是上面这些,很简单,对吧。但往往很多人都会迷糊在自动内存管理这块上,感觉像是有魔法,但其实原理也很简单?


先看看最经典的程序入口程序:

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

int retVal = UIApplicationMain(argc, argv, nil, nil);

[pool release];


我们先把pool看成一个普通对象?很简单,先是alloc,pool的retainCount为1。第三句release,retainCount为0,自动调用它的dealloc方法。它和任何其它普通对象没 任何区别。


魔法在哪里?

在声明pool后,release它之前的这段代码,所有段里的代码(先假设中间没有声明其它的AutoreleasePool实例),凡是调用了 autorelase方法的实例,都会把它的retainCount加1,并在此pool实例中添1次此实例要回收的记录以做备案。当此pool实例 dealloc时,首先会检查之前备案的所有实例,所有记录在案的实例都会依次调用它的release方法。



NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSObject *o = [[NSObject alloc] init];

[o autorelease];                                //在pool实例dealloc时,release一次此实例,重要的是并不是在此行去release

NSLog(@"o retainCount:%d",[o retainCount]);    //此时还可以看到我们的o实例还是可用的,并且retainCount为1

[pool release];    //pool的 retainCount为0,自动调用其dealloc方法,我们之前备案的小o也将在这里release一次(因为咱们之前仅仅autorelease一次)




真对同一个实例,同一个Pool是可以多次注册备案(autorelease)的。在一些很少的情况化可能会出现这种需求:  

NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSObject *o = [[NSObject alloc] init];

[o retain];

[o autorelease];

[o autorelease];

[pool release];




我们调用了两次A类(retainCount加1的方法),使其retainCount为2,而接下来的两次autorelease方法调用,使其在 pool中注册备案了两次。这里的pool将会在回收时调用此实例的两次release方法。使其retainCount降为0,完成回收内存的操作,其 实这也是完全按照内存管理规则办事的好处?


AutoreleasePool是被嵌套的!

池是被嵌套的,嵌套的结果是个栈,同一线程只有当前栈顶pool实例是可用的:


|  pool_3  |

|  ---------      |

|  pool_2      |

|  ---------   |

|  pool_1  |

|_______|


其代码如下:



NSAutoreleasePool *pool1 = [[NSAutoreleasePool alloc] init];

NSAutoreleasePool *pool2 = [[NSAutoreleasePool alloc] init];

NSAutoreleasePool *pool3 = [[NSAutoreleasePool alloc] init];


NSObject *o = [[NSObject alloc] init] autorelease];


[pool3 release];

[pool2 release];

[pool1 release];




我们可以看到其栈顶是pool3,o的autorelease是把当前的release放在栈顶的pool实例管理。。。也就是pool3。

在生命周期短,产生大量放在autoreleasePool中管理实例的情况下经常用此方法减少内存使用,达到内存及时回收的目的。


AutoreleasePool还被用在哪里?

在上面的例子里,也可以看到,我们在执行autorelease方法时,并没有时时的进行release操作?它的release被延时到pool实例的 dealloc方法里。这个小细节使我们的Objective-C用起来可以在方法栈中申请堆中的内存,创建实例,并把它放在当前pool中延迟到此方法 的调用者释放?

你可能感兴趣的:(Object-C 内存管理)