一、BLOCK 循环引用

一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身。构成循环引用。

// 定义 block 的时候,会对外部变量做一次 copy,强引用, self自身为强引用。

解决方案如下:

  1. #import "ViewController.h"
  2. #import "NetworkTools.h"
  3.  
  4. @interface ViewController ()
  5. @property (nonatomic, strong) NetworkTools *tools;
  6. @end
  7.  
  8. @implementation ViewController
  9. // 1. 解除循环引用,需要注意打断引用链条即可!
  10. - (void)viewDidLoad {
  11. [super viewDidLoad];
  12.  
  13. // 局部变量不会产生循环应用,全局属性会产生循环引用
  14. self.tools = [[NetworkTools alloc] init];
  15.  
  16. // 1. 定义 block 的时候,会对外部变量做一次 copy,会对 self 进行强引用
  17.  
  18. // 解除循环引用方法1
  19. // __weak 是 iOS 5.0 推出的
  20. // 如果异步操作没有完成,释放控制器,__weak 本身是弱引用
  21. // 当异步执行完毕,进行回调,self 已经被释放,无法访问属性,也无法调用方法
  22. // __weak 相当于 weak,不会做强引用,但是如果对象被释放,执行的地址,会指向 nil
  23. // __weak 更安全
  24. __weak typeof(self) weakSelf = self;
  25.  
  26. // 解除循环引用方法2
  27. // __unsafe_unretained 是 iOS 4.0 推出的
  28. // MRC 经典错误,EXC_BAD_ACCESS 坏内存访问,野指针
  29. // 相当于 assign,不会做强引用,但是如果对象被释放,内存地址保持不变,如果此时再调用,就会出现野指针访问
  30. // __unsafe_unretained typeof(self) weakSelf = self;
  31.  
  32. [self.tools loadData:^(NSString *html) {
  33. // strongSelf 强引用,对 weakSelf 进行强引用,本意,希望在异步完成后,继续执行回调代码
  34. //然而并没有什么作用!!!!!!!!
  35. __strong typeof(self) strongSelf = weakSelf;
  36.  
  37. NSLog(@"%@ %@", html, strongSelf.view);
  38. }];
  39. }
  40.  
  41. - (void)dealloc {
  42. NSLog(@"控制器 88");
  43. }
  44.  
  45. @end

二、计时器NSTimer循环引用

主要是因为从timer的角度,timer认为调用方self被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从self的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。

例子说明:

一方面,NSTimer经常会被作为某个类的成员变量,而NSTimer初始化时要指定self为target,容易造成循环引用。 另一方面,若timer一直处于validate的状态,则其引用计数将始终大于0。先看一段NSTimer使用的例子(ARC模式):

  1. import <Foundation/Foundation.h>
  2.  
  3. interface Friend : NSObject
  4. -(void)cleanTimer;
  5. end
  6.  
  7. import "Friend.h"
  8.  
  9. interface Friend ()
  10. STimer *_timer;
  11. end
  12.  
  13. implementation Friend
  14.  
  15. -(id)init
  16. {
  17. if (self = [super init]) {
  18. _timer = [NSTimer scheduledTimerWithTimeInterval: target:self selector:@selector(handleTimer:)
  19. userInfo:nil repeats:YES];
  20. }
  21. return self;
  22. }
  23.  
  24. - (void)handleTimer:(id)sender
  25. {
  26. NSLog(@"%@ say: Hi!", [self class]);
  27. }
  28.  
  29. - (void)cleanTimer
  30. {
  31. [_timer invalidate];
  32. _timer = nil;
  33. }
  34.  
  35. - (void)dealloc
  36. {
  37. [self cleanTimer];
  38. NSLog(@"[Friend class] is dealloced");
  39. }
  40.  
  41. @end

在类外部初始化一个Friend对象,并延迟5秒后将friend释放(外部运行在非arc环境下)

  1. Friend *f = [[Friend alloc] init];
  2. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, *NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  3. [f release];
  4. });

我们所期待的结果是,初始化5秒后,f对象被release,f的dealloc方法被调用,在dealloc里面timer失效,对象被析构。但结果却是如此:

  1. -- ::35.300 WZLCodeLibrary[:] Friend say: Hi!
  2. -- ::36.299 WZLCodeLibrary[:] Friend say: Hi!
  3. -- ::37.300 WZLCodeLibrary[:] Friend say: Hi!
  4. -- ::38.299 WZLCodeLibrary[:] Friend say: Hi!
  5. -- ::39.299 WZLCodeLibrary[:] Friend say: Hi!//运行了5次后没按照预想的停下来
  6. -- ::40.299 WZLCodeLibrary[:] Friend say: Hi!
  7. -- ::41.300 WZLCodeLibrary[:] Friend say: Hi!
  8. -- ::42.300 WZLCodeLibrary[:] Friend say: Hi!
  9. -- ::43.299 WZLCodeLibrary[:] Friend say: Hi!
  10. -- ::44.300 WZLCodeLibrary[:] Friend say: Hi!<br>.......根本停不下来.....

这是为什么呢?主要是因为从timer的角度,timer认为调用方(Friend对象)被析构时会进入dealloc,在dealloc可以顺便将timer的计时停掉并且释放内存;但是从Friend的角度,他认为timer不停止计时不析构,那我永远没机会进入dealloc。循环引用,互相等待,子子孙孙无穷尽也。问题的症结在于-(void)cleanTimer函数的调用时机不对,显然不能想当然地放在调用者的dealloc中。一个比较好的解决方法是开放这个函数,让Friend的调用者显式地调用来清理现场。如下:

  1. Friend *f = [[Friend alloc] init];
  2. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, *NSEC_PER_SEC), dispatch_get_main_queue(), ^{
  3. [f cleanTimer];
  4. [f release];
  5. });

三、委托delegate

在委托问题上出现循环引用问题已经是老生常谈了,本文也不再细讲,规避该问题的杀手锏也是简单到哭,一字诀:声明delegate时请用assign(MRC)或者weak(ARC),千万别手贱玩一下retain或者strong,毕竟这基本逃不掉循环引用了!

上面说的是我们常见的,其实循环引用就是说我们的强引用形成了闭环,还会有很多自己写的代码中会出现,平时还是要注意写法。

不好意思,下面再啰嗦一遍,进一步说明:

  1. 循环引用,指的是多个对象相互引用时,使得引用形成一个环形,导致外部无法真正是否掉这块环形内存。其实有点类似死锁。
  2.  
  3. 举个例子:A->B->C->....->X->B ->表示强引用,这样的B的引用计数就是2,假如A被系统释放了,理论上A会自动减小A所引用的资源,就是B,那么这时候B的引用计数就变成了1,所有B无法被释放,然而A已经被释放了,所有B的内存部分就肯定无法再释放再重新利用这部分内存空间了,导致内存泄漏。
  4.  
  5. 情况一:delegate
  6.  
  7. Delegateios中开发中最常遇到的循环引用,一般在声明delegate的时候都要使用弱引用weak或者assign
  8.  
  9. @property (nonatomic, weak, nullable) id <UITableViewDelegate> delegate;
  10. 当然怎么选择使用assign还是weakMRC的话只能用assign,在ARC的情况下最好使用weak,因为weak修饰的变量在是否后自动为指向nil,防止不安全的野指针存在
  11.  
  12. 情况二:Block
  13.  
  14. Block也是比较常见的循环引用问题,在Block中使用了self容易出现循环引用,因此很多人在使用block的时候,加入里面有用到self的操作都会声明一个__weak来修饰self。其实便不是这样的,不是所有使用了Block都会出现Self循环引用问题,只有self拥有Block的强引用才会出现这种情况。
  15.  
  16. 所以一般在函数中临时使用Block是不会出现循环应用的,因为这时候Block引用是属于栈的。当栈上的block释放后,block中对self的引用计数也会减掉
  17.  
  18. 当然不一定要SelfBlock有直接的引用才会出现,假如self的变量BB中有个Block变量,就容易出现这种情况,好的是在block出现循环引用的,xcode7会出现警告提示(之前版本不确定)。
  19.  
  20. 情况三:NSTimer
  21.  
  22. 这是一个神奇的NSTimer,当你创建使用NSTimer的时候,NSTimer会默认对当前self有个强引用,所有在self使用完成打算是否的时候,一定要先使用NSTimerinvalidate来停止是否时间控制对self的引用
  23.  
  24. [_timer invalidate];

iOS 循环引用解决方案的更多相关文章

  1. iOS循环引用

    iOS循环引用 当前类的闭包/Block属性,用到了当前类,就会造成循环引用 此闭包/Block应该是当前类的属性,我们经常对Block进行copy,copy到堆中,以便后用. 单方向引用是不会产生循 ...

  2. Atitit.json xml 序列化循环引用解决方案json

    Atitit.json xml 序列化循环引用解决方案json 1. 循环引用1 2. 序列化循环引用解决方法1 2.1. 自定义序列化器1 2.2. 排除策略1 2.3. 设置序列化层次,一般3级别 ...

  3. iOS 循环引用讲解(中)

    谈到循环引用,可能是delegate为啥非得用weak修饰,可能是block为啥要被特殊对待,你也可能仅仅想到了一个weakSelf,因为它能解决99%的关于循环引用的事情.下面我以个人的理解谈谈循环 ...

  4. iOS 循环引用

    1.循环引用一般是指:A持有B,B同时持有A,从而导致死循环无法释放对象. 2.一般循环引用出现在block和delegate中,而一般解决方法就是将self变成weakSelf(强引用变成弱引用), ...

  5. iOS循环引用问题

    今天面试问道了循环引用,所以就看了看,原来只是知道使用了Block容易造成循环引用.今天就来简单的介绍一些循环引用. 先来简单介绍一下什么是循环引用? 循环引用可以简单的理解成:A对象引用了B对象,B ...

  6. iOS循环引用常见场景和解决办法

    好多场景会导致循环引用,例如使用Block.线程.委托.通知.观察者都可能会导致循环引用. 1.委托 遵守一个规则,委托方持有代理方的强引用,代理方持有委托方的弱引用. 实际场景中,委托方会是一个控制 ...

  7. iOS 循环引用 委托 (实例说明)

    如何避免循环引用造成的内存泄漏呢: 以delegate模式为例(viewcontroller和view之间就是代理模式,viewcontroller有view的使用权,viewcontroller同时 ...

  8. CADisplayLink、NSTimer循环引用解决方案

    前言:CADisplayLink.NSTimer 循环引用问题 ​ CADisplayLink.NSTimer会对Target产生强引用,如果target又对他们产生强引用,那么就会引发循环引用. @ ...

  9. OC MRC之循环引用问题(代码分析)

    // // main.m // 07-循环引用 // // Created by apple on 13-8-9. // Copyright (c) 2013年 itcast. All rights ...

随机推荐

  1. 激活层和pooling的作用

    激活层: 激活函数其中一个重要的作用是加入非线性因素的,将特征映射到高维的非线性区间进行解释,解决线性模型所不能解决的问题 pooling层: 1. invariance(不变性),这种不变性包括tr ...

  2. c++之函数值传递和引用传递解析----关键在于理解函数return的实现机制(内存分配)

    函数调用过程解析 func里的a存储在调用fun函数时开辟的栈空间里,这块栈只在调用func时对func可用,调用结束后返回的a,其实是暂存在寄存器里的(一般情况下是eax),而返回到main里时,m ...

  3. SQL连接查询和嵌套查询详解

    连接查询 若一个查询同时涉及两个或两个以上的表,则称之为连接查询.连接查询是数据库中最最要的查询, 包括: 1.等值连接查询 2.自然连接查询 3.非等值连接查询 4.自身连接查询 5.外连接查询 6 ...

  4. phpStorm的远端部署

    首先远端服务器的路径: /var/www -rwxrwxrwx jiangzhaowei jiangzhaowei 6月 index.html* lr-xr-xr-x root root 2月 php ...

  5. 错误 128 无法将类型“string”隐式转换为“System.Windows.Forms.DataGridViewTextBoxColumn”

    原因是DataGridView中列的Name属性值和DataPropertyName属性值一样,比如Name="CardID",DataPropertyName="Car ...

  6. vue watch 深度监听以及立即监听

    vue watch对象可以监听数据,数据发生变化,处理函数 watch虽可以监听,但只是浅监听,只监听数据第一层或者第二层.比如对于整个对象的监听,需要用到深度监听 vm.$watch('obj',f ...

  7. Hibernate的session.createSQLQuery的几种查询方式

    当我们用HQL进行子查询的时候,如select * from Tree where pid in (select id from Tree,此时HIBERANTE就会报错,说什么*号错误之类的.但如果 ...

  8. codevs-1204

    1204 寻找子串位置 题目描述 Description 给出字符串a和字符串b,保证b是a的一个子串,请你输出b在a中第一次出现的位置. 输入描述 Input Description 仅一行包含两个 ...

  9. Several ports (8005, 8080, 8009) required

    Several ports (8005, 8080, 8009) required by Tomcat v7.0 Server at localhost are already in use. The ...

  10. Node.js学习(第四章:初见express)

    Express框架是一款简洁而灵活的node.js web应用框架.前面我们自己手动创建服务器在Express中就是一个API的事情,这就使得我们更加注重业务的功能和开发效率上,不必纠结过多底层的事情 ...