oc的内存管理

JAVA 使用GC 机制自动管理内存的,Objective-C支持手动管理内存,也支持 GC 机制,但是GC机制对于 iOS设备无效,也就是仅对 Mac OS X 电脑有效。这是合理的,因为iPhoneiPodiPad等的内存、CPU肯定要比电脑低很多,你必须谨慎对待内存的使用,而不能肆无忌惮的等着GC 帮你去收拾烂摊子。


OC采用对象的内部通过一个retailCount计数器变量来记录对象引用次数,

每次调用对象的allocnow copy 方法时,retailCount1

调用release方法时retailCount1

调用 retain方法时retailCount1【调用retain方法会返回对象,与直接将对象赋值给一个变量的区别在于,retain会将retailCount1

retailCount0时,OC会自动调用对象的dealloc方法进行内存释放【当对象的retailCount0时,内存已经被释放,而变量还指向之前对象的内存地址,此时如果再调用对象的方法时,会发生野指针错误】

如,

 

  1. int main(){  
  2. Fraction *frac=[[Fraction alloc] initWithNumerator: 3 denominator: 5]; printf("%d ",[frac retainCount]);//1---alloc 分配内存并使引用计数器从 0 变为 1  
  3. [frac retain];//2---引用计数器加 1 printf("%d ",[frac retainCount]);  
  4. [frac retain];//3---引用计数器加 1 printf("%d ",[frac retainCount]);  
  5. [frac release];//2---引用计数器减 1 printf("%d ",[frac retainCount]);  
  6. [frac release];//1---引用计数器减 1 printf("%d ",[frac retainCount]);  
  7. [frac release];//0---引用计数器减 1  
  8. //此时 frac  dealloc 方法自动被调用,Objective-C 回收 frac 对象被回收。你可以在 Fraction 中覆盖-(void) dealloc 方法中加个输出语句观察一下。此时你再去调用 frac 的方 法都会导致程序崩溃,因为那块内存去被清理了。但是你可以像下面这样做。  
  9. frac=nil;  
  10. [frac print];//记得前面说过 nil  null 的区别  


这段代码输出了frac的引用计数器的变化1---2---3---2---1---0,当然这段代码的retain操作毫无意义,仅仅是演示retainrelease之后引用计数器的变化情况。

我们定义一个住址类型的类:

Address.h 

 

  1. #import <Foundation/Foundation.h>  
  2. @interface Address: NSObject{ NSString *city;  
  3. NSString *street;  
  4. }  
  5. -(void) setCity: (NSString*) c;  
  6. -(void) setStreet: (NSString*) s;  
  7. -(void)setCity: (NSString*) c andStreet: (NSString*) s; -(NSString*) city;  
  8. -(NSString*) street;  
  9. @end  


与前面的示例不同的是Address的成员变量citystreet都是NSString的对象类型,不是基本数据类型。 

Address.m 

 

  1. #import "Address.h"  
  2. @implementation Address -(void) setCity: (NSString*) c{  
  3.     [c retain]; [city release]; city=c;  
  4. }  
  5. -(void) setStreet: (NSString*) s{  
  6.     [s retain]; [street release]; street=s;  
  7. }  
  8. -(void)setCity: (NSString*) c andStreet: (NSString*) s{  
  9.     [self setCity: c];  
  10.     [self setStreet: s];  
  11. }  
  12. -(NSString*) city{  
  13.     return city;  
  14. }  
  15. -(NSString*) street{  
  16.     return street;  
  17. }  
  18. -(void) dealloc{  
  19.     [city release]; [street release]; [super dealloc];  
  20. @end  


你可以先不理会这里的一堆retainrelease操作,一会儿会做讲解。 

main.m

 

  1. NSString *city=[[NSString alloc] initWithString@"Beijing"];  
  2. // initWithString  NSString 的一个使用 NSString 字面值初始化的方法。与直接使用下面的 C 语言字符序列初始化相比,@” ”支持 Unicode 字符集。  
  3. NSString *street=[[NSString alloc] initWithCString"Jinsongzhongjie"];  
  4. // initWithCString  NSString 的一个使用 C 语言的字符序列初始化的方法。  
  5. Address *address=[[Address alloc] init]; [address setCity: city andStreet: street];  
  6. [city release]; [street release]; [address release];  

 

 

 

 

我们在 main函数中创建了citystreet两个NSString的实例,然后setteraddress实例,由于citystreet你使用了alloc分配内存,你势必就要release它们。首先要确定的是在main函数里release还是在addressrelease?因为你确实把他们两个传递给Address的实例address了。我们一般按照谁创建的就谁回收的原则(对象的拥有权),那么自然你要在main方法里release,我们恰当的选择了在调用完addresssetter方法之后release,因为citystreetmain函数中的任务(address里的两个成员变量赋值)就此结束。此时,新的问题来了,我们在main函数中setter完之后release对象citystreet,因此citystreetretainCount会由1变为0,这就会导致你传递到address里的citystreet指针指向的对象被清理掉,而使address出错。很显然,你需要在addresssetter方法里retain一下,增加citystreet的引用计数为2,这样即便在main函数release之后,citystreet指向的内存空间也不会被dealloc,因为2-1=1,还有一个引用计数。因此就有了Address中的setter的写法,我们拿setCity方法为例

 

  1. -(void) setCity: (NSString*) c{  
  2.     [c retain];//---1  
  3.     [city release];//---2  
  4.     city=c;//---3  
  5. }  


JAVA这种使用GC机制的语言中,我们只需要写第三条语句。其实Objective-C中你也可以只写第三条语句,我们管这种方式获得的city叫做弱引用,也就是city只是通过赋值操作,把自己指向了c指向的对象,但是对象本身的引用计数器没有任何变化,此时city就要承担[c release]之后所带来的风险,不过有些情况下,你可能确实需要这种弱引用。当然,大多数情况下,我们使用的都是强引用,也就是使用第一行代码首先retain一下,使引用计数器加1,再进行赋值操作,再进一步说就是先通过retain方法拿到对象的拥有权,再安全的使用对象。

第二行代码又是为什么呢?其实这是为了防止city可能已经指向了一个对象,如果不先对city进行一次release,而直接把city指向c指向的对象,那么city原来指向的对象可能会出现内存泄漏,因为city在改变指向的时候,没有将原来指向的对象的引用计数器减1,违反了你retain对象之后,要在恰当的时刻release对象的要求。第三行代码毋庸置疑的要在最后一行出现,但按照前面的阐述,貌似第一行、第二行的代码是没有先后顺序的,也就是可以先[city release],[c retain],但真的是这样吗?有一种较为少见的情况,那就是把自己作为参数赋给自己(这听起来很怪),也就是说参数c和成员变city是一个指针变量,那么此时如果你先调用[city release],就会导致对象的retainCount0,对象被dealloc,那么[c retain]就会报错,因为对象没有了。综上所述,上面所示的三行代码是比较好的组织方式。但其实你可能也会看到有些人用其他的方式书写setter方法,这也没什么好奇怪的,只要保证对象正常的使用、回收就是没有问题的代码。

最后我们再看一下Address中的dealloc方法,因为你在setter中持有了citystreet指向的对象的拥有权,那么你势必要和main函数一样,负责在恰当的时刻release你的拥有权,由于你在address实例中可能一直要用到citystreet,因此release的最佳时刻就是address要被回收的时候。另外,我们知道对象被dealloc之后,原来指向它的指针们依然指向这块内存区域,Objective-C并不会把这些指着垃圾的指针们都指向nil,因此你如果还调用这些指针们的方法,就会报错。因此有些人喜欢在dealloc里把对象release之后紧接着指向nil,防止它会被意外调用而出现空指针异常。但我认为你这样可能屏蔽了错误,不是一个好办法。

Objective-C 的手动管理内存还提供了一种半自动的方式,就是第八节用到的NSAutoreleasePool类型。只要实例是allocnewcopy出来的,你就需要选择完全手动的管理内存,也就是你要负责release。第八节的DenominatorNotZeroException异常我们是通过父类NSException的类方法exceptionWithName:reason:userInfo创建的,我们并没有使用到allocnewcopy关键字,这种情况下,你只需要定义一个NSAutoreleasePool就可以了。为什么是这样呢?

我们先看下面的代码

 

  1. NSAutoreleasePool *pool=[[NSAutoreleasePool alloc] init];  
  2. Fraction *frac=[[Fraction alloc] initWithNumerator: 3 denominator: 5];  
  3. printf("frac 的引用次数 %d ",[frac retainCount]);//1  
  4. [frac retain];  
  5. printf("frac 的引用次数 %d ",[frac retainCount]);//2  
  6. [frac autorelease];  
  7. printf("frac 的引用次数 %d ",[frac retainCount]);//2  
  8. [frac autorelease];  
  9. printf("frac 的引用次数 %d ",[frac retainCount]);//2 [  
  10. pool release];  


这里我们对frac用的是autorelease方法,不是release方法,autorelease方法并不对引用计数器减1,而是将对象压入离它最近的NSAutoreleasePool的栈顶(所谓离得最近就表示多个NSAutoreleasePool是可以嵌套存在的),等到NSAutoreleasePoolrelease方法被调用后,NSAutoreleasePool会将栈内存放的指针指向的对象的release方法。

ps:NSAutoreleasePooleIOS 5以前的语法,IOS 5以后使用

 

  1. @autoreleasepool {  
  2.        .....  
  3. }  

 

其实exceptionWithName:reason:userInfo方法的内部实现就是把创建好的NSException的实例的指针返回之前,首先调用了指针的autorelease方法,把它放入了自动回收池。

其实在iOS开发中,苹果也是不建议你使用半自动的内存管理方式,但不像GC机制一样被禁用,原因是这种半自动的内存管理,容易在某些情况下导致内存溢出。我们看一下如下的代码:

 

  1. NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];  
  2. for(int i=0;i<1000000;i++){ NSString *s=@"..."if(i%1000==0){//执行代码}  
  3. }  
  4. [pool release];  


这里我们在循环100万次的代码中每次都创建一个NSString实例,由于NSString没有使用alloc创建实例,因此我们使用了自动回收池。但这段代码的问题是这100万个NSString要在for循环完毕,最后的pool release方法调用后才会回收,这就会造成在for循环调用过程中,内存占用一直上涨。当然,你可以改进上面的代码如下所示:

 

  1. NSAutoreleasePool *pool=[[NSAutoreleasePool alloc]init];  
  2. for(int i=0;i<1000000;i++){  
  3. NSString *s=@"..."if(i%1000==0){  
  4. [pool release];  
  5. pool=[[NSAutoreleasePool alloc] init]; }  
  6. }  


上面的代码每循环1000,就回收自动回收池,然后再紧接着重新创建一个自动回收池给下1000NSString对象使用。其实这个问题很像Hibernate的一个问题:给你一张1000万行记录的Excel,让你导入到数据库,你该怎么做呢?直接的做法如下所示:
Session session=获取HibernateJDBC连接对象

for(int i=0;i<Excel 的行数;i++){
Object obj=每一行的Excel记录对应的JAVA对象;

session.save(obj);

}
Transaction.commit();
由于Hibernate的一级缓存是在你提交事务的时候才清空,并且刷新到数据库上的,因此在循环结束前,你的一级缓存里会积累大量的obj对象,使得内存占用越来越大。

解决办法与Objective-C的自动回收池差不多,就是如下的做法:Session session=获取HibernateJDBC连接对象
for(int i=0;i<Excel的行数;i++){

Object obj=每一行的Excel记录对应的JAVA对象;session.save(obj);

if(i%1000==0){

session.flush();

}

}
Transaction.commit();
我们看到每隔1000次就刷新一级缓存,它的作用就是清理一级缓存,把数据都发送到数据库上面去,但是我并没有提交事务,也就是数据都从JVM转移到数据库的缓冲区累积了,数据库依然会等待循环结束后的commit()操作才会提交事务。 


总结:内存管理原则

1、谁创建,谁释放。如果你通过allocnew(mutable)copy来创建一个对象,那么你必须调用releaseautorelease。换句话说,不是你创建的,就不用你去释放

2、一般来说,除了allocnewcopy之外的方法创建的对象都被声明了autorelease

3、谁retain,谁release。只要你调用了retain,无论这个对象是如何生成的【包含autorelease】,你都要调用release

原文地址:https://www.cnblogs.com/killiancheung/p/4569780.html