Objective-C内存管理

来源:互联网 发布:全民k歌改分软件 编辑:程序博客网 时间:2024/06/07 11:37

Obj-C中的内存管理不同于C语言式的完全手动管理方式,malloc和free操控内存,也不同于GC(Gabage Collection)语言(例如java&ruby等)的自动回收方式,在Obj-C中的内存管理方式采用的是比较折中的方式,也就是手动和半自动结合,采用自动对象释放池进行自动管理或者采用引用计数值进行手动管理。

1.      引用计数值

至于为什么要进行内存管理,这里我想大家应该基本了解了,即使我这种不太了解底层的码畜都知道一些,那么内存管理就是CPU内存资源的分配和回收,良好的内存管理机制在于尽量控制CPU内存资源的浪费。

Obj-C中语言中引用计数值的管理方式,对象每次alloc1次计数值为1同时获得内存,每retain 1次计数值加1,每release 1次计数值减1,直到计数值为0的时候,对象就会被销毁,主要的操控接口如下:

        Alloc、allocWithZone、new(并且初始化)——为对象分配内存,计数值+1,返回实例

         Release——计数值-1

         Retain——计数值+1

         Copy、mutableCopy——复制一个实例,计数值为1,返回实例,所得到的对象是一个全新的对象

         Autorelease在当前的上下文的autoreleasePool栈顶添加对象,由于它的引入使Obj-C的内存管理方式由全手动上升为半自动。

那么我们来看一段代码实例,演示下内存的计数值管理

[cpp] view plaincopy
  1. #import <Foundation/NSObject.h>  
  2. #import <Foundation/NSAutoreleasePool.h>  
  3. #import <Foundation/NSString.h>  
  4. #import <Foundation/NSArray.h>  
  5. #import <Foundation/NSValue.h>  
  6. Int main (int argc, char * argv[])  
  7. {  
  8.          NSAutoreleasePool* pool=[[NSAutoreleasePool alloc] init];  
  9.          NSNumber*myInt=[[NSNumber alloc] initWithInt: 100];//实例化一个NSNumber对象myInt,并且调用alloc方法,其引用计数值加1,为+1  
  10.          NSLog(@”myInt retain count=%lx”, (unsigned long) [myInt retainCount]);  
  11.          //输出myInt的引用计数  
  12.          [myInt retain];//myInt对象调用retain方法,引用计数值再+1,为+2  
  13.          NSLog(@”myInt retain count after retain=%lx”, (unsigned long) [myInt retainCount]);  
  14.          //输出myInt的引用计数  
  15.          [myInt release];//myInt对象调用release方法,引用计数值-1,为+1,此时myInt对象仍然存在,并未释放  
  16.          NSLog(@”myInt retain count after release=%lx”, (unsigned long) [myInt retainCount]);  
  17.          //输出myInt的引用计数值  
  18.          NSNumber* secant=[myInt copy];//myInt调用copy方法,返回引用计数为1的实例,此时secInt的引用计数值为1,myInt的引用计数值不变  
  19.          NSLog(@” secant retain count after copy=%lx”, (unsigned long) [secant retainCount]);  
  20.          [secInt release];//secInt调用release方法,引用计数-1,为0,secInt对象被彻底释放  
  21.          [myInt release];//myInt对象调用release方法,应用计数-1,为0,myInt对象被彻底释放  
  22.          [pool drain];  
  23.          return 0;  
  24. }  


程序的最终输出结果如下:

myInt retain count=1

myInt retain count after retaine=2

myInt retain count after release=1

secInt retain count after copy=1

 

上述这段代码演示了计数机制是如何工作的,简单的规则就是,alloc和retain会让对象引用计数+1,release会让对象的引用计数-1,而copy对象则是只会将新的对象的引用计数+1,所以简单的规避内存泄露的一个方法就是有多少个alloc和retain,就要有多少个相应的release。

自动释放池

在ObjC中也有一种内存自动释放的机制叫做“自动引用计数”(或“自动释放池”),与C#、Java不同的是,这只是一种半自动的机制,有些操作还是需要我们手动设置的。自动内存释放使用@autoreleasepool关键字声明一个代码块,如果一个对象在初始化时调用了autorelase方法,那么当代码块执行完之后,在块中调用过autorelease方法的对象都会自动调用一次release方法。这样一来就起到了自动释放的作用,同时对象的销毁过程也得到了延迟(统一调用release方法)。看下面的代码:

Person.h

////  Person.h//  MemoryManage////  Created by Kenshin Cui on 14-2-15.//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>@interface Person : NSObject#pragma mark - 属性#pragma mark 姓名@property (nonatomic,copy) NSString *name;#pragma mark - 公共方法#pragma mark 带参数的构造函数-(Person *)initWithName:(NSString *)name;#pragma mark 取得一个对象(静态方法)+(Person *)personWithName:(NSString *)name;@end

Person.m

////  Person.m//  MemoryManage////  Created by Kenshin Cui on 14-2-15.//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import "Person.h"@implementation Person#pragma mark - 公共方法#pragma mark 带参数的构造函数-(Person *)initWithName:(NSString *)name{    if(self=[super init]){        self.name=name;    }    return self;}#pragma mark 取得一个对象(静态方法)+(Person *)personWithName:(NSString *)name{    Person *p=[[[Person alloc]initWithName:name] autorelease];//注意这里调用了autorelease    return p;}#pragma mark - 覆盖方法#pragma mark 重写dealloc方法-(void)dealloc{    NSLog(@"Invoke Person(%@) dealloc method.",self.name);    [super dealloc];}@end

main.m

////  main.m//  MemoryManage////  Created by Kenshin Cui on 14-2-15.//  Copyright (c) 2014年 Kenshin Cui. All rights reserved.//#import <Foundation/Foundation.h>#import "Person.h"int main(int argc, const char * argv[]) {    @autoreleasepool {        Person *person1=[[Person alloc]init];        [person1 autorelease];//调用了autorelease方法后面就不需要手动调用release方法了        person1.name=@"Kenshin";//由于autorelease是延迟释放,所以这里仍然可以使用person1                Person *person2=[[[Person alloc]initWithName:@"Kaoru"] autorelease];//调用了autorelease方法                Person *person3=[Person personWithName:@"rosa"];//内部已经调用了autorelease,所以不需要手动释放,这也符合内存管理原则,因为这里并没有alloc所以不需要release或者autorelease                Person *person4=[Person personWithName:@"jack"];        [person4 retain];    }    /*结果:     Invoke Person(rosa) dealloc method.     Invoke Person(Kaoru) dealloc method.     Invoke Person(Kenshin) dealloc method.     */        return 0;}

当上面@autoreleaespool代码块执行完之后,三个对象都得到了释放,但是person4并没有释放,原因很简单,由于我们手动retain了一次,当自动释放池释放后调用四个对的release方法,当调用完person4的release之后它的引用计数器为1,所有它并没有释放(这是一个反例,会造成内存泄露);autorelase方法将一个对象的内存释放延迟到了自动释放池销毁的时候,因此上面person1,调用完autorelase之后它还存在,因此给name赋值不会有任何问题;在ObjC中通常如果一个静态方法返回一个对象本身的话,在静态方法中我们需要调用autorelease方法,因为按照内存释放原则,在外部使用时不会进行alloc操作也就不需要再调用release或者autorelase,所以这个操作需要放到静态方法内部完成。

对于自动内存释放简单总结一下:

  1. autorelease方法不会改变对象的引用计数器,只是将这个对象放到自动释放池中;
  2. 自动释放池实质是当自动释放池销毁后调用对象的release方法,不一定就能销毁对象(例如如果一个对象的引用计数器>1则此时就无法销毁);
  3. 由于自动释放池最后统一销毁对象,因此如果一个操作比较占用内存(对象比较多或者对象占用资源比较多),最好不要放到自动释放池或者考虑放到多个自动释放池;

  4. ObjC中类库中的静态方法一般都不需要手动释放,内部已经调用了autorelease方法;,系统又是什么时候释放的呢?在每一个事件周期(event cycle)的开始,系统会自动创建一个自动释放池;        在每一个事件周期的结尾,系统会自动销毁这个自动释放池。一般情况下,你可以理解为:当你的代码在持续运行时,自动释放池是不会被销毁的,这段时间内你也可以安全地使用自动释放的对象;当你的代码运行告一段落,开始等待用户输入(或者其它事件)时,自动释放池就会被释放掉,池中的对象都会收到一个release消息,有的可能会因此被销毁。        这是很难确定的时间,如果自动释放池的销毁时间过早,那么程序就很危险,这个恐怕很难满足程序员的要求吧。        自动释放池的缺点:它延缓了对象的释放,在有大量自动释放的对象时,会占用大量内存资源。因此,你需要避免将大量对象自动释放。并且,在以下两种情况下,你需要手动建立并手动销毁掉自动释放池:1.当你在主线程外开启其它线程时:系统只会在主线程中自动生成并销毁掉自动释放池。2.当你在短时间内制造了大量自动释放对象时:及时地销毁有助于有效利用iPad上有限地内存资源。

属性参数

像上面这样编写setCar方法的情况是比较多的,那么如何使用@property进行自动实现呢?答案就是使用属性参数,例如上面car属性的setter方法,可以通过@property定义如下:

@property (nonatomic,retain) Car *car;

你会发现此刻我们不必手动实现car的getter、setter方法程序仍然没有内存泄露。其实大家也应该都已经看到前面Person的name属性定义的时候我们同样加上了(nonatomic,copy)参数,这些参数到底是什么意思呢?

propertyParameter


0 0