【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也释放不掉!导致循环引用

相关推荐
前端不太难15 小时前
Flutter / RN / iOS,在长期维护下的性能差异本质
flutter·ios
程序员雄杰16 小时前
腾讯云轻量应用服务器mac中ssh免密登录到服务器
macos·ssh·腾讯云
YongPagani1 天前
Mac安装Homebrew
macos
Byron Loong1 天前
【系统】Mac系统和Linux 指令对比
linux·macos·策略模式
软件小滔2 天前
拖拽出来的专业感
经验分享·macos·mac·应用推荐
搜狐技术产品小编20232 天前
精通 UITableViewDiffableDataSource——从入门到重构的现代 iOS 列表开发指南
ios·重构
coooliang2 天前
Macos下载元神 ipa文件
macos
Benny的老巢2 天前
【n8n工作流入门02】macOS安装n8n保姆级教程:Homebrew与npm两种方式详解
macos·npm·node.js·n8n·n8n工作流·homwbrew·n8n安装
tangweiguo030519872 天前
SwiftUI 状态管理完全指南:从 @State 到 @EnvironmentObject
ios
程序员agions2 天前
Unity 游戏开发邪修秘籍:从入门到被策划追杀的艺术
unity·cocoa·lucene