据说ObjC三大难点之一就有内容管理,这项比较有趣的内容终于来临了!Come on~
1.自动释放池
在处理Foundation程序时,为了使用Foundation对象,必须设置自己的池。系统使用这个池来跟踪对象,以便以后释放。在应用程序中,可以通过调用来建立这个池。
如:NSAutoreleasePool * pool=[[NSAutoreleasePool alloc]init];
建立了自动释放池之后,Foundation将自动为这个池添加数组、字符串、字典等对象,使用完该池后调用drain来释放它的内存,如:[pool drain];
也可以通过像当前的自动释放池发送一条autorelease的消息,将对象加入自动释放池中,这样对象会在自动释放池释放时释放掉对象本身的内存。
当程序产生大量的临时对象是,可能需要在程序中创建多重自动释放池,如:
NSAutoreleasePool *tempPool;
for (int i = 0; i < 10; i++) {
tempPool = [[NSAutoreleasePool alloc] init];
NSString *string = [NSString stringWithString:@"ABC"];
string = [string lowercaseString];
string = [string stringByAppendingString:@"XYZ"];
NSLog(@"%@",string);
[tempPool drain];
}
自动释放池实例:需应用NSObject及NSAutoreleasePool
@interface Foo:NSObject
{
int x;
}
@end
@implementation Foo
@end
NSAutoreleasePool *p = [[NSAutoreleasePool alloc]init];
Foo *f = [[Foo alloc]init];
NSLog(@"%x",[f retainCount]);//init
[p drain];
NSLog(@"%x",[f retainCount]);//p drain
p = [[NSAutoreleasePool alloc]init];
[f autorelease];
NSLog(@"%x",[f retainCount]);//p init f autorelease
[f retain];
NSLog(@"%x",[f retainCount]);//f reatin
[p drain];
NSLog(@"%x",[f retainCount]);//p drain
[f release];
结果输出:
2013-06-03 01:36:59.365 Test[904:c07] 1
2013-06-03 01:36:59.367 Test[904:c07] 1
2013-06-03 01:36:59.368 Test[904:c07] 1
2013-06-03 01:36:59.369 Test[904:c07] 2
2013-06-03 01:36:59.370 Test[904:c07] 1
由此我们可以知道当添加了autorelease后的池被释放时会向对象发送release消息~但我们最后仍需手动释放对象避免内存泄漏。
2.计数
计数这个概念是当对象被多个地方引用,而确不知道它到底工作完没有,这时候我们如果把它释放了后面引用到它的必将出现错误,所以引入了计数这样得概念。
创建对象时,计数会自动设置为1,也可以用使用retain对其计数加1,而当我们调用release时,对象得引用次数会减1.我们可以通过retainCount获得对象得引用计数。
代码实例:
NSNumber *myInt =[NSNumber numberWithInteger:100];
NSNumber *myInt2;
NSMutableArray *arr = [NSMutableArray array];
NSLog(@"myInt retain count =%lx",(unsigned long) [myInt retainCount]);
//return 0;
[arr addObject: myInt ];
NSLog(@"afetr adding to arr = %lx",(unsigned long) [myInt retainCount]);
myInt2 = myInt;
NSLog(@"afetr asssingnment to int2 = %lx",(unsigned long) [myInt retainCount]);
[myInt retain];
NSLog(@"myInt afetr myInt retain = %lx",(unsigned long) [myInt retainCount]);
NSLog(@"myInt2 retain myInt retain = %lx",(unsigned long) [myInt2 retainCount]);
[myInt release];
NSLog(@"myInt afetr myInt release = %lx",(unsigned long) [myInt retainCount]);
[arr removeObjectAtIndex:0];
NSLog(@"myInt afetr arr remove = %lx",(unsigned long) [myInt retainCount]);
结果:
2013-06-02 15:30:44.652 Test[614:c07] myInt retain count =1
2013-06-02 15:30:44.654 Test[614:c07] afetr adding to arr = 2
2013-06-02 15:30:44.656 Test[614:c07] afetr asssingnment to int2 = 2
2013-06-02 15:30:44.658 Test[614:c07] myInt afetr myInt retain = 3
2013-06-02 15:30:44.660 Test[614:c07] myInt2 retain myInt retain = 3
2013-06-02 15:30:44.662 Test[614:c07] myInt afetr myInt release = 2
2013-06-02 15:30:44.664 Test[614:c07] myInt afetr arr remove = 1
myInt 开始初始化后计数就变为1;
添加到数组中引用次数随之变为2;
接下来,将myInt赋值给变量myInt2这个操作并没有使myInt对象得引用次数增加,那么myInt赋值给myInt2时并没有复制实际得对象只是让myInt2指向myInt的内存指针,所以当myInt的引用计数减少到0时,myInt2将拥有无效的对象引用;
之后就时retain让对象计数增加,release让对象计数减少,remove让对象计数再减少~
* 补充一点需要注意的:NSString 对象在内存中常量字符串的空间分配与其他对象不同,它们没有引用计数机制,因为永远不能释放这些对象,向NSString对象发送消息retainCount,它会返回0xffffffff;
代码实例2:
@interface ClassAA :NSObject
{
NSString *str;
}
-(void) setStr:(NSString *)s;
-(NSString *) str;
@end
@implementation ClassAA
-(void) setStr:(NSString *)s
{
str = S;
//[str retain];
}
-(NSString *)str
{
return str;
}
@end
ClassAA *ca = [[ClassAA alloc]init];
NSLog(@"%x",[ca retainCount]);
[ca setStr:@"11"];
NSLog(@"%x",[ca retainCount]);
结果:
2013-06-03 01:10:12.681 Test[866:c07] 1
2013-06-03 01:10:12.684 Test[866:c07] 1
如上程序有缺陷。
而如果把setStr中的注视去掉 那么结果就是 1 2
这样以后别的代码调用释放引用时,程序将不会收到影响因为计数不一样了。
我们也可以重写类的dealloc方法,让它release变量及super dealloc。
Cocoa内存管理规则
Cocoa内存管理原则很简单:
- 当使用new,alloc或者copy方法创建对象的时候,该对象计数器为1。当不在使用的时候,要release释放该对象或者autorelease提交到对象自动释放池。
- 当通过其他方法获得对象,要假设该对象的计数器已经是1,而且被设置autorelease了。在获得该对象后不需要进行清理操作。如果打算使用一段时间,需要retain操作,并且在使用完毕做release释放它。
- 如果retain了某个对象。需要release或者autorelease该对象。并且保持retain和release方法调用的次数一样多。
举一个常见的例子。比如使用NSArray:
int main (int argc, const char * argv[]) { NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init]; Author *author=[[Author alloc]init]; NSArray *array=[NSArray arrayWithObjects:author,nil]; NSLog(@"author retain count: %i",[author retainCount]); [author release]; NSLog(@"author retain count: %i",[author retainCount]); [pool drain]; return 0; }
这里创建了一个author对象,并且把它放到NSArray中去了。当放入NSArray后,会发现author的引用计数为2了,也就是说在放入集合中后,引用计数会自动加1。
另外,array变量不是通过new、alloc或者copy创建的,因此我们假设(实际上就是)它会retain使引用计数为1了。而且还是autorelease的。因此上面的例子并未release array。
-----------------------
摘一段总结:
内存管理关心的是清理(回收)不用的内存,以便内存能够再次利用。
提供给Objective-C程序员的基本内存管理模型有以下三种:
1)自动垃圾收集。(iOS运行环境并不支持垃圾收集,在这个平台开发程序时没有这方面的选项,只能用在Mac OS X 程序上开发。这个机制挺恶心的,用mac电脑的人知道,当内存不足的时候,机器基本上就是卡死了。)
2)手工引用计数和自动释放池。(这种方式,对程序员的要求很高。自己维护嘛,)
3)自动引用计数(ARC)。
手工内存管理规则的总结:
1)如果需要保持一个对象不被销毁,可以使用retain。在使用完对象后,需要使用release进行释放。
2)给对象发送release消息并不会必须销毁这个对象,只有当这个对象的引用计数减至0时,对象才会被销毁。然后系统会发送dealloc消息给这个对象用于释放它的内存。
3)对使用了retain或者copy、mutableCopy、alloc或new方法的任何对象,以及具有retain和copy特性的属性进行释放,需要覆盖dealloc方法,使得在对象被释放的时候能够释放这些实例变量。
4)在自动释放池被清空时,也会为自动释放的对象做些事情。系统每次都会在自动释放池被释放时发送release消息给池中的每个对象。如果池中的对象引用计数降为0,系统会发送dealloc消息销毁这个对象。
5)如果在方法中不再需要用到这个对象,但需要将其返回,可以给这个对象发送autorelease消息用以标记这个对象延迟释放。autorelease消息并不会影响到对象的引用计数。
6)当应用终止时,内存中的所有对象都会被释放,不论它们是否在自动释放池中。
7)当开发Cocoa或者iOS应用程序时,随着应用程序的运行,自动释放池会被创建和清空(每次的事件都会发生)。在这种情况下,如果要使自动释放池被清空后自动释放的对象还能够存在,对象需要使用retain方法,只要这些对象的引用计数大于发送autorelease消息的数量,就能够在池清理后生存下来。
自动引用计数(ARC):
强变量,通常,所有对象的指针变量都是强变量。
如果想声明强变量,可以使用__strong Faction *fl;这样的__strong来修饰。
值得注意的是,属性默认不是strong,其默认的特性是unsafe_unretained类似assign。所以需要声明属性strong时,可以如下:
@property (strong, nonatomic) NSMutableArray *birdNames;
编译器会保证在事件循环中通过对赋值执行保持操作强属性能够存活下来。带有unsafe_unretained(相当于assign)或weak的属性不会执行这些操作。
弱变量,可以使用__week关键字来声明。弱变量不能阻止引用的对象被销毁。当引用的对象释放时,弱变量会被自动设置为nil。
需要注意的是,在iOS4和Mac OS V10.6中不支持弱变量。在这种情况下,你仍然可以为属性使用unsafe_unretained, assing特性.
ARC都会在“底层”发生,所以一般不用关心。