iOS教程(一)iOS内存管理

最近一个月学习了iOS开发相关的知识。Objective-C的内存管理是最基础也是最重要的。以此篇博客作一个记录,并为后来学习iOS开发的同学提供一个参考。

OC内存管理简介

OC的内存管理不同于C++的内存管理方式。C++的内存管理方式相对来说比较简单粗暴,new出来的内存,不用的时候delete掉就行。
OC的内存管理采用了引用计数的方法。简单的说,就是对每一个对象都保存一个引用计数值,当程序中增加一个对该对象的引用时,引用计数值增加1;反之,程序中减少一个对该对象的引用时,则引用计数值减少1。当引用计数值为0是,系统释放对象。

OC内存管理的原则:谁创建对象,就由谁释放对象

retain & release

首先介绍这两个最基本的方法。对象的retain函数将对象的引用计数加1。对象的release函数将对象的引用计数减1,同时判断对象的引用计数是否为0,如果为0,释放对象。

NSString *str = [[NSString alloc] init]; // 引用计数为1
[str retain]; // 引用计数为2
[str release]; // 引用计数为1
[str release]; // 对象释放,str沦为野指针
// str释放后,调用retainCount函数得到的引用计数可能仍旧为1。

autorelease

但是,在某些情况下,只有retain和release并不能很好的解决问题。例如:

- (NSString*)getStr
{
NSString *str = [[NSString alloc] init];
// do some jobs
return str; // str并没有release
}

在上述的例子中,由于函数调用者需要用到函数返回的str,因此,不能在getStr函数中release掉str。导致的结果是,函数调用者在获得函数的返回值str,使用完后调用release释放str。这就违反了在OC的谁创建谁释放的内存管理原则。

这个时候就需要autorelease了!

在这里,要明确一个概念。对象调用autorelease函数后,并不是说这个对象就完全由系统管理了,调用autorelease函数只代表系统会在将来的某个时候,调用一次该函数的release方法。
调用autorelease函数后,对象会被添加到自动释放池中。在下一个runloop或者其他未来的时刻,自动释放池中的对象都会被release一次。自动释放池就是NSAutoReleasePool对象啦。

那么,刚刚的getStr的函数可以改写成:

- (NSString*)getStr
{
NSString *str = [[NSString alloc] init];
// do some jobs
// str在函数内创建,并调用autorelease。由系统release一次。
// 满足OC的谁创建谁释放的内存管理原则。
return [str autorelease];
}

那么,调用getStr函数的操作也有些许变化。

NSString *str = [[obj getStr] retain]; // retain一次,否则str什么时候被释放掉都不知道(函数内有调用autorelease)。
// do some jobs
[str release]; // 用完后release

属性property

相比于C++的类,OC的类多了属性这一块东西。大部分情况下,定义一个属性,同时定义了一个成员变量,以及该成员变量的getter和setter。

@interface MyObject: NSObject
@property (retain, nonatomic) NSArray *array;
@end

对于MyObject的对象,可以像如下的方式访问和修改array属性:

MyObject *obj = [[[MyObject alloc] init] autorelease];
NSObject *c = [obj.array objectAtIndex:0];
obj.array = [[[NSArray alloc] init] autorelease];

本质上,MyObject定义了_array成员变量,以及getArray和setArray方法。

那么,在定义属性时,retain和nonatomic是什么意思呢?在这里只讲内存管理相关的retain,copy,assign。其他的会在我的另一篇博客中讲解。

  • retain
    当属性定义为retain时,通过obj.array = newArray赋值时,会retain新对象,并release掉旧对象,像下面这样。
- (void)setArray:(NSArray*)newArray
{
[newArray retain];
[_array release];
_array = newArray;
}

在上述代码中,先release就对象,再retain新对象会不会有问题咧?当然有问题!如果newArray和array是同一个对象,先release旧对象,那么可能release的时候对象就已经释放了,接下来再retain就没有什么意义了。

  • copy
    当属性定义为copy时,属性赋值会拷贝新对象。
- (void)setArray:(NSArray*)newArray
{
_array = copy of newArray;
}
  • assign
    assign是最简单的一个,一般用于基本类型。属性赋值时不会做额外的操作,直接赋值。
- (void)setArray:(NSArray*)newArray
{
_array = newArray;
}
  • 总结
    一般情况,NSString类型属性定义为copy,其他NSObject子类定义为retain,基础属性定义为assign。
    对于retain和copy的属性,需要在析构函数中dealloc中release。
- (void)dealloc
{
[_array release];
_array = nil;
}

当为retain型属性赋值时,需要特别注意。如果一个变量是通过带有alloc,new,copy,create单词函数新建对象后赋值给retain属性,则新建变量需要autorelease。例如。

self.str = [[[NSString alloc] init] autorelease];

子线程中的内存管理

前面提到了自动释放池NSAutoReleasePool对象。在主线程中,系统会自动创建一个自动释放池对象。在子线程中,则需要自己创建自动释放池对象,并在线程结束时释放自动释放池。。否则,调用autorelease无效,导致内存泄露。

// self.thread为retain的属性
self.thread = [[[NSThread alloc] initWithTarget:self selector:@selector(newThreadFunc) object:nil] autorelease];
// ...
- (void)newThreadFunc
{
NSAutoReleasePool *pool = [[NSAutoReleasePool alloc] init];
// jobs
[pool drain]; // or [pool release];
}

关于调用drain还是release。在高版本sdk中,两者是等价的。但是低版本中,drain才能释放pool。因此,考虑到兼容性,建议调用drain。

ARC(Automatic Reference Counting)

ARC,自动引用计数。前面讲述的内存管理,虽有autorelease帮助,但还是属于手动管理内存。ARC是编译期技术。在ARC下,是不允许调用retain,release,以及autorelease函数的,完全交给编译器去管理。

您的支持将鼓励我继续创作。