【iOS】——Block循环引用

循环引用原因

如果在Block中使用附有_ _strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block所持有,这样容易引起循环引用。

objective-c 复制代码
HPPerson *person = [[HPPerson alloc] init];
 person.block = ^{
             NSLog(@"person.age--- %d",person.age);
 };

在上面代码中,person对象强持有block对象,在block语法中,block对象又强持有person对象,此时达成互相强持有,谁也无法释法谁,造成循环引用。

当造成block循环引用时编译器会检测出并发出警告

另外,如果block内没有使用self也会捕获self,引起循环引用

objective-c 复制代码
typedef void (^blk_t) (void);

@interface HPPerson : NSObject
{
    blk_t _block;
    int _age;
}
@end
#import "HPPerson.h"

@implementation HPPerson
-(id)init {
    self = [super init];
    _block = ^{NSLog(@"age = %d", _age);};
    return self;
}

- (void)dealloc
{
    NSLog(@"%s", __func__);
}
@end

这是因为虽然没有使用self,但使用了self对象中的结构体成员,因此也会捕获self。

避免循环引用

使用weak修饰符

objective-c 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HPPerson *person = [[HPPerson alloc] init];
        person.age = 10;
        
        __weak HPPerson *weakPerson = person;
        
        person.block = ^{
            NSLog(@"person.age--- %d",weakPerson.age);
        };
        NSLog(@"--------");

    }
    return 0;
}

编译完成之后是

objective-c 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
    // block内部对weakPerson是弱引用
  HPPerson *__weak weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, HPPerson *__weak _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

局部变量消失时候,对于HPPerson来说,只有一个弱指针指向它,那它就销毁,然后block也销毁。

使用__unsafe_unretained修饰符

objective-c 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        HPPerson *person = [[HPPerson alloc] init];
        person.age = 10;
        
        __unsafe_unretained HPPerson *weakPerson = person;
        
        person.block = ^{
            NSLog(@"person.age--- %d",weakPerson.age);
        };
        NSLog(@"--------");

    }
    return 0;
}

编译完成之后是

objective-c 复制代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  HPPerson *__unsafe_unretained weakPerson;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, HPPerson *__unsafe_unretained _weakPerson, int flags=0) : weakPerson(_weakPerson) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

虽然__unsafe_unretained可以解决循环引用,但是最好不要用,因为:

  • __weak:不会产生强引用,指向的对象销毁时,会自动让指针置为nil
  • __unsafe_unretained:不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变,会造成野指针

使用_ _Block修饰符

objective-c 复制代码
int main(int argc, const char * argv[]) {
    @autoreleasepool {
       __block HPPerson *person = [[HPPerson alloc] init];
        person.age = 10;
        person.block = ^{
            NSLog(@"person.age--- %d",person.age);
            //这一句不能少
            person = nil;
        };
        // 必须调用一次
        person.block();
        NSLog(@"--------");
    }
    return 0;
}

使用_ _Block修饰符解决循环引用时,需要注意的点有:

  • 在block对象中需要将_ _block变量置为nil
  • 必须调用block对象

如果不调用block对象时,会造成下面情况的循环引用:

  1. HPPerosn类对象持有Block
  2. Block持有_ _block变量
  3. _ _block变量持有HPPerson对象

因为block会对__block产生强引用

objective-c 复制代码
__block HPPerson *person = [[HPPerson alloc] init];
person.block = ^{
        NSLog(@"person.age--- %d",person.age);
        //这一句不能少
        person = nil;
};

person对象本身就对block是强引用

objective-c 复制代码
@property (copy, nonatomic) HPBlock block;

__block对person产生强引用

objective-c 复制代码
struct __Block_byref_person_0 {
  void *__isa;
__Block_byref_person_0 *__forwarding;
 int __flags;
 int __size;
 void (*__Block_byref_id_object_copy)(void*, void*);
 void (*__Block_byref_id_object_dispose)(void*);
    //`__block`对person产生强引用
 HPPerson *__strong person;
};

当执行完person = nil时候,__block解除对person的引用,进而,全都解除释放了。 但是必须调用person = nil才可以,否则,不能解除循环引用

强弱共舞

  • 当Block捕获self时,应该使用弱引用,这样即使Block持有self的引用,也不会阻止self被释放。
  • 由于弱引用可能变成nil,因此在Block内部使用self之前,需要检查它是否为nil
  • 为了避免在Block内部因selfnil而导致的崩溃,可以在Block的开始处使用强引用
  • 使用完成之后当Block的作用域结束之后即可释放
objective-c 复制代码
#import <UIKit/UIKit.h>
typedef void(^blk_t)(void);
@interface ViewController : UIViewController
@property (nonatomic, strong) blk_t block;
@property (nonatomic, copy) NSString *name;

@end
#import "ViewController.h"

@interface ViewController ()

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    self.name = @"Hello";
    __weak typeof(self) weakSelf = self;
    self.block = ^(){
         dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongWeak.name);

    };

    self.block();
}


@end

此时self持有block,block弱引用self,弱引用会自动变为nil,强持有中断,所以不会引起循环引用。但该方法可能存在中途就释放掉的问题(手动延迟,可能需要调用self.name的时候name已经被释放了)如果self被销毁,那么block则无法获取name。

因此可以改进上面的代码:

objective-c 复制代码
    self.name = @"Hello";

    __weak typeof(self) weakSelf = self;
    self.block = ^(){
        __strong __typeof(weakSelf)strongWeak = weakSelf;

        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", strongWeak.name);
        });
    };
    self.block();

在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self。

weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放。

参数形式解决循环引用

通过给block传参(指针拷贝)

objective-c 复制代码
    // 循环引用
    self.name = @"Hello";
    self.block = ^(ViewController * ctrl){
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
            NSLog(@"%@", ctrl.name);
        });
    };
    self.block(self);

Block循环引用场景

objective-c 复制代码
    // staticSelf_定义:
    static ViewController *staticSelf_;

    - (void)blockWeak_static {
        __weak typeof(self) weakSelf = self;
        staticSelf_ = weakSelf;
    }

weakSelf虽然是弱引用,但是staticSelf_静态变量,并对weakSelf进行了持有,staticSelf_释放不掉,所以weakSelf也释放不掉!导致循环引用

相关推荐
承渊政道8 分钟前
【动态规划算法】(从入门到精通:路径问题)
数据结构·c++·学习·算法·leetcode·macos·动态规划
文件夹__iOS15 小时前
SwiftUI 核心选型:class + ObservableObject VS struct + @State
ios·swiftui·swift
开开心心就好20 小时前
解决打印机共享难题的实用工具
人工智能·vscode·游戏·macos·音视频·语音识别·媒体
Digitally20 小时前
如何顺畅将传音 Infinix 数据传输到 Mac?
macos
SameX20 小时前
独立开发了一款健康记录 App,聊聊几个让我纠结很久的设计决策
ios
报错小能手21 小时前
Swift UI 框架 实战 简易计数器、待办清单 、随机壁纸图库、个人笔记
ui·ios
游戏开发爱好者81 天前
深入理解iOSTime Profiler:提升iOS应用性能的关键工具
android·ios·小程序·https·uni-app·iphone·webview
for_ever_love__2 天前
UI学习:多界面传值的正向传值(属性传值)和反向传值(代理传值)
学习·ui·ios·objective-c
开心就好20252 天前
全面介绍iOS开发工具:Xcode、AppCode、CocoaPods、Fastlane和Git
后端·ios
懋学的前端攻城狮2 天前
数据持久化与缓存策略:在离线与在线间架起桥梁
ios·swift