你知道block循环引用怎么解决了吗?

简介: 在使用block的时候,最需要注意的问题便是循环引用,这节主要讲解,block是如何产生的?碰到循环引用又该怎么解决。

一、循环引用的产生


1.1、代码示例

1、Person.h


#import <Foundation/Foundation.h>
@interface Person : NSObject
@property (nonatomic,assign) int age;
@property (nonatomic,copy) void(^block)(void);
@end


2、Person.m


#import "Person.h"
@implementation Person
-(void)dealloc{
    NSLog(@"---------Person__dealloc");
}
@end


3、main.m


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 10;
        person.block = ^{
            NSLog(@"-------:%d",person.age);
        };
        NSLog(@"-------::::::::---------");
    }
    return 0;
}


4、编译后,打印日志:


block-01[14499:3644589] -------::::::::---------
Program ended with exit code: 0


发现Person对象没有被销毁。


1.2、流程分析

编译完代码之后,发现 Person 对象没有被销毁,让我们来分析一下原因,分为两段:

1、当程序编译完成时候:

5119a540f5894c768cdd05088d820030.png

1、main.m类中有一个person指针指向了Person类

2、底层新建一个block结构体对象,内部也有一个person指针,也指向了Person类(强引用)


3、Person类中有一个成员变量_block(属性block编译之后生成),它指向了block结构。

其结构如下图:

29ee35ed79664f0196b83d754e8cd728.png

[外链图片转存失败,源站可能有防盗在这里插入!链机制,建描述]议将图片上https://传(imblog.snimg.Pl5Gcn/5a3fc0b04709486b6807d6aaf52c5327.png#pic_center)https://img-blog.csdnimg.c/5a3fc0b0470946b88607d6aaf52c5327.png#pic_center)]


2、当程序运行完,离开代码作用域时:


1、main.m类中的person指针(在栈中)被销毁。

2、但此时,block结构体对象和Person类存在互相引用的问题,而且是强引用,导致这两个对象互相持有,无法被销毁。

其结构如下图:


a0317b28d22c4b45b9b4ef79f6497436.png

简而言之,就是Person对象中的_block成员变量持有了Block结构体,而Block结构体中也有一个成员变量(self)持有了Person对象。

1.3、总结

对象强引用Block,Block强引用对象。两个强引用,你引用我,我引用你,导致内存无法释放。

如下图:

0f564965d628404e90f288b37d79d01c.png


二、循环引用的解决


2.1、思路分析

那具体又是怎么操作呢,我们在拿上面的循环引用图,分析一下:

1695c3ca443d4eb2bb2eaf4de0107c1f.png


已知【1、2】两个引用都是强引用,我们只要把其中一个引用设置成弱引用就可以了。那么设置哪一条为弱引用更合理呢?

在代码的实现过程中,我们希望不管什么时候引用Person类的block变量他都是没有问题的,只要Person没有被释放,block结构体也不能被释放。所以线条【2】最好时强引用关系。那么只能将线条【1】设置成弱引用关系(用虚线表示),如下图:

ae62a647f30941768d600a2e938e5f93.png


2.2、解决方案


2.1、ARC编译环境

1、用__weak、__unsafe_unretained解决

__weak:不会产生强引用


指向的对象销毁时,自己会主动销毁,变成 nil。


__unsafe_unretained:不会强引用,不安全。


指向的对象销毁时,自己不会主动销毁,会变成野指针。(虽然能解决问题,因为他不安全,不推荐使用)


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        Person *person = [[Person alloc] init];
        person.age = 10;
        __weak Person *weakP = person;
        // 跟上一句代码一个作用,使用跟方便
        // __weak typeof(person) weakP = person;
        person.block = ^{
            NSLog(@"-------:%d",weakP.age);
        };
        NSLog(@"-------::::::::---------");
    }
    return 0;
}


底层结构:


785efc8cb6dc441ea2a562c6738f590b.png

打印日志:


block-01[14499:3644589] -------::::::::---------
block-01[14499:3644589] ---------Person__dealloc
Program ended with exit code: 0

此时Person对象被销毁了。


2、用__block解决

注意:必须调用block、block函数执行完之后必须将对象置为nil。


示例代码


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *person = [[Person alloc] init];
        person.age = 10;
        person.block = ^{
            NSLog(@"-------:%d",person.age);
            // 2、函数执行完成之后,必须将对象清空
            person = nil;
        };
        // 1、必须执行block函数
        person.block();
        NSLog(@"-------::::::::---------");
    }
    return 0;
}


缺点:


1、代码量多了。

2、必须要执行block函数(如果都没有执行block函数,那么它就一直存在,导致内存泄漏)


综上,最好的解决方案使用 __weak 引用。


2.2、MRC编译环境

1、用__unsafe_unretained解决

因为MRC编译环境下是不支持 __weak 弱指针修饰的。


代码示例


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __unsafe_unretained Person *person = [[Person alloc] init];
        person.age = 10;
        person.block = ^{
            NSLog(@"-------:%d",person.age);
        };
        person.block();
        [person release];
        NSLog(@"-------::::::::---------");
    }
    return 0;
}


2、用__block解决

在之前我们知道,__block修饰符 ARC时会retain,MRC时不会retain。

那么在MRC时,可以解决循环引用问题。


#import <Foundation/Foundation.h>
#import "Person.h"
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        __block Person *person = [[Person alloc] init];
        person.age = 10;
        person.block = ^{
            NSLog(@"-------:%d",person.age);
        };
        person.block();
        [person release];
        NSLog(@"-------::::::::---------");
    }
    return 0;
}


打印日志:


2022-05-09 13:29:02.145827+0800 block-01[18252:3704010] -------:10
2022-05-09 13:29:02.146321+0800 block-01[18252:3704010] ---------Person__dealloc
2022-05-09 13:29:02.146382+0800 block-01[18252:3704010] -------::::::::---------
Program ended with exit code: 0

引入弱引用之前:

06ed6a64fd884b9090e582b3dc0bf156.png


引入弱引用之后:

6cc2866d1e634c09bd3d1bd0c1d52349.png

相关文章
|
1月前
|
C++
记录一次循环引用的问题
记录一次循环引用的问题
|
1月前
|
存储 安全 Java
synchronized原理-字节码分析、对象内存结构、锁升级过程、Monitor
本文分析的问题: 1. synchronized 字节码文件分析之 monitorenter、monitorexit 指令 2. 为什么任何一个Java对象都可以成为一把锁? 3. 对象的内存结构 4. 锁升级过程 (无锁、偏向锁、轻量级锁、重量级锁) 5. Monitor 是什么、源码查看(hotspot虚拟机源码) 6. JOL工具使用
|
11月前
|
存储 数据库
如何解决循环引用的问题
解决循环引用的问题
137 0
|
11月前
|
Java C++
JVM学习日志(九) 对象的finalization机制
对象的finalization机制 简述
84 0
|
1月前
shared_ptr循环引用问题以及解决方法
shared_ptr循环引用问题以及解决方法
59 0
一次性讲清楚 Handler 使用不当导致的内存泄露?
一次性讲清楚 Handler 使用不当导致的内存泄露?
一次性讲清楚 Handler 使用不当导致的内存泄露?
|
IDE Java 编译器
记一次inline使用不当导致编译期Null指针的排查过程
周五的一个下午,我哼着小曲和往常一样合完代码。准备运行试试看,结果build时发现了这样一个异常。
122 0
记一次inline使用不当导致编译期Null指针的排查过程
|
iOS开发
block的循环引用分析
block的循环引用分析
124 0
block的循环引用分析
|
iOS开发
iOS开发:block死循环及__weak弱引用提前释放的问题解决
block死循环及__weak弱引用提前释放的问题解决
295 0
Block循环引用
Block循环引用
76 0