首页 > 代码库 > OC语言--内存管理

OC语言--内存管理

1.内存管理原理的介绍

 

1.1C的内存管理

char *p = (char *)malloc(100*sizeof (char)); 

这是C的动态内存分配,我们手动跟系统申请了100个字节的内存;或者说系统在堆里开辟了100个字节的空间,并将这个空间的首地址返回给指针变量p。

 

strcpy(p,"Hello World!");

将字符串拷贝给指针变量p指向的内存空间。

puts(p);

将p指针指向的内存空间里的字符串打印出来。

free(p);

使用完成后,手动跟系统释放内存空间;或者说系统回收空间。如上就是C里简单的内存管理。

 

C的内存管理,我们手动申请,手动释放。

这样来看,我们只需要注意两个问题就好了:

1,申请内存,使用完成后需要释放,如果不释放会造成内存泄漏。

2,不能多次释放,如果多次释放,则会崩溃。

但是,如果项目比较复杂,需要有几十上百号人一起分工完成,就很容易出现问题。

比方说我们开辟了一块内存空间,里面存放了一块很有用的数据。但是,这个数据不只有我在这一块代码里用,甚至有多个人,在程序的多个地方使用。这样造成的结果就是,就算我使用完成这块内存,我也不能去释放他,因为我不能确定,别人在别的地方是否还需要使用这块内存。内存泄露在所难免了。

 

2.OC的内存管理方式:引用计数

 

2.1引用计数

对于一块动态申请的内存,有一个人(指针)使用,就给这个内存的计数器(该计数器在该对象中)加1,使用完成后,就给这个计数器减1,当这个内存的引用计数为0了,我们则释放他,这样,上面的问题就解决了。OC,就是使用引用计数这种方式来管理内存的。

 

2.2内存管理的黄金法则

对于引用计数来说,有一套内存管理的黄金法则:

The basic rule to apply is everything that increases the reference counterwith alloc, [mutable]copy[withZone:] or retain is in charge of the corresponding [auto]release.

 

如果对一个对象使用了alloc、[mutable]copy、retain,那么你必须使用相应的release或者autorelease。

 通俗一点的说法就是谁污染谁治理。

 

2.3MRC和ARC

ARC Automatic Reference Counting,自动引用计数,由xcode,帮我们去管理内存。

MRC Manual  Reference Counting,手动引用计数,由我们手动管理内存。

 

但就目前来看,很多公司还是使用MRC.

 

2.4 如何将工程改为MRC

xcode5,工程创建的时候是ARC的,我们如果想要MRC,需要进行如下设置。

选中工程 - >target - >Bulid Settings - >搜索:automatic reference counting或auto,将Objective-C Automatic Reference Counting改为NO。

 

 

3.手动内存管理的操作(MRC)

 

3.1alloc与release

创建一个新的工程,先别将内存管理改为手动

创建一个Dog类

  

[objc] view plaincopy
  1. @interface Dog : NSObject  
  2.   
  3.   @end  
  4.   
  5.    
  6.   
  7.   @implementation Dog  
  8.   
  9.   - (void)dealloc  
  10.   
  11.   {  
  12.   
  13.     NSLog(@"dog dealloc");  
  14.   
  15.     [super dealloc];  
  16.   
  17.   }  
  18.   
  19.   @end  

 

 

  dealloc里的析构函数,当对象销毁的时候,会自动调用这个方法,我们在这里重写这个方法。

 

  在main函数里,写入如下代码:

   

[objc] view plaincopy
  1. int main(int argc, const charchar * argv[])  
  2.   
  3.   {  
  4.   
  5.     @autoreleasepool {  
  6.   
  7.              Dog *dog = [[Dog alloc] init];  
  8.   
  9.      }  
  10.   
  11.     NSLog(@"程序即将退出");  
  12.   
  13.     return 0;  
  14.   
  15.   }  

 

  从终端打印信息来看,程序即将退出这条打印之前,已经打印dog dealloc,也就是说在程序运行结束前,dog对象已经销毁了。这个是ARC,由xcode帮我们管理dog对象。

 

  将ARC改为MRC,再执行程序,dog对象并没有销毁,因为我们现在是手动管理了,我们需要遵守内存管理的黄金法则;Dog *dog = [[Dog alloc] init]; 我们需要对dog进行release。

 

将main函数代码改为如下形式:

[objc] view plaincopy
  1. int main(int argc, const charchar * argv[])  
  2.   
  3. {  
  4.   
  5.     @autoreleasepool {  
  6.   
  7.         Dog *dog = [[Dog alloc] init];  
  8.   
  9.         [dog release];  
  10.   
  11.      }  
  12.   
  13.     NSLog(@"程序即将退出");  
  14.   
  15.     return 0;  
  16.   
  17. }  

 

再次执行程序,从打印可以看出,dog对象已经销毁。这就是黄金法则,我们对dog进行alloc,就要对dog进行release。

注意,release 并不是销毁对象,而是让对象的引用计数减1,当对象的引用计数快为0的时候,自动调用dealloc方法并销毁对象。

 

3.2 retain与retainCount

retain,将对象进项保留操作,也就是使对象的引用计数加1。

retainCount,打印一个对象的引用计数。

 

将main函数代码改为如下形式:

 

[objc] view plaincopy
  1. int main(int argc, const charchar * argv[])  
  2.   
  3.   {  
  4.   
  5.     @autoreleasepool {  
  6.   
  7.         Dog *dog = [[Dog alloc] init];  
  8.   
  9. //此时打印的结果,retainCount值为1,  
  10.   
  11. //也就是我们alloc,创建dog对象时,对象的引用计数为1  
  12.   
  13. NSLog(@"dog retainCount = %lu",[dog retainCount]);  
  14.   
  15.    
  16.   
  17. //dog1指针要使用(引用)dog对象,  
  18.   
  19. //此时,为避免dog对象进行release,  
  20.   
  21. //使得引用计数减1变为0,销毁对象,  
  22.   
  23. //我们进行了retain操作。  
  24.   
  25. Dog *dog1 = [dog retain];  
  26.   
  27. //此时打印的结果,retainCount值为2  
  28.   
  29. NSLog(@"dog retainCount = %lu",[dog retainCount]);  
  30.   
  31. Dog *dog2 = [dog retain];  
  32.   
  33. //此时打印的结果,dog,dog1,dog2,retainCount值都为3,  
  34.   
  35. //因为这三个指针指向同一个对象。  
  36.   
  37. NSLog(@"dog retainCount = %lu",[dog retainCount]);  
  38.   
  39. NSLog(@"dog1 retainCount = %lu",[dog1 retainCount]);  
  40.   
  41. NSLog(@"dog2 retainCount = %lu",[dog2 retainCount]);  
  42.   
  43.    
  44.   
  45.  //release 并不是销毁对象,让对象的引用计数减1  
  46.   
  47.  [dog release];  
  48.   
  49. //此时打印的结果,dog,dog1,dog2,retainCount值都为2,  
  50.   
  51. //虽然dog执行了release,但dog指针还是指向那个对象。  
  52.   
  53. //此时dog对对象只有使用权,而没有拥有权。  
  54.   
  55.    
  56.   
  57.   NSLog(@"dog retainCount = %lu",[dog retainCount]);  
  58.   
  59.   NSLog(@"dog1 retainCount = %lu",[dog1 retainCount]);  
  60.   
  61.   NSLog(@"dog2 retainCount = %lu",[dog2 retainCount]);  
  62.   
  63.   [dog1 release];  
  64.   
  65.   [dog2 release];  
  66.   
  67. //执行完上面两句话的时候,dog对象就销毁了。  
  68.   
  69. //虽然这里我们可以写两句[dog release];  
  70.   
  71. //也能达到同样的效果,但是,务必不要这样写,  
  72.   
  73. //我们要遵守内存管理的黄金法则:  
  74.   
  75. Dog *dog = [[Dog alloc] init]; // 这是对dog指针进行alloc,需要对应[dog release];  
  76.   
  77. Dog *dog1 = [dog retain]; //这是对dog1指针进行retain,需要对应[dog1 retain];  
  78.   
  79.    
  80.   
  81. //这时候打印dog的retainCount是错误的用法!!  
  82.   
  83. //因为对象已经销毁了!! 对一个已经销毁的对象发送消息是逻辑错误的!  
  84.   
  85. //会造成程序的崩溃,  
  86.   
  87. //因为dog对象已经销毁了,没法调用dog对象的方法。  
  88.   
  89. //注意,如果上面不加两行打印的话,可能不会崩溃。  
  90.   
  91.         NSLog(@"dog retainCount = %lu",[dog retainCount]);  
  92.   
  93.      }  
  94.   
  95.     NSLog(@"程序即将退出");  
  96.   
  97.     return 0;  
  98.   
  99.   }  

 

 

3.3 类的复合中使用

在上面代码中,增加Person类

[objc] view plaincopy
  1. @interface Person : NSObject {  
  2.   
  3.   // 一个人,养了一条狗  
  4.   
  5.     Dog *_dog;  
  6.   
  7.   }  
  8.   
  9.   - (void)setDog:(Dog *)dog;  
  10.   
  11.   - (Dog *)dog;  
  12.   
  13.   @end  

 

 setDog方法形式:

 

   

[objc] view plaincopy
  1. @implementation Person  
  2.   
  3.   /* 人并没有真正持有狗, 
  4.  
  5. 如果在main函数里[dog release],让dog的引用计数减1,就变为0, 
  6.  
  7. dog就销毁了。 
  8.  
  9.     - (void)setDog:(Dog *)dog 
  10.  
  11.   { 
  12.  
  13.     _dog = dog; 
  14.  
  15.   } 
  16.  
  17.     */  
  18.   
  19.    
  20.   
  21.   /* 如果人再持有别的狗, 
  22.  
  23. 就会造成第一条狗得不到释放,内存泄露。 
  24.  
  25.   - (void)setDog:(Dog *)dog 
  26.  
  27.   { 
  28.  
  29.     _dog = [dog retain]; 
  30.  
  31.   } 
  32.  
  33.     */  
  34.   
  35.    
  36.   
  37.   /* 如果本来持有一条狗,又重新设置这条狗,先进行release, 
  38.  
  39. 这个时候,很可能dog就销毁了,然后,就没法再次retain了。 
  40.  
  41.   - (void)setDog:(Dog *)dog 
  42.  
  43.   { 
  44.  
  45.     [_dog release]; 
  46.  
  47.     _dog = [dog retain]; 
  48.  
  49.   } 
  50.  
  51.     */  
  52.   
  53.    
  54.   
  55.   // 标准写法  
  56.   
  57.   - (void)setDog:(Dog *)dog  
  58.   
  59.   {  
  60.            if (_dog  !=  dog) {  
  61.   
  62.         [_dog release];  
  63.   
  64.         _dog = [dog retain];  
  65.   
  66.            }  
  67.   }  
  68.   
  69.   - (Dog *)dog  
  70.   
  71.   {  
  72.      return _dog;  
  73.   }  
  74.   
  75.   - (void)dealloc  
  76.   {  
  77.    NSLog(@"person dealloc");  
  78.   
  79. // 人在销毁的时候,一并将持有的dog对象销毁  
  80.   
  81.     [_dog release];  
  82.   
  83.     [super dealloc];  
  84.   
  85.   }  
  86. @end  

 

 错误方法分析:

 

   

[objc] view plaincopy
  1. //第一个setDog:方法对应的错误  
  2.   
  3.           Dog *xiaoBai = [[Dog alloc] init];  
  4.   
  5.           Person *xiaoXin = [[Person alloc] init];  
  6.   
  7.              [xiaoXin setDog:xiaoBai];  
  8.   
  9.   //引用计数为1  
  10.   
  11.              NSLog(@"count = %lu",xiaoBai.retainCount);  
  12.   
  13.              [xiaoBai release];  
  14.   
  15.   //此时狗已经销毁了,因此,xiaoXin需要持有这条狗。  
  16.   
  17.              [xiaoXin release];  
  18.   
  19.    
  20.   
  21.  // 第二个setDog:方法对应的错误  
  22.   
  23.                Dog *xiaoBai = [[Dog alloc] init];  
  24.   
  25.                 Person *xiaoXin = [[Person alloc] init];  
  26.   
  27.                 [xiaoXin setDog:xiaoBai];  
  28.   
  29.   //引用计数为2  
  30.   
  31.                NSLog(@"count = %lu",xiaoBai.retainCount);  
  32.   
  33.                [xiaoBai release];  
  34.   
  35.                Dog *xiaoHei = [[Dog alloc] init];  
  36.   
  37.                [xiaoXin setDog:xiaoHei];  
  38.   
  39.                [xiaoHei release];  
  40.   
  41.                [xiaoXin release];  
  42.   
  43.   //此时xiaoBai这条狗没有释放  
  44.   
  45.    
  46.   
  47.   //第三个setDog:方法对应的错误  
  48.   
  49.                Dog *xiaoBai = [[Dog alloc] init];  
  50.   
  51.                 Person *xiaoXin = [[Person alloc] init];  
  52.   
  53.                 [xiaoXin setDog:xiaoBai];  
  54.   
  55.   //引用计数为2  
  56.   
  57.                NSLog(@"count = %lu",xiaoBai.retainCount);  
  58.   
  59.                [xiaoBai release];  
  60.   
  61.   //这样设置是不对的,因为在setDog:里,将dog进行release的时候,  
  62.   
  63.   //引用计数为0,dog就销毁了,无法再retain了。  
  64.   
  65.                [xiaoXin setDog:xiaoBai];  
  66.   
  67.                [xiaoXin release];  
  68.   
  69.   //另外,这里还要说明,类里,类外,都需要遵守内存管理。  

 

 

3.4 @property retain,assign,copy展开

i.) retain展开

如上代码里,Person的setter和getter方法,也可以用property,写成如下形式:

@property (nonatomic, retain) Dog *dog;

进行如上测试,都没有问题。

因此,实际如果写成这样@property (nonatomic, retain) Dog *dog;,

则会展开如下:

 

[objc] view plaincopy
  1. - (void)setDog:(Dog *)dog  
  2.   
  3.   {  
  4.   
  5.     if (_dog != dog) {  
  6.   
  7.              [_dog release];  
  8.   
  9.              _dog = [dog retain];  
  10.   
  11.       }  
  12.   
  13.   }  
  14.   
  15.    
  16.   
  17.   - (Dog *)dog  
  18.   
  19.   {  
  20.   
  21.     return _dog;  
  22.   
  23.   }  
  24.   
  25.    


ii.) assign展开

  @property (nonatomic, assign) Dog *dog;,assign是直接复制,

则会展开如下:

 

[objc] view plaincopy
  1. - (void)setDog:(Dog *)dog  
  2.   
  3.   {  
  4.   
  5.          _dog = dog;  
  6.   
  7.   }  
  8.   
  9.   - (Dog *)dog  
  10.   
  11.   {  
  12.   
  13.      return _dog;  
  14.   
  15.   }  


 

  iii.) copy展开

  @property (nonatomic, copy) Dog *dog;,copy,拷贝,

将原来的对象拷贝一份出来,展开如下:

 

[objc] view plaincopy
  1. - (void)setDog:(Dog *)dog  
  2.   
  3.   {  
  4.   
  5.     if (_dog != dog) {  
  6.   
  7.           [_dog release];  
  8.   
  9.         _dog = [dog copy];  
  10.   
  11.       }  
  12.   
  13.   }  
  14.   
  15.   - (Dog *)dog  
  16.   
  17.   {  
  18.   
  19.      return _dog;  
  20.   
  21.   }  


 

3.5 字符串内存管理

  i.) 字符串的内存管理

  对于字符串而言,非常不遵守黄金法则! (如果从字符串的引用计数来看,乱七八糟!)这只是一个表象! 其实内部还是遵循的!!

  我们要做的是,我们依旧遵守我们的黄金法则!

 

[objc] view plaincopy
  1. NSString *str = [[NSString alloc] initWithFormat:@"%d %s",1,"hello"];  
  2.   
  3.         NSLog(@"count1 = %lu",str.retainCount);  
  4.   
  5.         NSString *str2 = @"hello";  
  6.   
  7.          NSLog(@"count2 = %lu",str2.retainCount);  
  8.   
  9.         NSString *str3 = [str retain];  
  10.   
  11.         NSString *str4 = [str2 retain];  
  12.   
  13.         NSLog(@"count3 = %lu",str.retainCount);  
  14.   
  15.         NSLog(@"count4 = %lu",str2.retainCount);  
  16.   
  17.         NSString *str5 = [[NSString alloc] initWithString:str];  
  18.   
  19.         NSString *str6 = [[NSString alloc] initWithString:str2];  
  20.   
  21.         NSLog(@"count5 = %lu",str5.retainCount);  
  22.   
  23.         NSLog(@"count6 = %lu",str6.retainCount);  
  24.   
  25.         // NSString *str7 = [NSString stringWithFormat:@"%d",5];  
  26.   
  27.        [str release];  
  28.   
  29.        [str3 release];  
  30.   
  31.        [str4 release];  
  32.   
  33.        [str5 release];  
  34.   
  35.        [str6 release];  
  36.   
  37.        // str7不用release!!  

 

        

因此,如果是NSString,我们的property格式写成如下: @property (nonatomic, copy) NSString *name;

 

ii.) copy和mutableCopy

        

[objc] view plaincopy
  1. NSMutableString *string = [[NSMutableString alloc] initWithString:@"hello"];  
  2.   
  3.    
  4.   
  5.         // 这样写会崩溃,看对象,不看指针  
  6.   
  7.         // copy 将(可变或不可变)字符串拷贝成不可变字符串,string2 实际是不可变字符串  
  8.   
  9.         // NSMutableString *string2 = [string copy];  
  10.   
  11.    
  12.   
  13.         // [string2 appendString:@"world"];  
  14.   
  15.         NSString *string2 = [string copy];  
  16.   
  17.         NSLog(@"string2 = %@",string2);  
  18.   
  19.    
  20.   
  21.         // mutableCopy 将(可变或不可变)字符串拷贝成可变字符串  
  22.   
  23.         NSMutableString *string3 = [string2 mutableCopy];  
  24.   
  25.         [string3 appendString:@"world"];  
  26.   
  27.         NSLog(@"string3 = %@",string3);  
  28.   
  29.    
  30.   
  31.         // 不用管它的引用计数是多少,我们遵守我们自己的黄金法则就够了  
  32.   
  33.         [string release];  
  34.   
  35.         [string2 release];  
  36.   
  37.         [string3 release];  
  38.   
  39.         // UI里,也不要随便打印retainCount, 各人顾各人  
  40.   
  41.         // new 相当于alloc init,在OC或IOS里几乎不用!!  

 

 

3.6 数组的内存管理

[objc] view plaincopy
  1. int main(int argc, const charchar * argv[])  
  2.   
  3. {  
  4.   
  5.     @autoreleasepool {  
  6.   
  7.    
  8.   
  9.         Dog *dog1 = [[Dog alloc] init];  
  10.   
  11.         Dog *dog2 = [[Dog alloc] init];  
  12.   
  13.         Dog *dog3 = [[Dog alloc] init];  
  14.   
  15.         Dog *dog4 = [[Dog alloc] init];  
  16.   
  17.         NSLog(@"dog1.retainCount = %lu",dog1.retainCount);  
  18.   
  19.         NSMutableArray *array = [NSMutableArray arrayWithObjects:dog1,   
  20.   
  21. dog2, dog3, dog4, nil nil];  
  22.   
  23.         NSLog(@"dog1.retainCount = %lu",dog1.retainCount);  
  24.   
  25.    
  26.   
  27.         [array addObject:dog1];  
  28.   
  29.         NSLog(@"dog1.retainCount = %lu",dog1.retainCount);  
  30.   
  31.    
  32.   
  33.         // NSLog(@"array = %@",array);  
  34.   
  35.         [array removeLastObject];  
  36.   
  37.         NSLog(@"dog1.retainCount = %lu",dog1.retainCount);  
  38.   
  39.    
  40.   
  41.         // 新的array不是我们alloc new... 的,我们不需要release  
  42.   
  43.         // [array release];  
  44.   
  45.         NSLog(@"dog1.retainCount = %lu",dog1.retainCount);  
  46.   
  47.         [dog1 release];  
  48.   
  49.         [dog2 release];  
  50.   
  51.         [dog3 release];  
  52.   
  53.         [dog4 release];  
  54.   
  55.    
  56.   
  57.     }  
  58.   
  59.     //for @autoreleasepool  
  60.   
  61.     return 0;  
  62.   
  63. }  

 

 

结论

  1)当我们创建数组的时候,数组会对每个对象进行引用计数加1

  2)当数组销毁的时候,数组会对每个对象进行引用计数减1

  3)当我们给数组添加对象的时候,会对对象进行引用计数加1

  4)当我们给数组删除对象的时候,会对对象进行引用计数减1

  总之,谁污染谁治理,管好自己就可以了。

 

3.7 autorelease与 autoreleasepool

autoreleasepool是一个对象

autorelease 是一个方法

 

在main函数里写如下代码:

[objc] view plaincopy
  1. int main(int argc, const charchar * argv[])  
  2.   
  3.   {  
  4.   
  5.     @autoreleasepool {  
  6.   
  7.       Dog *dog = [[Dog alloc] init];  
  8.   
  9. //dog并没有马上销毁,而是延迟销毁,  
  10.   
  11. //将dog对象的拥有权交给了autoreleasepool  
  12.   
  13.     [dog autorelease];  
  14.   
  15. //这个是可以打印的,因为打印完dog的引用计数后,  
  16.   
  17. //dog对象才销毁  
  18.   
  19. @autoreleasepool{  
  20.   
  21.      [dog autorelease]  
  22.   
  23. }  
  24.   
  25.     NSLog(@"retainCount = %lu",dog.retainCount);  
  26.   
  27.      }  
  28.   
  29.     NSLog(@"程序即将退出");  
  30.   
  31.     return 0;  
  32.   
  33.   }  

 

 注意: autoreleasepool相当于一个数组,如果哪个对象发送autorelease消息,实际将对象的拥有权交给了autoreleasepool;当autoreleasepool销毁的时候,将向autoreleasepool里持有的所有对象都发送一个release消息。

 

3.8 加方法的内存管理 

  我们用加方法创建的对象,不用我们release,是因为类内部的实现使用了autorelease,延迟释放。

 

  在Dog类的声明里增加一个加方法

  + (id)dog;

  在Dog类的实现里进行实现

  + (id)dog

  {

/*注意,这里不要写成release,如果是release,那么刚创建就销毁了,

使用autorelease,使得将对象的拥有权交给了自动释放池,

只要自动释放池没有销毁,dog对象也就不会销毁。*/

 

return [[[Dog alloc] init] autorelease];

  }

 

  在main函数里代码如下:

   

[objc] view plaincopy
    1. int main(int argc, const charchar * argv[])  
    2.   
    3.   {  
    4.   
    5.     @autoreleasepool {  
    6.   
    7.              Dog *dog = [[[Dog alloc] init] autorelease];  
    8.   
    9.              Dog *dog1 = [Dog dog];  
    10.   
    11.              NSLog(@"count = %lu",dog.retainCount);  
    12.   
    13.              NSLog(@"count1 = %lu",dog1.retainCount);  
    14.   
    15.       }  
    16.   
    17.   } 

OC语言--内存管理