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

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

项目简单的类结构

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] 的.

局部变量不会造成,执行完自动释放,不会造成内存泄漏 - 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] .

例2:

- (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,为什么 不会产生循环引用呢。

匪夷所思如果有哪个大神路过,麻烦给个思路,我真想不明白。

场景二

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

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,有好多 线程同步的 知识,望 共同努力,共勉,手动!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,047评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,807评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,501评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,839评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,951评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,117评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,188评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,929评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,372评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,679评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,837评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,536评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,168评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,886评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,129评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,665评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,739评论 2 351

推荐阅读更多精彩内容