• iOS 循环引用解决方案


    一、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:1 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, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    3     [f release];
    4 });

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

     1 2015-03-18 18:00:35.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
     2 2015-03-18 18:00:36.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
     3 2015-03-18 18:00:37.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
     4 2015-03-18 18:00:38.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
     5 2015-03-18 18:00:39.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!//运行了5次后没按照预想的停下来
     6 2015-03-18 18:00:40.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
     7 2015-03-18 18:00:41.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
     8 2015-03-18 18:00:42.300 WZLCodeLibrary[41422:3390529] Friend say: Hi!
     9 2015-03-18 18:00:43.299 WZLCodeLibrary[41422:3390529] Friend say: Hi!
    10 2015-03-18 18:00:44.300 WZLCodeLibrary[41422:3390529] 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, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
    3     [f cleanTimer];
    4     [f release];
    5 });

    三、委托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];
  • 相关阅读:
    Python-dict与set
    Python-实现对表插入百万条数据
    Python—元组tuple
    数据库查询
    python-操作MySQL数据库
    Python-类的继承
    Python-内置类属性
    Python-类的概念及使用1
    Python异常处理
    了解dto概念,什么是DTO
  • 原文地址:https://www.cnblogs.com/xujinzhong/p/8427572.html
Copyright © 2020-2023  润新知