iOS 内存管理
存储机制
首先,我们来了解一下iOS中对象的存储机制,通常我们使用对象的方法是声明一个对象指针,这个对象指针是存放在栈区的,再让这个栈区的对象指针指向对象,对象是存放在堆区的,也就是说这个对象指针(栈区)中存放着对象的地址(堆区)。
栈区一般用于存放局部变量以及函数的参数,是由编译器分配的,而堆区的内存则是程序运行过程中动态的分配和释放的,也就是说对象的内存是程序运行过程中动态分配和释放的,iOS中通过引用计数来管理对象内存的分配与释放。
引用计数
OC语言使用引用计数来管理对象的内存,每一个对象都对应有一个计数器用来记录这个对象被使用的情况,我们把这个计数器称为引用计数。当创建一个对象时,它的引用计数为1,如果有一个新的指针指向这个对象则它的引用计数加1,如果该指针不再指向这个对象的引用计数减1。
alloc/retain/release/delloc
引用计数是通过 alloc/retain/release/delloc 操作来实现的
- alloc(也包含其他可以生成对象的方法:new/copy/mutableCopy) :生成并持有对象
- retain:持有对象
- release:释放对象
- delloc:销毁对象,当对象引用计数为0的时候会调用delloc方法销毁对象
//生成并持有对象,对象引用计数为1
NSObject *object = [[NSObject alloc] init];
NSLog(@"%lu",(unsigned long)[object retainCount]);
//对象引用计数加1
[object retain];
NSLog(@"%lu",(unsigned long)[object retainCount]);
//引用计数减1
[object release];
NSLog(@"%lu",(unsigned long)[object retainCount]);
// [object release];//这里再release引用计数就是0,会自动调用delloc方法销毁对象
// NSLog(@"%@",object);//对象已经销毁,这里再使用该对象就会出错
上面代码的运行结果
2018-11-14 21:27:56.209992+0800 MRC[2459:91153] 1
2018-11-14 21:27:56.210297+0800 MRC[2459:91153] 2
2018-11-14 21:27:56.210473+0800 MRC[2459:91153] 1
NSAutoreleasepool
除了引用计数,iOS还提供了 NSAutoreleasepool——自动释放池来管理内存。我们通过alloc和init方法初始化 NSAutoreleasepool 对象,drain 方法废弃这个对象,在 NSAutoreleasepool 的生命周期内调用对象的autorelease方法,就可以让 autoreleasepool 管理这个对象,它会将该对象加入当前自动释放池,当 autoreleasepool 销毁(也就是调用drain方法)的时候再释放自动释放池中的所有对象。
for (int i = 0; i < 10e5 * 2; i++) {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
NSObject *obj = [[NSObject alloc] init];
[obj autorelease];
[pool drain];//在调用drain方法的时候会自动释放对象
}
for (int i = 0; i < 10e5 * 2; i++) {
NSObject *obj = [[NSObject alloc] init];//不会自动释放
}
执行上面两段代码我们会发现第一个占用内存明显比第二个的少,正是 NSAutoreleasePool 的作用。
ARC
以上就是iOS内存管理机制,但是现在我们不需要要手动管理内存,因为iOS提供了ARC来自动管理内存(ARC环境下是不能显示调用retain/release/delloc),ARC就是自动引用计数,我们使用恰当的所有权修饰符(或属性关键字)修饰对象,系统会根据修饰符对对象的引用计数进行相应的操作,并在合适的时候自动释放对象。
所有权修饰符
- __strong 对对象的强引用,引用计数加一;
- __weak 对对象的弱引用,不持有对象,引用计数不加一;
- __autoreleaseing 自动释放对象,会将对象加入到自动释放池。
- __unsafe_unreatin 用于对象类型,不会自动置nil
ARC 环境下默认使用 __strong 修饰符
属性关键字
属性关键字用来修饰对象的属性
- strong 对对象的强引用,引用计数加一
- copy 用于不可变类型,强引用,引用计数会加一
- weak 弱引用,引用计数不加一,对象销毁的时候会自动置nil
- assgin 用于基本类型,不会置nil
- unsafe_unreatin 用于对象类型,不会自动置nil
@autoreleaseool{}
虽然ARC环境下系统会自动管理内存,但是有些情况下依旧需要使用到自动释放池,在ARC 环境下用 @autoreleaseool{} 代替 NSAutoreleasePool 对象的初始化与销毁,花括号范围内就是这个自动释放池的生命周期 ,下面我们来看几个ARC环境下使用 @autoreleaseool{} 的例子。
//代码一
for (int i = 0; i < 10e5 * 2; i++) {
@autoreleasepool {
NSString *str = [NSString stringWithFormat:@"hi + %d", i];
}
}
//代码二
for (int i = 0; i < 10e5 * 2; i++) {
NSString *str = [NSString stringWithFormat:@"hi + %d", i];
}
分别执行上面两段代码,我们会发现代码一的内存明显小于代码二,这正是因为代码一使用了 @autoreleaseool{},当出了autoreleasepool的生命周期时会自动释放掉该对象。
值得一提的是ARC环境下系统会在适当的时候创建和销毁自动释放池来管理某些特定的对象(比如通过array方法初始化的可变数组)。其中创建与销毁的时机与 Runloop 的运行状态有关。
ARC的缺陷
循环引用
在ARC环境下会有循环引用的问题,当一个对象自己持有自己或者两个对象相互持有或者多个对象之间的引用形成环的时候就会导致相互持有,从而都不能释放造成内存泄漏的问题,这个问题可以通过weak修饰符来解决weak修饰符可以达到引用但不持有对象的效果,而且会在指针所引用对象被废弃的时候自动将该指针置nil。
不能管理Core Foundation对象
ARC环境不会自动管理Core Foundation对象,我们需要手动管理Core Foundation 对象的内存或者通过__bridge、__bridge_retained、__bridge_transfer关键字进行Core Foundationd对象与OC对象的转换,把Core Foundation 对象的内存管理权交给ARC去处理。
- (__bridge type)expression) 只做类型转换,不修改对象所有权(相互转换)
- (__bridge_retained CF type)expression) 对象所有权归CF对象(OC对象转CF对象)
- (__bridge_transfer Objective-C type)expression) 类型转换后,将该对象的引用计数交给ARC管理(CF对象转OC对象)