iOS之循环引用问题
- 循环引用的产生
- 内存分布区域
- 内存管理机制
- 循环引用常见问题
循环引用的产生
内存中和变量有关的分区:堆、栈、静态区。其中,栈和静态区是操作系统自己管理的,对程序员来说相对透明,所以,一般我们只需要关注堆的内存分配,而循环引用的产生,也和其息息相关,即循环引用会导致堆里的内存无法正常回收,从而导致内存泄漏。
内存分布区域
1、栈区(stack)— 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。分配内存的时候,栈区的地址是 从上往下分配(越分配地址越小 i 地址比j大)
2、堆区(heap) — 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。堆区的地址是 由下往上的
3、全局区(静态区)(static)— 全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区— 常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区— 存放函数体的二进制代码。
内存管理模型
在iOS中有2套内存管理机制:MRC(MannulReference Counting)和ARC(Automatic Reference Counting)。其中ARC起源于iOS 4.3,在那之前,苹果开发者只能手动使用retain和release来进行内存管理。这样做的问题很明显,如果你在开发过程中,一个不小心没有retain和release成对出现,那么很容易使得内存没有释放,最后导致程序内存不足而导致闪退。
MRC
在你创建一个对象的时候,他会需要进行retain,然后在你不在持有他的时候进行release,所以每个对象都有一个retain count来进行计数。
内存管理规则如下:
1> 当调用alloc、new、copy(mutableCopy)方法产生一个新对象的时候,就必须在最后调用一次release或者autorelease
2> 当调用retain方法让对象的计数器+1,就必须在最后调用一次release或者autorelease
2.集合的内存管理细节
1> 当把一个对象添加到集合中时,这个对象会做了一次retain操作,计数器会+1
2> 当一个集合被销毁时,会对集合里面的所有对象做一次release操作,计数器会-1
3> 当一个对象从集合中移除时,这个对象会一次release操作,计数器会-1
3.普遍规律
1> 如果方法名是add\insert开头,那么被添加的对象,计数器会+1
2> 如果方法名是remove\delete开头,那么被移除的对象,计数器-1
ARC
iOS 5.0之后引用自动管理机制——自动引用计数(ARC),管理机制与手动机制一样,只是不再需要调用retain、release、autorelease;它编译时的特性,当你使用ARC时,在适当位置插入release和autorelease;它引用strong和weak关键字,strong修饰的指针变量指向对象时,当指针指向新值或者指针不复存在,相关联的对象就会自动释放,而weak修饰的指针变量指向对象,当对象的拥有者指向新值或者不存在时weak修饰的指针会自动置为nil。
循环引用常见问题
类相互引用
- (void)viewDidLoad {
[super viewDidLoad];
ClassA *a = [[ClassA alloc] init];
ClassB *b = [[ClassB alloc] init];
a.classB = b;
b.classA = a;
}
两个类 A 和 B A强引用B B强引用A 所以下面的代码 dealloc 不会走 发生循环引用
解决方法 让其中任意一个类 变成弱引用即可 即 @property (nonatomic, weak) ClassA *classA;
运行结果如下:
2.Block问题
- (void)blockFunc {
self.block = ^{
self.str = @"hello";//发生循环引用
NSLog(@"%@", self.str);
};
self.block();
}
可以发现发生循环引用 解决办法为 用weak 进行弱引用
- (void)blockFunc {
__weak typeof(self) weakSelf = self;
self.block = ^{
weakSelf.str = @"hello";
NSLog(@"%@", weakSelf.str);
};
self.block();
}
注:此处为自己的原创:
==========
/**
第二种方式 此种方法需要确保再self释放之后不会再有调用当前类的其他方法。
**/
self.nameStr = @"sssss";
__block ViewController2 *bSelf = self;
self.pushBlock = ^{
NSLog(@"name的值是:%@",bSelf.nameStr);
bSelf = nil;
};
self.pushBlock();
/**
第三种方式
将当前对象作为block的入参传入到Block内部去
**/
self.nameStr = @"ssss"
self.pushBlock = ^(ViewController2 *obj) {
NSLog(@"name的值是:%@",obj.nameStr);
};
self.pushBlock(self);
=========
原创结束
当在block中添加延迟函数时 下面函数就会出现问题
- (void)blockFunc {
self.str = @"hello";
__weak typeof(self) weakSelf = self;
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf.str);
});
};
self.block();
}
1 打印null 5秒之内VC返回 此时VC 页面消失 会走dealloc函数 故里面的weakSelf会为空
2 打印hello 5秒之后VC返回 延迟函数已执行完 打印完整的hello VC 消失 走dealloc
解决办法为 用strong 进行 强引用
- (void)blockFunc {
self.str = @"hello";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongSelf.str);
});
};
self.block();
}
首先 weakSelf 就是弱引用 防止里面循环引用 其次 内部的strongSelf是个 局部变量 仅存在于栈中 当strongSelf.str 执行完 strongSelf就会回收 不会造成循环引用 strongSelf 会使vc引用计数器加1 当其返回时 并不会立刻走dealloc函数 而是等strongSelf.str它执行完 引用计数为0 才会走dealloc 函数 所以不论什么时候返回 strongSelf.str都是有值的 这就是为什么__weak 和 __strong 成对出现的原因
3.NSTimer
- (void)viewDidLoad {
[super viewDidLoad];
[NSTimer scheduledTimerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
// 等同于 下面 RunLoop -> timer -> self
self.timer = [NSTimer timerWithTimeInterval:1.0 target:self selector:@selector(timerClicked) userInfo:nil repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:self.timer forMode:NSDefaultRunLoopMode];
}
- (void)timerClicked {
NSLog(@"timerClicked ----");
}
这段代码发生内存泄露 页面消失 timerClicked函数一直执行
解决办法为 didMoveToParentViewController函数 将timer 置为空 或在合适位置将其timer取消
- (void)didMoveToParentViewController:(UIViewController *)parent {
if (nil == parent) {
[self.timer invalidate];
self.timer = nil;
}
}