码迷,mamicode.com
首页 > 移动开发 > 详细

iOS内存管理原则

时间:2015-04-26 22:40:43      阅读:227      评论:0      收藏:0      [点我收藏+]

标签:

1.  内总管理原则(引用计数)
    IOS的对象都继承于NSObject,   该对象有一个方法:retainCount ,内存引用计数。 引用计数在很多技术都用到: window下的COM组件,多线程的信号量,读写锁,思想都一样。
    
   (一般情况下: 后面会讨论例外情况)
    alloc      对象分配后引用计数为1
    retain    对象的引用计数+1
    copy      copy 一个对象变成新的对象(新内存地址) 引用计数为1 原来对象计数不变
   
    release            对象引用计数-1 如果为0释放内存
    autorelease      对象引用计数-1 如果为0不马上释放,最近一个个pool时释放    
    内存管理的原则就是最终的引用计数要平衡,
   如果最后引用计数大于0  则会内存泄露
   如果引用 计数等于0还对该对象进行操作,则会出现内存访问失败,crash    所以尽量设置为nil
   
   这两个问题都很严重,所以请一定注意内存释放和不用过后设置为nil
2. autoReleasePool
   每个工程都有一个 main.m 文件: 内容如下:
   int main(int argc, char *argv[]) {
   
    NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
    int retVal = UIApplicationMain(argc, argv, nil, nil);
    [pool release];
    return retVal;
}
   很明显C语言的main 函数: 
  NSAutoreleasePool 是用来做autorelease 变量释放的,前面说了, autorelease不会马上释放,当他到了最近的pool release 时会检查reatin count 是不是为0, 为0就释放。 
当我们在一段代码时加入了大量的autorelease变量时,我们应该为这段代码加上
Autoreleasepool,其它时候不用
在返回一个对象时常常要返回一个autorelease 对象,因为objectC 对象都是动态内存,没有
栈的概念,所以不能像C/C++一样返回一个对象到栈,只能用autorelease对象。
3. 成员变量与属性
    实际情况并非上面那么简单,你可能需要在一个函数里调用另一个函数分配的变量这时候
有两个选择:  类成员变量和使用属性
   @interface TestMem: NSObject {
            TestObject   *m_testObject ;            // 成员变量
            TestObject   *testObject;                 //成员变量
    }
 成员变量与上面的内存管理是一致的,只是在不同的函数里要保持引用计数加减的平衡
所以要你要每次分配的时候检查是否上次已经分配了。是否还能调用 
什么时候用属性?
  1. 把成员做为public.
  2. outlet 一般声明为属性( 这个内存于系统控制,但我们还是应该做一样操作,后面会讲)
  3. 如果很多函数都需要改变这个对象 ,或这个函数会触发很多次,建议使用属性。我们看看属性函数展开后是什么样子:

// assign
-(void)setTestObject :(id)newValue{
    testObject= newValue;
}
// retain
-(void)setTestObject  :(id)newValue{
    if (testObject!= newValue) {
        [testObject release];
        testObject= [newValue retain];
    } 
}
// copy
-(void)setTestObject :(id)newValue{
    if (testObject != newValue) {
        [testObject release];
        testObject = [newValue copy];
    }
}
asssign 相于于指针赋值,不对引用计数进行操作,注意原对象不用了,一定要把这个设置为nil
retain 相当于对原对象的引用计数加1
copy 不对原对象的引用计数改变,生成一个新对象引用计数为1
注意:
  self.testObject 左值调用的是setTestObject 方法. 右值为get方法,get 方法比较简单不用说了
  而 真接testObject 使用的是成员变量
  self.testObject = [[testObject alloc] init];   // 错  reatin 两次
  testObject =   [NSArray objectbyindex:0];  //错 不安全,没有retain 后面release会出错
                                                                  如果testObject已有值也会mem leak       
4. 自动管理对象
   IOS 提供了很多static(+) 创建对象的类方法,这些方面是静态的,可以直接用类名
调用如:
   NSString *testString = [NSString stringWithFormat:@"test" ];
   testString 是自动管理的对象,你不用relese 他,他有一个很大的retain count, release后数字不变。
  
5. 例外
   有一些通过alloc 生成的对象相同是自动管理的如:
   NSString  *testString  = [[NSString alloc] initWithString:@"test1"];
   retain count 同样是很大的数,没办法release
   但为了代码对应,还是应该加上[ testString release];
   不然xcode的Analyze 会认识内存leak, 但Instruments leak 工具检测是没有的
6.view  内存管理
   通常我们编程会用到view,   在view 中的方法:
   
  viewDidload 
  didReceiveMemoryWarning 
 viewDidUnload 
@property (retain) NSArray *iarrTestMem
        
        viewDidLoad
            init retain is         0
            (alloc)               +1 =1
                                    
        if(memwarning)
               didrecivememwarning  -0 =1
                                               
               didviewunload        -1
                                           =0  
                                  will load  viewDidLoad when next load view
        esle
               dealloc:             -1
                                      =0
    我们来看一个变量的生命周期 
 
   当view被alloc
     A. 框架会自动调用viewDidLoad
         一般来说在viewDidLoad 中分配变量: 
         假设为alloc 现在变量的reatin 为 0 +1 = 1;
     
     第一种情况:
      B. 如果这时候view被释放,会调用 dealloc, 
        这时候我们应该在dealloc里release 这个变量现在为0
     
     第二种情况:
       B. 这时候view被换到下层,如navigation的上级,不可显示状态:如果
          系统内存吃紧: 系统会发 消息,我们的didrecivememwarning 函数
          被调用 ,该函数是提醒我们应该释放一些现在用不上的东西,特别是一些较大的
          如图片,cache 数据等, 注意如果在这里释放了,代码在用的地方要进行
          判断,是否需要重新加载
       C. 他会调用didviewunload
          这时候我们要注意了,因为他调用了didviewunload,在下次这个view被显示的时候
         会再次调用didviewload, 我们在didviewload里分配的娈童就会被分配两次,所以我
         们在这里一定要释放didview里分配的变量,不管直接分配的还是间接分配的,
         如发消息给其它函数分配的。 那我们在下次分配之前释放一次行不行, 不行,因为这
         时候内存吃紧了,你应该多释放内存,不然你的程序可能会被系统kill. 还有虽然对
         [xxx release]  对nil 发 release 是没问题的,但逻辑上让人觉得很奇怪,以为在别的
         地方分配过。 所以这里应该释放内存,
            如果你是一个属性,用:
                  self.xxx = nil 比较好,他相当于帮你释放了原来的,还把xxx设置为了nil.
           如果不是一个属性 :也最好把xxx= nil.  这样比较安全。如果这个view不再被换入
          下一步直接调用 dealloc, 这时候你的dealloc里的[xxx release], 如果这里的xxx不为nil你在
          didviewunload里release 了,就非常危险
        D. 如果这时候view被释放,会调用 dealloc, 
           这时候我们应该在dealloc里release 
    
前面我们说了outlet的内存没办法管理(看下生命周期)
   
   
  @property (nonatomic,retain) IBOutlet UILabel *ilblTestMem;
         init retain is 2
         
         didviewLoad:     
                              = 2
         if(memwarning)
                 memwaring       - retain count -1
                                         = 1
                viewdidunload:   -1  
                                         =0
                              will load  viewDidLoad when next load view
        else
             dealloc:         -1
                                =1
      第一种情况
       A. didviewLoad:
                retain count 为2 
       B.  dealloc:
                 -1   retain count 为1
      第二种情况:
       B.   memwarning 
               系统会把 retain count 减到1
       C.   viewdidunload
               我们应该release 一次 这样 retain count 到0,变量会马上dealloc,更快的释放内存
               注意用 self.ilblTestMem = nil  设置为nil,  为 dealloc 做准备
       D.  dealloc:
              -1  

iPhone/Mac Objective-C内存管理教程和原理剖析

 

前言

初学objectice-C的朋友都有一个困惑,总觉得对objective-C的内存管理机制琢磨不透,程序经常内存泄漏或莫名其妙的崩溃。我在这里总结了自己对objective-C内存管理机制的研究成果和经验,写了这么一个由浅入深的教程。希望对大家有所帮助,也欢迎大家一起探讨。

 

此文涉及的内存管理是针对于继承于NSObject的Class。

一 基本原理

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

 

细心的读者肯定能发现这个对象没有被销毁,何时销毁呢?谁去销毁它?

 

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

运行时通过监控工具可以发现使用的内存在急剧增加,直到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

 

二 口诀与范式

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];

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

         objA.objB = objB1;

}

 

三 @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 *) getObjB;

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

@end

 

@implementation ClassA

-(ClassB*) getObjB

{

}

 

-(void) setObjB:(ClassB*) value

{

}

 

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

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

-(void) dealloc

{

}

 补充说明

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

NSString *str1 = @”constant string”;

str1的retain count是个很大的数字。Objective-C对常量字符串做了特殊处理。

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

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

iOS内存管理原则

标签:

原文地址:http://www.cnblogs.com/huangzs/p/4458403.html

(0)
(0)
   
举报
评论 一句话评论(0
登录后才能评论!
© 2014 mamicode.com 版权所有  联系我们:gaon5@hotmail.com
迷上了代码!