iOS与OS X内存管理
- 本文的写作目的是为学习记录,同时分享给大家,希望大神能够对文中错误的理解进行指正。
- 如果文章内容涉及到其他已经发表了,但文章中又未提及转载事项,请及时与本人联系。
- 本文为个人理解,如果部分知识点与真实情况有出入,请忽略本文。
1 相关知识点
1.1 内存管理
软件运行的时候,对计算机内存资源的分配和使用进行管理的策略。
1.2 引用计数
一种内存管理策略。(以下解释来自百度百科)
- 当一个新的引用指向对象时,引用计数器就递增;
- 当去掉一个引用时,引用计数就递减;
- 当引用计数到零时,该对象就将释放占有的资源。
1.3 iOS与OS X的自动释放池
把所有生成的对象放到一个“池子”里,当超过“池子”的作用域,就会自动帮你释放对象,不用你手动release。
1.4 iOS与OS X的自动引用计数
- “俗称”ARC(Automatic Reference Counting)。
- 如名,就是iOS和OS X系统内自行处理内存管理,不用你专门敲代码去给对象的引用计数+1、-1。
- 从iOS 5和OS X Lion开始引入了自动引用计数,所以除非你需要适配在那之前的版本,不然你完全可以打开ARC(想知道怎么开?请自行百度)。
1.5 内存泄漏
应该让出的内存资源,却一直占着不放。
2 引用计数
- 生成对象(+1);
- 持有对象(+1);
- 释放对象(-1);
- 废弃对象(=0)。
3 iOS与OS X的引用计数
3.1 生成对象(alloc/new/copy/mutableCopy)
- alloc方法
生成对象,并用某个变量去持有对象; - new方法
也是生成对象,并用某个变量去持有对象([NSObject new]与[[NSObject alloc] init]完全一致); - copy方法
生成对象,并用某个变量去持有对象的副本; - mutableCopy方法
同copy,区别在于生成的对象,copy生成不可变更的对象,mutableCopy则生成可变。
使用以上方法名称开头,并且以驼峰拼写法命名的方法,也意味着自己生成并持有对象。例如:
allocFirstObject
newSecondObject
copyThirdThat
mutableCopyFourthObject等。
3.2 持有对象(retain)
除去上述的四种方法之外取得的对象,可以用retain方法持有该对象。例如:
id testArr = [NSMutableArray array];
[testArr retain];
3.3 释放对象(release)
自己持有的对象,一旦不再需要,持有者有义务释放该对象。释放使用release方法。例如:
id testObj = [[NSObject alloc] init];
[testObj release];
3.4 废弃对象(dealloc)
当引用计数为0时,则调用dealloc方法废弃对象。
如果需要显示调用dealloc方法,即在废弃对象后进行必要的操作,则需要调用[super dealloc]。例如:
- ( void ) dealloc
{
[super dealloc];
}
3.5 释放之后再次释放
在持有对象之后,需要释放对象,但不能再次释放已经释放的对象。下例会Crash。例如:
id testObj = [[NSObject alloc] init];
[testObj release];
[testObj release];
3.6 释放自己不持有的对象
非自己生成的对象,需要用retain持有,如果没有用retain持有,就释放也会Crash。例如:
id testObj = [NSMutableArray array];
[testObj release];
4 iOS与OS X的自动释放池autorelease
4.1 具体过程:
- 生成自动释放池;
- 调用autorelease方法,将对象放进自动释放池中;
- 废弃自动释放池,所有在池中的对象自动调用release方法;
例如:
@autoreleasepool {
Id testObj = [[NSObject alloc] init];
[testObj autorelease];
}
//也可以通过生成NSAutoreleasePool对象的方式,来生成自动释放池
//通过生成NSAutoreleasePool对象的方式,需要手动废弃自动释放池(使用drain方法)。
4.2 注意事项
- 当大量读取大容量文件时,要在适当的地方生成、持有或废弃自动释放池。如果不及时释放自动释放池所占用的内存,会导致内存占用过大而Crash。
- Cocoa框架中,部分方法返回已调用autorelease方法的对象。具体信息请查阅官方文档。
5 iOS与OS X的自动引用计数(ARC)
所有ARC的操作需要在Xcode4.2之后进行,并且打开ARC功能(Xcode4.2之后默认打开)。
5.1 变量修饰符
5.1.1 __strong
- “强引用”。
- 生成并持有对象,非生成也可以通过赋值持有对象。
- 超出作用域时,自动释放。
- id类型和对象类型默认添加该修饰符。
例如:
{
id __strong testObj = [[NSObject alloc] init];
//;在ARC中,以上写法与id testObj = [[NSObject alloc] init]完全相同
}
//超出范围,自动释放
5.1.2 __weak
- “弱引用”。
- 弱引用不能持有对象。
- 不能直接生成,需要间接赋值。
- 弱变量持有对象时,若对象被废弃,则此变量自动失效或为nil。
- 防止循环引用,而导致内存泄漏。
例如:
@interface Test : NSObject
{
id __weak testObj;
}
- ( void ) setObject: (id __strong)obj;
@end
@implementation Test
- ( id )init
{
self = [super init];
}
- ( void ) setObject: (id __strong)obj
{
testObj = obj;
}
以下为引用:
{
id test0 = [[Test alloc] init];
id test1 = [[Test alloc] init];
[test0 setObject:test1];
[test1 setObject:test0];
//由于testObj为__weak类型,不持有对象,此处不会发生循环引用
}
//如果testObj为__strong类型,则持有对象
//导致testObj一直持有对象,导致对象无法释放==内存泄漏
5.1.3 __unsafe_unretained
- 该修饰符修饰的变量,不属于自动引用计数的范围。
- 不能直接生成,需要间接赋值。
- 如果赋值的对象被废弃或不存在,则程序有可能(有可能,即个别情况)崩溃。
例如:
id __unsafe_unretained testObj1 = nil;
{
id testObj0 = [[NSObject alloc] init];
testObj1 = testObj0;
NSLog(@”%@”,testObj1);
}
NSLog(@”%@”,testObj1);
//其中testObj0的对象引用已被废弃,则访问testObj1有可能出现程序崩溃
5.1.4 __autoreleasing
- 如名,ARC的自动释放池(以下简称“池”)。
- 在iOS的main函数中就已经生成了池,并且包含程序的所有内容。
- 不能用生成NSAutoreleasePool的方式,来生成自动释放池,编译器会报错。
- 正常情况下,如果你用@autoreleasepool生成池,必须显式将对象注册到池中。
但也存在个别情况:
1、非自己生成并持有的对象,会自动注册到池中。
例如:
@autoreleasepool {
id testObj = [NSMutableArray array];
//生成的对象已自动注册到池中
}
2、访问附有__weak修饰符的变量时,会将即将引用的对象注册到池中。
例如:
@autoreleasepool {
id testObj0 = [[NSArray alloc] init];
//此处若需将testObj0注册到池中,须显式使用__autoreleasepool修饰符声明
id __weak testObj1 = testObj0;
//在赋值的同时,将testObj0引用的对象注册到池中
}
3、id的指针、对象的指针变量会自动附上__autoreleasepool修饰符。
例如:
@autoreleasepool {
id testObj0 = [[NSObject alloc] init]; // testObj0自动附上__strong修饰符
id *testObj1 = &testObj0; //*testObj1自动附上__autoreleasepool修饰符
//此处会报错,因为指针赋值时,修饰符必须一致
}
Tips:无论ARC是否开启,_objc_autoreleasePoolPrint();函数都可以打印出已经注册到当前池中所有对象。
5.2 iOS与OS X中ARC开启的状态下,注意事项
5.2.1 禁用关键字
- 不能使用alloc/retain/release/retainCount/autorelease。
- 估计大家都知道这点,在ARC开启状态下,以上这些方法都是不能使用的。
5.2.2 须遵守的命名规则
- 在ARC未开启时候的命名规则,参考3.1 最后一条所述。
- 而在ARC开启的时候,除去以上规则同样有效外,还要考虑init关键字:
1、以init开头(并且用驼峰拼写发)命名的方法,必须是一个实例方法,并且需要返回对象。
2、以init开头(并且用驼峰拼写发)命名的方法,返回的对象并不自动注册到自动释放池中。
5.2.3 不要显示调用dealloc
- 对象被废弃时,不管ARC是否开启,都会调用对象的dealloc方法。
- 参考以上所述,可以在类中实现dealloc方法(实现dealloc方法在ARC是否开启之间的区别,请参考3.4)。
例如:
- ( void ) dealloc
{
//无需[super dealloc]
}
- 如上例中所述,在ARC开启的状态下,不要显式调用dealloc。
5.2.4 不要使用NSAutoreleasePool
在ARC开启的状态下,使用NSAutoreleasePool类生成自动释放池,编译器会报错。
5.2.5 不能使用区域(NSZone)
无论ARC是否开启,最好都不要用(其实我还没去了解这个“区域”是个什么东西o(╯□╰)o)。
5.2.6 对象型变量不能成为struct的成员
例如:
struct TestStruct {
NSArray * array;
//报错,不能用对象型变量
}
5.2.7 __bridge转换
- 不能显示转换id和void *(C/C++语言中的空指针类型),需要用__bridge关键字进行转换。
例如:
id testObj = [[NSObject alloc] init];
void *testVoid = (__bridge void *)testObj;
id testObj1 = (__bridge id)testVoid;
- __bridge_retained转换,可使要转换赋值的变量,也持有所赋值的对象。
- __bridge_transfer转换,被转换的变量所持有的对象,在转换完成后释放。
Tips:Foundation框架与Core Foundation框架的对象互相转换,需要用__bridge关键字进行转换。
5.3 iOS与OS X的属性(property)
属性声明的属性与所有权修饰符的对应关系:
属性声明的属性 | 所有权修饰符 |
---|---|
assign | __unsafe_unretained修饰符 |
copy | __strong修饰符(但是赋值的是被复制的对象) |
retain | __strong修饰符 |
strong | __strong修饰符 |
unsafe_unretained | __unsafe_unretained修饰符 |
weak | __weak修饰符 |
5.4 容器
5.4.1静态容器(NSArray、NSDictionary、NSSet)
- 除去__unsafe_unretained修饰符以外,__strong、__weak、__autoreleasing修饰符都能保证其指定变量初始化为nil。
- 数组超出其作用域,则变量失效、对象释放(蛮押韵的)。
5.4.2 动态容器(NSMutableArray、NSMutableDictionary、NSMutableSet)
- Objective-C的动态容器使用的基本规则,基本同静态一致。
- iOS中C语言的动态容器:
1、声明动态容器,需要用指针。
例如:
id __strong *array = nil;
2、附有__strong修饰符的id型变量初始化为nil,附有__strong修饰符的id型指针变量初始化不一定为nil。
3、calloc函数,给附有__strong修饰符的id型指针变量分配内存。
例如:
array = (id __strong *)calloc(entries, sizeof(id));
4、操作__strong修饰符的变量,需要自己释放所有元素。释放所有元素后,利用free(array)的方法废弃内存块。
例如:
for (NSUInteger i = 0; i < entries; ++i) {
array[i] = nil;
}
free(array);
5、以下函数会造成内存泄漏的危险,请慎用:
- 用malloc函数分配内存(之后,可用memset函数将内存置零);
- 用memcpy函数拷贝数组元素;
- 用realloc函数重新分配内存块。
6、__weak修饰符与__strong修饰符使用方式相同;但__autoreleasing修饰符存在不可控因素,慎用;__unsafe__unretained修饰符在编译器的内存管理对象之外,只能作为指针类型使用。
参考:
- 《Objective-C高级编程 iOS与OS X多线程和内存管理》【日】Kazuki Sakamoto Tomohiko Furumoto 著 黎华 译