Block循环引用的三种解决方式

今天 抽空看了下 *Objective-C高级编程iOS与OSX多线程和内存管理*,发现本身以前所理解的为何block会发生循环引用?`有些理解是错误的,还好看了这个书,最后弄清楚了,但愿写出来,既能算是一种总结,又能让其余小伙伴避免再遇到这个坑!下面 让咱们一块儿来看几个场景!编程

项目简单的类结构bash

import "ViewController.h"
#import "DetailViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    
    DetailViewController *detailVC = [DetailViewController new];
    detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
        NSLog(@"d: %@--", d);
    };
    [self presentViewController:d animated:YES completion:nil];
}

//---------------------DetailViewController-----------------------------
@interface DetailViewController : UIViewController

@property (nonatomic, copy) void(^testMemoryLeaksBLock)(DetailViewController *detailVC);

@end

@implementation DetailViewController

-(void)dealloc {
    
    NSLog(@"dealloc");
}

- (void)viewDidDisappear:(BOOL)animated {
    
    [super viewDidDisappear:animated];
    !_testMemoryLeaksBLock ?: _testMemoryLeaksBLock(self);
}

- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
    [self dismissViewControllerAnimated:YES completion:nil];
}

复制代码

场景一

正如项目结构那样,当detailVC disappear时候,回调block是否会有内存泄漏呢?多线程

detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
        NSLog(@"d: %@--", d);
    };
复制代码

局部变量不会形成,执行完自动释放,不会形成内存泄漏 - 1

sow 正如上面分析图,通常状况下,是有内存泄漏的,但就是因为detailVC.testMemoryLeaksBLock所持有的d指针是一个局部变量,当block执行完以后,这个局部变量引用计数就为0,就被释放了,所以d 就再也不持有detailVCdetailVC.testMemoryLeaksBLockdetailVC就再也不持有, 循环引用被打破,仍是会走 -[DetailViewController dealloc] 的.app

局部变量不会形成,执行完自动释放,不会形成内存泄漏 - 2
####更正:block不会强引用 block内部的 局部变量weak弱指针,只会强引用 block 外部 strong指针,并非 block结束以后就会释放掉局部变量,因此不会引发循环,由于若是像那样说的话,假如block不执行,那局部变量岂不是就释放不掉了。 具体看一下例1:

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *student = [[Student alloc]init];
    student.name = @"Hello World";
    
    __weak typeof(student) weakStu = student;
    Student *strongStu = weakStu;
    student.study = ^{
        
        //第1种写法
        //Student *strongStu = weakStu;
        //第2种写法
        //__strong typeof(weakStu) strongStu = weakStu;
        //第3种写法
        //typeof(student) strongStu = weakStu;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            NSLog(@"my name is = %@", strongStu.name);
        });
    };
}
复制代码

例1是有内存泄漏的没有走-[Student dealloc],由于未执行student.study, 因此dispatch_after block也不会走,可是dispatch_after bLock却强引用了strongStu仍是发生了循环引用。这个比较好理解。可是下面例2,就改了一行代码 我怎么也想不通为何 没有发生循环引用,走了-[Student dealloc] .ui

例2:atom

- (void)viewDidLoad {
    [super viewDidLoad];
    
    Student *student = [[Student alloc]init];
    student.name = @"Hello World";
    
    __weak typeof(student) weakStu = student;
    student.study = ^{

        /**
         * 三种写法是同样的效果,都是为了防止局部变量`student`在`viewDidLoad `以后销毁。若是不这样写的话,
         * 因为`student`局部变量被销毁,因此为nil,再走到`dispatch_after block`时候,因为weakStu是弱指针,
         * 因此不会强引用,最后打印为null,这不是咱们想要的效果,`AFNetWorking`好多第三方库也是这么写的
         * 第1种写法
         * Student *strongStu = weakStu;
         * 第2种写法
         * __strong typeof(weakStu) strongStu = weakStu;
         * 第3种写法
         * typeof(student) strongStu = weakStu;
         */
        //随便取哪种写法,这里取第一种写法
         Student *strongStu = weakStu;
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            
            NSLog(@"my name is = %@", strongStu.name);
        });
    };
}
复制代码

dispatch_after block 强引用了外部变量strongStu ,这种不调用student.study()的写法为何没有循环引用呢,可是若是在ViwDidLoad结尾调用student.study(),那么会在2秒后执行完dispatch_after block才会走-[Student dealloc],不就说明dispatch_after block持有这个student,走完才回销毁,那若是不执行student.study()的话,按道理讲,应该也会被dispatch_after block持有这个student,为何 不会产生循环引用呢。spa

匪夷所思若是有哪一个大神路过,麻烦给个思路,我真想不明白。线程

场景二

block代码改为下面这样,当detailVC disappear时候,回调block是否又会有内存泄漏呢?3d

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
 detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
     NSLog(@"d: %@--\nd: %@",  d,  tmp); 
   };
复制代码

答案:的确有内存泄漏 ,由于循环引用的问题,具体看下图:指针

循环引用形成内存泄漏

上面状况若是懂的话,下面这种写法是同样的,经典的循环引用 detailVC持有testMemoryLeaksBLocktestMemoryLeaksBLock 持有 detailVC,致使两个引用计数都没法减一,最后谁也释放不了谁!!

DetailViewController * detailVC = [DetailViewController new]; 
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {
        
     NSLog(@"detailVC: %@",  detailVC); 
   };
复制代码

##如何解决内存泄漏 缘由咱们了解了,如今是该如何解决了,下面根据以前场景逐一给出不一样的思路,注意思路很重要,由于有不少种解决思路,都要搞清楚,触类旁通,由于场景一没有内存泄漏,所以主要针对场景二 ###针对场景二的解决方案1: 有问题的代码:

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

    NSLog(@"tmp: %@",  tmp); 
  };
复制代码

方案一的代码:

DetailViewController * detailVC = [DetailViewController new];
 __block id tmp = detailVC;
 detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

     NSLog(@"tmp: %@",  tmp); 
     tmp = nil;
 };
复制代码

虽然解决了内存泄漏,可是细心的看客姥爷确定发现了这样写的一个弊端,没错,那就是 若是 detailVC.testMemoryLeaksBLock ()没有调用的话,仍是会形成内存泄漏的,由于testMemoryLeaksBLock仍是间接地强引用了detailVC, 算是一个思路吧,毕竟思路要广才能 想的更多,学的更多,不是吗!

###针对场景二的解决方案2: 有问题的代码:

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

    NSLog(@"tmp: %@",  tmp); 
  };
复制代码

方案二的代码:

DetailViewController * detailVC = [DetailViewController new];
__weak id weakTmp = detailVC;
 detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

     NSLog(@"weakTmp: %@",  weakTmp); 
 };
复制代码

这也是日常开发中用得最多的一种解决循环引用的方法。来给小总结吧:方案一和方案二都是断掉testMemoryLeaksBLockdetailVC的强引用,天然能够;其实开发中还有一种方案也是能够的,那就是 断掉detailVCtestMemoryLeaksBLock 的强引用。

###针对场景二的解决方案3: 有问题的代码:

DetailViewController *detailVC = [DetailViewController new]; 
id  tmp = detailVC;
detailVC.testMemoryLeaksBLock = ^(DetailViewController *d) {

    NSLog(@"tmp: %@",  tmp); 
  };
复制代码

方案三的代码:

@implementation DetailViewController

-(void)dealloc {
    
    NSLog(@"dealloc");
}

- (void)viewDidDisappear:(BOOL)animated {
    
    [super viewDidDisappear:animated];
    !_ testMemoryLeaksBLock ?: _testMemoryLeaksBLock(self);
    //就加了下面一行代码也是能够的,由于一旦手动把 _testMemoryLeaksBLock置为空,  那么这个block就没有任何对象持有它,
    //换一句话说就是没有对象强引用这个block, 那么若是这个block以前在堆里,它就会被废弃掉,
    _testMemoryLeaksBLock= nil;
}
@end
复制代码

每一次执行完block以后都手动置nil,断掉detailVCtestMemoryLeaksBLock 的强引用也不失为一种方法。

####更正:block不会强引用 block内部的局部变量和 弱指针,只会强引用 block 外部strong指针,并非 block结束以后就会释放掉局部变量,因此不会引发循环,由于若是像那样说的话,假如block不执行,那局部变量岂不是就释放不掉了。 ####总结:这也是日常开发中三种解决循环引用的方法。但愿你们周末都玩得开心,么么哒,下周准备写 GCD,有好多 线程同步的 知识,望 共同努力,共勉,手动!

相关文章
相关标签/搜索