IOS程序内存管理

http://blog.csdn.net/lamp_zy/article/details/8167742

一、非ARC方式,即手动引用计数方式:

这种方式使用引用计数来控制对象的释放

1.alloc,copy,retain三个函数(包括名字中带有着三个词的函数)每调用一次引用计数加1

alloc:分配一块内存用于存放调用类的对象

retain:保有这个对象,通过使其引用计数加1来实现保有,retain返回的对象的指针如果不调用release,这个对象的引用计数至少还有1,同个这样来控制保有这个对象

copy:新开辟一块内存,把调用对象拷贝到里面,copy返回的对象跟被copy的对象完全独立,改变其中一个不会影响另一个

 

2.release,autorelease每调用一次,引用计数减1:

autorelease:

ClassA *Func1() 

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

每一个runloop,系统会隐式创建一个autorelease pool,每个runloop结束时,对应autorelease pool会销毁,pool中每个object都会release一次

一次事件就产生一个runloop,如一次鼠标点击,键盘点击,一次触摸,一次异步http连接并接收完数据等

也可以自己创建autorelease pool,比如在一个循环中用到了太多的临时变量,在整个循环结束(或每一次循环结束)后想让其自动release:

void main()
{
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

NSArray *args = [[NSProcessInfo processInfo] arguments];
unsigned count, limit = [args count];

for (count = 0; count < limit; count++)
{
NSAutoreleasePool *loopPool = [[NSAutoreleasePool alloc] init];
NSString *fileContents;
NSString *fileName;

fileName = [args objectAtIndex:count];
fileContents = [[[NSString alloc] initWithContentsOfFile:fileName] autorelease];//调用了autorelease,才会进入autorelease pool
// this is equivalent to using stringWithContentsOfFile:

/* Process the file, creating and autoreleasing more objects. */

[loopPool release];
}

/* Do whatever cleanup is needed. */
[pool drain];

exit (EXIT_SUCCESS);
}

drain和release在引用计数环境中(ios)是一样的,release自身的同时遍历pool中所有对象进行release,在垃圾回收环境中(mac,ios不支持垃圾回收),release不做任何事情,drain则能触发垃圾回收,所以,一般用drain,以后的兼容性更好

autorelease的执行效率并不高,不能马上释放需要释放的内存,可能导致积压很多需要释放的内存,所以尽量不用用autorelease

 

3.引用计数等于0时,会调用对象的dealloc用于析构对象,dealloc并不影响引用计数,永远不要显式调用它,要完全依赖引用计数机制

 

4.NSString/set等容器在对象加入后,对象的引用计数会加1以得到其控制权,移除对象时,对应引用计数减1,UIView对subView的控制,UINavigationController对其内的controller等其他类似容器的对象对其子对象也是一样的机制

 

5.循环引用:

a对象中一个方法alloc了一个b对象,并调用b对象的一个方法funb,b对象的funb中,alloc了一个c对象,调用了c对象的func,在c对象的func中,又alloc了一个b对象

这样下来,b对象的引用计数为2,c对象的引用计数为1

a对象处理完后调用b对象的release,本来希望释放掉b对象(a对b alloc一次,按理只要release一次就可以释放b),但是由于循环引用(b和c互相引用),导致b对象release一次后,引用计数还有1,没有释放,b和c都是在dealloc中释放对方的,b没有调用dealloc,c也就不会调用dealloc,这样就形成了b和c的内存泄漏

如何避免循环引用造成的内存泄漏呢:

以delegate模式为例(viewcontroller和view之间就是代理模式,viewcontroller有view的使用权,viewcontroller同时也是view的代理(处理view中的事件)):

UserWebService.h

 

  1. #import<UIKit/UIKit.h>    
  2. //定义一个ws完成的delegate    
  3. @protocol WsCompleteDelegate    
  4. @required    
  5. -(void) finished;//需要实现的方法    
  6. @end    
  7. @interface UserWebService:NSObject    
  8. {    
  9.  id <WsCompleteDelegate> delegate;//一个id类型的dategate对象    
  10. }    
  11. @property (assign) id <WsCompleteDelegate> delegate;    
  12. -(id)initWithUserData:(User *)user;    
  13. -(void)connectionDidFinishLoading:(NSURLConnection *)connection;    
  14. @end    

UserWebService.m:
#import <Foundation/Foundation.h> 
@systhesize delegate;//同步这个delegate对象 
@implementation UserWebService 
-(void)connectionDidFinishLoading:(NSURLConnection *)connection 

 

        [delegate finished]

@end 

LoginViewController.h:

#import "UserWebService.h" //包含含有委托的头文件
@interface LoginViewController:UIViewController <WsCompleteDelegate> 
-(void)submit; 
@end 

 

LoginViewController.m:

@implementation LoginViewController
-(void) submit 

User *user = [[User alloc]init]; 
[user setUserId:@"username"]; 
[user setPassword:@"password"]; 

ws = [[UserWebService alloc] initWithUserData:user]; 

ws.delegate = self;//设置委托的收听对象 

[user release]; 
[ws send]; 

//实现委托中的方法, 
-(void) finished 

NSAttry *users = [ws users]; 

@end 

可以看到,delegate声明为assign:

@property (assign) id <WsCompleteDelegate> delegate;

如果声明为retain会如何?

LoginViewController alloc了一个UserWebService,UserWebService的代理又是LoginViewController,这样就循环引用了

ws = [[UserWebService alloc] initWithUserData:user]; //UserWebService对象引用计数加1
ws.delegate = self;//LoginViewController对象引用计数加1

外部框架allocLoginViewController对象后,LoginViewController对象的引用计数为2 ,release后还是无法销毁,产生内存泄漏

所以用assign而不是retain声明属性可以避免循环引用,ARC下用弱引用也可以解决

6.内存检测可以用xcode继承的instrument工具,不过循环引用引起的内存泄露是检测不出来的

7.对象release到引用计数为0后,如果对应指针没有赋值为nil,怎出现野指针

8.ClassA *a = [[ClassA alloc] init];

  a = nil;//alloc的内存没有任何对象可以控制它了,引用计数永远为1,这就造成了内存泄露

9.简单的赋值操作并不会改变对象的引用计数:

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

ClassA *b = a;//a和b指向的对象的引用计数还是1

10.@property:

默认为@property为@property(atomic,assign)

nonatomic:  没有对应的atomic关键字,即使上面是这么写,但atomic叧是在你没有声明这个特性的时候系统默认,你无法主动去声明这一特性。nonatomic不支持多线程访问,atomic有同步机制,支持多线程访问,如果需要多线程访问,声明为atomic(维持默认),否则声明为nonatomic,因为nonatomic效率比atomic高得多

关于assign、retain和copy:  assign是系统默认的属性特性,它几乎适用亍OC的所有变量类型。对于非对象类型的变量,assign是唯一可选的特性。但是如果你在引用计数下给一个对象类型的变量声明为assign,那么你会在编译的时候收到一条来自编译器的警告。因为assign对于在引用计数下的对象特性,叧创建了一个弱引用(也就是平时说的浅复制)。返样使用变量会很危险。当你release了前一个对象的时候,被赋值的对象指针就成了无头指针了。因此在为对象类型的变量声明属性的时候,尽量少(或者不要)使用assign。 

关于assign合成的setter,看起来是这样的:  

-(void)setObjA:(ClassA  *)a  {   

objA  =  a;  

}    

在深入retain之前,先把声明为retain特性的setter写出来: 

 -(void)setObjA:(ClassA  *)a 

{   

If(objA != a) 

{  

[objA  release];  

objA  =  a; 

[objA  retain];  //对象的retain count 加1

 } 

明显的,在retain的setter中,变量retain了一次,那么,即使你在程序中  self.objA  =  a; 只写了这么一句,objA仍然需要release,才能保证对象的retain count 是正确的。但是如果你的代码  objA  =  a; 叧写了这么一句,那么这里只是进行了一次浅复制,对象的retain count 并没有增加,因此这样写的话,你不需要在后面release objA。  这2句话的区别是,第一句使用了编译器生成的setter来设置objA的值,而第二句叧是一个简单的指针赋值

11.NSString *str = [[NSString alloc] initwithstring @“abc”];

    str = @“abcd”;‘

    [str release];

    NSLog("%@",str);//打印出abcd

str为什么没有变成野指针呢?因为字符串常量(包括NSString和@“......”)的引用计数很大(100K+),基本上不会释放掉(由OC自己管理),所以字符串常量可以不用release

 

二、ARC(auto release count):自动引用计数方式:

这种方式使用强引用和弱引用来控制对象的释放,只要对象的强引用计数为0,对象就立即释放,变量默认为强引用

强/弱引用的数量是指向这个对象的强/弱指针的数量

弱引用:

在ARC方式下:

 

NSString __weak *string = [[NSString alloc] initWithFormat:@"First Name: %@", [self firstName]];
NSLog(@"string: %@", string);

打印出来的是null,因为这个NSString对象创建完成后,没有任何强引用指向它,所以立即释放了,并赋值为nil

 

 

__unsafe_unretained:弱引用变量声明,等同于assign

 

__weak:声明了一个可以自动nil化的弱引用,释放后,变量自动置为nil

 

__strong:强引用,等同于retain
ARC在IOS4.0及以后的系统都可以用,但是weak声明必须在ios5及以后才可以用,如果启用了RAC,选择目标运行平台为ios4,xcode会自动插入一个兼容库到工程中,以实现自动兼容

ARC引入的新规则:

ARC能够起作用引入了一些新的规则。这些规则定义了所有的内存管理方方面面,某些规则是为了更好的体验,还有一些规则是为了减少编程人员在内存管理方面的工作。如果你违反这些规则,就会得到编译期的错误,而不是运行期的错误。

  • 你不能显示调用dealloc,不能实现和显示调用retain,release,retainCount和autorelease。

    当然也不能使用@selector(retain)@selector(release)等相关的功能。

  • 你不能在C结构中使用实例指针。

    与其使用结构,建议你使用Objective-C类来管理数据。

  • id和void *不能进行隐式转换。

    你必须显式的告诉编译器这个转换的类型。

  • 不能使用NSAutoreleasePool的实例.

    作为替代,ARC提供@autoreleasepool块作为替代。后者提供了更灵活的方式。

为了和手动管理内存相兼容,ARC定义了函数和变量命名一条规则:

  • 属性变量的命名不能使用new开始。

     
posted @ 2013-01-25 10:25  六界剑仙  阅读(103)  评论(0)    收藏  举报