iOS内存管理理解
在Build Phases -> Compile Sources -> 对应的文件加上-fno-objc-arc的编译参数可以启用MRC模式
1.简介
1.1 内存管理的思考方式
- 自己生成的对象,自己持有
- 非自己生成的对象,自己也能持有(指针指向这个对象,引用计数+1,就是持有)
- 不再需要自己持有的对象时释放
- 非自己持有的对象无法释放
1.2 ARC下的内存管理
ARC虽然能够解决90%的内存管理问题,另外还有10%的需要开发者自己处理
- 过度使用block,无法解决循环引用问题
- 遇到底层Core Foundation对象,需要自己手动管理它们的引用计数时
2.引用计数(Reference Count)
2.1 什么是引用计数
引用计数是一种管理对象生命周期的方式,一个内存块被强指针指向的数量。
2.2 alloc/retain/release/dealloc
cocoa框架中foundation框架类库的NSObject类是担负着内存管理的
- alloc: 生成并持有对象。
自己持有的对象可以使用alloc/new/copy/mutableCopy开头命名,如果自己不持有不能是有这些作为开头。 - retain:持有对象。
引用计数+1,超过最大数时会报错。寻址找到对象内存地址头部,减去存放引用计数值的struct的大小的地址,获取到计数值并+1,当计数值为超过最大值时抛出异常代码。 - release:释放对象。
引用计数-1,如果释放了非自己持有的对象或者释放了引用计数为0的对象,程序会崩溃。寻址找到对象内存地址头部,减去存放引用计数值的struct的大小的地址,获取到计数值并-1,当计数值为0时调用dealloc方法。 - dealloc:废弃对象。
废弃由alloc开辟的内存块
指向一个内存块的每一条指针,都能使用retain和release对这个内存块的引用计数进行改变。
//开辟内存块,引用计数为1
id object1 = [[NSObject alloc] init];
//object2的指针指向object1创建的内存块,内存块的引用计数还是1
id object2 = object1;
//object2使用retain是引用计数+1
[object2 retain];
//object1使用release将内存块上的引用计数-1
[object1 release];
//object1还是可以使用release将内存块的引用计数再-1
//此时内存块上的引用计数值为0,内存空间被系统回收
//tips:系统知道马上就要回收内存了,没必要-1了,直接回收对象
[object1 release];
如果想要销毁一个对象,不仅需要使用release将引用计数-1,还需要将对象的指针置为nil
id object1 = [[NSObject alloc] init];
[object1 release];
object1 = nil;
2.3 引用计数的实现
GNUstep中的实现:采用在内存块头部放置引用计数来进行管理
- 分配存放对象所需要的内存空间,将该内存空间置0
- 用一个整数来记录retain的引用计数值,并把这个整数写到内存空间的头部
- 返回对象指针
GNUstep优点:
- 少量代码就能完成
- 能够统一管理引用计数用的内存块和对象用内存块
苹果源码中的实现:采用引用技术表(散列表)管理引用计数。
- 分配存放对象所需要的内存空间,将空间置0
- 在散列表中加入引用计数并附带对象的内存块地址
- 返回对象指针
苹果源码优点:
- 对象用内存块分配无需考虑内存块头部
- 引用技术表各记录中存有内存块地址,可从各个记录追溯到各个内存块地址,在调试时,只要引用计数表没有被破坏,就可以确认内存块位置,就可以检测各对象持有者是否存在。
2.4 别向已经释放的对象发消息
当一个对象引用计数为0时,系统已经将该对象的地址回收,它的输出结果是不一定的,如果被回收的地址为空,则会输出0,如果地址被内存复用了,就会造成系统崩溃。
NSObject *object = [[NSObject alloc] init];
NSLog(@"Reference Count = %u", [object retainCount]);
[object release];
//此时object的内存被释放了,输入为0或者崩溃
NSLog(@"Reference Count = %u", [object retainCount]);
3.循环引用(Reference Cycle)
如果对象A的成员变量是对象B,对象B的成员变量是A,释放对象A需要先释放成员变量B,而释放成员变量B也需要先释放成员变量A,形成一个无法释放的循环,造成内存泄漏,这就是循环引用。
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
view.callbackBlock = ^{
//view持有的block中调用了self,意味着view成员变量的一根指针指向了self
[self doSomething];
}
}
当 viewDidLoad 方法执行时,创建一个 block 并赋值给对象 view 的 callbackBlock 属性,callbackBlock捕捉 self,self 持有 self.view, v 在 addSubview 后成为 self.view 的子 view 而被 self.view 持有,这样就形成了一个引用循环,self -> self.view -> view -> callBackBlock -> self。
环路越是大的循环引用,越难以被发现。
打破循环方法1:主动断开循环引用
根据业务逻辑主动断开引用,在view 执行完callbackBlock后,将block置为nil,主动断开循环引用。
if (self.callbackBlock) {
self.callbackBlock();
//调用方将指向callbackBlock的指向指向了nil,主动释放block
self.callbackBlock = nil;
}
循环变成在view -> callBackBlock处被主动断开,循环引用打破
解决方法2:弱引用
以代理模式为例,对象A中创建对象B并拿到它的delegate,如果delegate为strong声明的,则会形成循环引用A -> B -> BDelegate -> A,而事实上delegate对象通常都被声明为weak型变量,就是为了避免循环引用,从BDdelegate -> A处打破循环引用。
系统对于每一个有弱引用的对象,都维护一个表来记录它所有的弱引用的指针地址。每当一个对象的引用计数为 0 时,系统就通过这张表,找到指向对象的所有弱引用指针,把它们置成 nil。
weakSelf 和 strongSelf:
weakSelf是将一根weak声明的指针指向了self,在self -> self.view -> view -> callBackBlock -> self循环中,weakSelf使用弱引用的方式从callBackBlock -> self处打破循环引用。
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
__weak __typeof__(self) weakSelf = self;
view.callbackBlock = ^{
[weakSelf doSomething];
}
}
但是使用weakSelf存在一个 callbackBlock 执行时self对象会释放的问题,如果在刚刚执行callbackBlock 中的方法时 weakSelf 就为 nil了,那么callbackBlock中的所有方法中的weakSelf都会是nil,callbackBlock的输出结果是唯一的,不会造成什么影响。但是如果self是在callbackBlock执行到一半的时候释放的,就会导致 callbackBlock出现多种不同的执行结果,这种时候就需要利用strongSelf来保证在所在block的作用域中self不被释放。
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
//将指向self的指针变成弱指针,这样不会造成循环引用
__weak __typeof__(self) weakSelf = self;
view.callbackBlock = ^{
//保证在所在block的作用域中self不被释放
//如果不加strongSelf,可能会出现在doSomething时self还存在,而在执行doAnoterThing时,self变成了nil
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doAnoterThing];
}
}
在嵌套block中,每个block中都需要设置strongSelf。
DemoViewController.m
- (void)viewDidLoad {
[super viewDidLoad];
CustomView *view = [[CustomView alloc] init];
[self.view addSubview:view];
//将指向self的指针变成弱指针,这样不会造成循环引用
__weak __typeof__(self) weakSelf = self;
view.callbackBlock = ^{
//保证在callbackBlock作用域内的self一直不释放
//如果self进入block时就为nil,则一直为nil。
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doAnoterThing];
obj.objCallbackBlock = ^{
//需要重新设置strongSelf,保证在objCallbackBlock作用域内的self一直不释放
__typeof__(self) strongSelf = weakSelf;
[strongSelf doObjSomething];
[strongSelf doObjAnotherThing];
}
}
}
4.Core Foundation
- __bridge
只做类型转换,不修改相关对象的引用计数,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。- Core Foundation对象 -> Objective-C对象
NSMutableString *mString;
CFMutableStringRef cfstr;
{
//通过CFCreate系列方法创建,内存块引用计数为1
CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
//由于mString是__strong修饰符修饰的,mString指向内存快时引用计数++
//引用计数为2
mString = (__bridge NSMutableString *)cfstring;
cfstr = cfstring;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
}
//由于CF对象不会自动释放,所以引用计数为2
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr)); - Objective-C对象 -> Core Foundation对象
CFMutableStringRef cfstr;
{
//通过alloc方法创建内存块,内存块引用计数为1
NSMutableString *mString = [[NSMutableString alloc] init];
//通过__bridge转换,内存块上的引用计数不变,依旧为1
cfstr = (__bridge CFMutableStringRef)mString;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
}
//内存块通过Arc机制释放,cfstr所指为野指针,崩溃或为未知对象
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
- Core Foundation对象 -> Objective-C对象
- __bridge_retained
类型转换后,将相关对象的引用计数加 1,原来的 Core Foundation 对象在不用时,需要调用 CFRelease 方法。-
Core Foundation对象 -> Objective-C对象 ERROR
NSMutableString *mString;
CFMutableStringRef cfstr;
{
//通过CFCreate系列方法创建,内存块引用计数为1
CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
//由于mString是__strong修饰符修饰的,mString指向内存快时引用计数++
//但由于是__bridge_retained,引用计数又会+1,在ARC下编译会报错
mString = (__bridge_retained NSMutableString *)cfstring;
cfstr = cfstring;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
}
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr)); -
Objective-C对象 -> Core Foundation对象
CFMutableStringRef cfstr;
{
//通过alloc方法创建内存块,内存块引用计数为1
NSMutableString *mString = [[NSMutableString alloc] init];
//因为是__bridge_retained 所以引用计数++
//内存块引用计数为2
cfstr = (__bridge_retained CFMutableStringRef)mString;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
}
//ARC下出了作用域,mString被释放,引用计数--
//引用计数为1
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
-
- __bridge_transfer
类型转换后,将该对象的引用计数交给 ARC 管理,Core Foundation 对象在不用时,不再需要调用 CFRelease 方法。-
Core Foundation对象 -> Objective-C对象
NSMutableString *mString;
CFMutableStringRef cfstr;
{
//通过CFCreate系列方法创建,内存块引用计数为1
CFMutableStringRef cfstring = CFStringCreateMutable(kCFAllocatorDefault, 1);
//因为是__bridge_transfer 所以引用计数交由OC对象管理
//引用计数为1 不变
mString = (__bridge_transfer NSMutableString *)cfstring;
cfstr = cfstring;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstring));
}
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr)); -
Objective-C对象 -> Core Foundation对象 ERROR
CFMutableStringRef cfstr;
{
//通过alloc方法创建内存块,内存块引用计数为1
NSMutableString *mString = [[NSMutableString alloc] init];
//因为是__bridge_transfer 所以引用计数交由CF对象管理
//在ARC下编译会报错
cfstr = (__bridge_transfer CFMutableStringRef)mString;
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
}
NSLog(@"retain count = %ld",CFGetRetainCount(cfstr));
-
- 总结
- Objective-C对象 -> Core Foundation对象用__bridge或__bridge_retain。
- Core Foundation对象 -> Objective-C对象用__bridge或__bridge_transfer。