iOS 循环引用解决方案
一、BLOCK 循环引用
一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身。构成循环引用。
// 定义 block 的时候,会对外部变量做一次 copy,强引用, self自身为强引用。
解决方案如下:
- #import "ViewController.h"
- #import "NetworkTools.h"
- @interface ViewController ()
- @property (nonatomic, strong) NetworkTools *tools;
- @end
- @implementation ViewController
- // 1. 解除循环引用,需要注意打断引用链条即可!
- - (void)viewDidLoad {
- [super viewDidLoad];
- // 局部变量不会产生循环应用,全局属性会产生循环引用
- self.tools = [[NetworkTools alloc] init];
- // 1. 定义 block 的时候,会对外部变量做一次 copy,会对 self 进行强引用
- // 解除循环引用方法1
- // __weak 是 iOS 5.0 推出的
- // 如果异步操作没有完成,释放控制器,__weak 本身是弱引用
- // 当异步执行完毕,进行回调,self 已经被释放,无法访问属性,也无法调用方法
- // __weak 相当于 weak,不会做强引用,但是如果对象被释放,执行的地址,会指向 nil
- // __weak 更安全
- __weak typeof(self) weakSelf = self;
- // 解除循环引用方法2
- // __unsafe_unretained 是 iOS 4.0 推出的
- // MRC 经典错误,EXC_BAD_ACCESS 坏内存访问,野指针
- // 相当于 assign,不会做强引用,但是如果对象被释放,内存地址保持不变,如果此时再调用,就会出现野指针访问
- // __unsafe_unretained typeof(self) weakSelf = self;
- [self.tools loadData:^(NSString *html) {
- // strongSelf 强引用,对 weakSelf 进行强引用,本意,希望在异步完成后,继续执行回调代码
- //然而并没有什么作用!!!!!!!!
- __strong typeof(self) strongSelf = weakSelf;
- NSLog(@"%@ %@", html, strongSelf.view);
- }];
- }
- - (void)dealloc {
- NSLog(@"控制器 88");
- }
- @end
二、计时器NSTimer循环引用
主要是因为从timer的角度,timer认为调用方self被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从self的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。
例子说明:
一方面,NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用。 另一方面,若timer一直处于validate的状态,则其引用计数将始终大于0。先看一段NSTimer使用的例子(ARC模式):
- import <Foundation/Foundation.h>
- interface Friend : NSObject
- -(void)cleanTimer;
- end
- import "Friend.h"
- interface Friend ()
- STimer *_timer;
- end
- implementation Friend
- -(id)init
- {
- if (self = [super init]) {
- _timer = [NSTimer scheduledTimerWithTimeInterval: target:self selector:@selector(handleTimer:)
- userInfo:nil repeats:YES];
- }
- return self;
- }
- - (void)handleTimer:(id)sender
- {
- NSLog(@"%@ say: Hi!", [self class]);
- }
- - (void)cleanTimer
- {
- [_timer invalidate];
- _timer = nil;
- }
- - (void)dealloc
- {
- [self cleanTimer];
- NSLog(@"[Friend class] is dealloced");
- }
- @end
在类外部初始化一个Friend对象,并延迟5秒后将friend释放(外部运行在非arc环境下)
- Friend *f = [[Friend alloc] init];
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, *NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [f release];
- });
我们所期待的结果是,初始化5秒后,f对象被release,f的dealloc方法被调用,在dealloc里面timer失效,对象被析构。但结果却是如此:
- -- ::35.300 WZLCodeLibrary[:] Friend say: Hi!
- -- ::36.299 WZLCodeLibrary[:] Friend say: Hi!
- -- ::37.300 WZLCodeLibrary[:] Friend say: Hi!
- -- ::38.299 WZLCodeLibrary[:] Friend say: Hi!
- -- ::39.299 WZLCodeLibrary[:] Friend say: Hi!//运行了5次后没按照预想的停下来
- -- ::40.299 WZLCodeLibrary[:] Friend say: Hi!
- -- ::41.300 WZLCodeLibrary[:] Friend say: Hi!
- -- ::42.300 WZLCodeLibrary[:] Friend say: Hi!
- -- ::43.299 WZLCodeLibrary[:] Friend say: Hi!
- -- ::44.300 WZLCodeLibrary[:] Friend say: Hi!<br>.......根本停不下来.....
这是为什么呢?主要是因为从timer的角度,timer认为调用方(Friend对象)被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从Friend的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。问题的症结在于-(void)cleanTimer函数的调用时机不对,显然不能想当然地放在调用者的dealloc中。一个比较好的解决方法是开放这个函数,让Friend的调用者显式地调用来清理现场。如下:
- Friend *f = [[Friend alloc] init];
- dispatch_after(dispatch_time(DISPATCH_TIME_NOW, *NSEC_PER_SEC), dispatch_get_main_queue(), ^{
- [f cleanTimer];
- [f release];
- });
三、委托delegate
在委托问题上出现循环引用问题已经是老生常谈了,本文也不再细讲,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了!
上面说的是我们常见的,其实循环引用就是说我们的强引用形成了闭环,还会有很多自己写的代码中会出现,平时还是要注意写法。
不好意思,下面再啰嗦一遍,进一步说明:
- 循环引用,指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。
- 举个例子:A->B->C->....->X->B ->表示强引用,这样的B的引用计数就是2,假如A被系统释放了,理论上A会自动减小A所引用的资源,就是B,那么这时候B的引用计数就变成了1,所有B无法被释放,然而A已经被释放了,所有B的内存部分就肯定无法再释放再重新利用这部分内存空间了,导致内存泄漏。
- 情况一:delegate
- Delegate是ios中开发中最常遇到的循环引用,一般在声明delegate的时候都要使用弱引用weak或者assign
- @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
- 当然怎么选择使用assign还是weak,MRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在是否后自动为指向nil,防止不安全的野指针存在
- 情况二:Block
- Block也是比较常见的循环引用问题,在Block中使用了self容易出现循环引用,因此很多人在使用block的时候,加入里面有用到self的操作都会声明一个__weak来修饰self。其实便不是这样的,不是所有使用了Block都会出现Self循环引用问题,只有self拥有Block的强引用才会出现这种情况。
- 所以一般在函数中临时使用Block是不会出现循环应用的,因为这时候Block引用是属于栈的。当栈上的block释放后,block中对self的引用计数也会减掉
- 当然不一定要Self对Block有直接的引用才会出现,假如self的变量B,B中有个Block变量,就容易出现这种情况,好的是在block出现循环引用的,xcode7会出现警告提示(之前版本不确定)。
- 情况三:NSTimer
- 这是一个神奇的NSTimer,当你创建使用NSTimer的时候,NSTimer会默认对当前self有个强引用,所有在self使用完成打算是否的时候,一定要先使用NSTimer的invalidate来停止是否时间控制对self的引用
- [_timer invalidate];
iOS 循环引用解决方案的更多相关文章
- iOS循环引用
iOS循环引用 当前类的闭包/Block属性,用到了当前类,就会造成循环引用 此闭包/Block应该是当前类的属性,我们经常对Block进行copy,copy到堆中,以便后用. 单方向引用是不会产生循 ...
- Atitit.json xml 序列化循环引用解决方案json
Atitit.json xml 序列化循环引用解决方案json 1. 循环引用1 2. 序列化循环引用解决方法1 2.1. 自定义序列化器1 2.2. 排除策略1 2.3. 设置序列化层次,一般3级别 ...
- iOS 循环引用讲解(中)
谈到循环引用,可能是delegate为啥非得用weak修饰,可能是block为啥要被特殊对待,你也可能仅仅想到了一个weakSelf,因为它能解决99%的关于循环引用的事情.下面我以个人的理解谈谈循环 ...
- iOS 循环引用
1.循环引用一般是指:A持有B,B同时持有A,从而导致死循环无法释放对象. 2.一般循环引用出现在block和delegate中,而一般解决方法就是将self变成weakSelf(强引用变成弱引用), ...
- iOS循环引用问题
今天面试问道了循环引用,所以就看了看,原来只是知道使用了Block容易造成循环引用.今天就来简单的介绍一些循环引用. 先来简单介绍一下什么是循环引用? 循环引用可以简单的理解成:A对象引用了B对象,B ...
- iOS循环引用常见场景和解决办法
好多场景会导致循环引用,例如使用Block.线程.委托.通知.观察者都可能会导致循环引用. 1.委托 遵守一个规则,委托方持有代理方的强引用,代理方持有委托方的弱引用. 实际场景中,委托方会是一个控制 ...
- iOS 循环引用 委托 (实例说明)
如何避免循环引用造成的内存泄漏呢: 以delegate模式为例(viewcontroller和view之间就是代理模式,viewcontroller有view的使用权,viewcontroller同时 ...
- CADisplayLink、NSTimer循环引用解决方案
前言:CADisplayLink.NSTimer 循环引用问题 CADisplayLink.NSTimer会对Target产生强引用,如果target又对他们产生强引用,那么就会引发循环引用. @ ...
- OC MRC之循环引用问题(代码分析)
// // main.m // 07-循环引用 // // Created by apple on 13-8-9. // Copyright (c) 2013年 itcast. All rights ...
随机推荐
- 激活层和pooling的作用
激活层: 激活函数其中一个重要的作用是加入非线性因素的,将特征映射到高维的非线性区间进行解释,解决线性模型所不能解决的问题 pooling层: 1. invariance(不变性),这种不变性包括tr ...
- c++之函数值传递和引用传递解析----关键在于理解函数return的实现机制(内存分配)
函数调用过程解析 func里的a存储在调用fun函数时开辟的栈空间里,这块栈只在调用func时对func可用,调用结束后返回的a,其实是暂存在寄存器里的(一般情况下是eax),而返回到main里时,m ...
- SQL连接查询和嵌套查询详解
连接查询 若一个查询同时涉及两个或两个以上的表,则称之为连接查询.连接查询是数据库中最最要的查询, 包括: 1.等值连接查询 2.自然连接查询 3.非等值连接查询 4.自身连接查询 5.外连接查询 6 ...
- phpStorm的远端部署
首先远端服务器的路径: /var/www -rwxrwxrwx jiangzhaowei jiangzhaowei 6月 index.html* lr-xr-xr-x root root 2月 php ...
- 错误 128 无法将类型“string”隐式转换为“System.Windows.Forms.DataGridViewTextBoxColumn”
原因是DataGridView中列的Name属性值和DataPropertyName属性值一样,比如Name="CardID",DataPropertyName="Car ...
- vue watch 深度监听以及立即监听
vue watch对象可以监听数据,数据发生变化,处理函数 watch虽可以监听,但只是浅监听,只监听数据第一层或者第二层.比如对于整个对象的监听,需要用到深度监听 vm.$watch('obj',f ...
- Hibernate的session.createSQLQuery的几种查询方式
当我们用HQL进行子查询的时候,如select * from Tree where pid in (select id from Tree,此时HIBERANTE就会报错,说什么*号错误之类的.但如果 ...
- codevs-1204
1204 寻找子串位置 题目描述 Description 给出字符串a和字符串b,保证b是a的一个子串,请你输出b在a中第一次出现的位置. 输入描述 Input Description 仅一行包含两个 ...
- Several ports (8005, 8080, 8009) required
Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use. The ...
- Node.js学习(第四章:初见express)
Express框架是一款简洁而灵活的node.js web应用框架.前面我们自己手动创建服务器在Express中就是一个API的事情,这就使得我们更加注重业务的功能和开发效率上,不必纠结过多底层的事情 ...