当前位置: 首页 > news >正文

OC-Block

关于OC中的block作为属性时,为什么要要用copy修饰

@property (nonatomic, copy) void (^completionBlock)(void);

很多文章包括AI都会给出类似结论

  • Block 默认分配在栈上,如果没有 copy,当方法退出后,Block 会被销毁。
  • 使用 copy 修饰符确保 Block 存储在堆上,避免栈上 Block 在方法返回后被销毁,确保 Block 在属性中能够正常存活和使用。
  • copy 是为了让 Block 在对象的生命周期内持久化,尤其是当 Block 需要在方法执行后继续使用时。

然而,随着技术的迭代优化,这些知识恐怕已经过时了,我们来看下面的demo

@interface ViewController ()@property (nonatomic, copy) void(^demoBolck)(void);@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.void (^tmpBlock)(void) = ^{NSLog(@"bolck called");};NSLog(@"tmpBlock %@", tmpBlock);_demoBolck = tmpBlock;NSLog(@"demoBolck %@", tmpBlock);_demoBolck();
}@end

 输出结果是这样的

tmpBlock <__NSGlobalBlock__: 0x1067cf490>

demoBolck <__NSGlobalBlock__: 0x1067cf490>

bolck called

我们看到,未引用任何外部变量的block初始化之后就是 “__NSGlobalBlock__”,在全局内存中存储,然后被当前页面的demoBlock属性引用,虽然设置了copy,但是并没有复制(内存没变)。因为他已经在全局内存了,生命周期和APP是一致的,后面随时可以调用,没有复制的必要了。我们甚至使用weak修饰这个属性也可以在后续代码中调用block:

@property (nonatomic, weak) void(^demoBolck)(void);

说到这里我们稍微展开一下,根据GPT的回答,block的存储方式有三种,原文大致如下:

  • 栈内存:用于存储局部变量和不捕获外部变量的 block。栈上的 block 会在离开作用域时自动销毁,名字:__NSStackBlock__。
  • 堆内存:用于存储捕获外部变量的 block,以及所有动态分配的对象,名字:__NSMallocBlock__。
  • 全局内存:用于存储常量和全局变量,且包括不捕获外部变量的 block,名字__NSGlobalBlock__。

有没有发现,关于栈内存和全局内存的说明是有重复的,理解起来好像是“不捕获外部变量的 block”既可以在全局内存存储也可以在栈内存存储。这里触发了他的知识盲区,反复询问也没说明白。所以我们需要继续自己做实验来验证。

既然说如果block引用了“外部变量”就会自动到堆内存,我们试一下

@interface ViewController ()@property (nonatomic, copy) void(^demoBolck)(void);@end@implementation ViewController- (void)viewDidLoad {[super viewDidLoad];// Do any additional setup after loading the view.NSString *pageName = @"ViewController";void (^tmpBlock)(void) = ^{NSLog(@"bolck called in %@", pageName);};NSLog(@"tmpBlock %@", tmpBlock);_demoBolck = tmpBlock;NSLog(@"demoBolck %@", tmpBlock);_demoBolck();
}@end

打印结果

tmpBlock <__NSMallocBlock__: 0x7b0c000fc3c0>

demoBolck <__NSMallocBlock__: 0x7b0c000fc3c0>

bolck called in ViewController

的确,引用了外部变量之后,block直接被存储到了堆内存(名字是__NSMallocBlock__),这种情况下属性的copy依然没什么卵用,copy、strong都没有什么变化,且后续调用都正常。

还做了其他实验,简单说一下

  • 如果block仅引用了一个全局变量,block还会存在全局内存中
  • 如果block引用了当前类的一个属性,block会存在堆内存中,和局部变量一致

众多实验中,唯独没有找到block存储在栈内存中的情况,本人孤陋寡闻,望熟悉的大哥不吝赐教。

总结

1、早期的程序设计中,如果仅仅是局部作用的block,大概率是存在栈内存中的,用完就被释放掉了。然后如果被传递给了某个类来引用,需要开发者自己通过设置属性为“copy”类型实现复制到堆内存中,以便启用引用计数来管理其生命周期,所以大家都用copy来修饰block属性。

2、现阶段的环境下(我用的Xcode Version 16.2 (16C5032a),iOS18.1,模拟器),苹果做了优化。直接根据block是否引用变量,以及所引用的变量的生命周期来控制block的存储位置,和我们给的属性修饰(copy、strong)没关系了。

为什么这么做呢?我们使用block的时候,大多数情况不会在当前作用域创建并直接调用,也就是大概率是将来的某个时候回调,那他就不是临时的,就会和我们的一些变量关联起来,需要一起管理其生命周期。如果还是老逻辑,那大概率都会从栈中复制到堆,折腾一次,优化概率远效率浪费的概率。所以这种设计逐渐被抛弃了。我没找到官方文档,仅个人猜测。

接下来

1、低版本的iOS系统我们大概率都要支持几个的,所以当下并不一定所有的版本都启用了这个机制,具体从什么时候变的我不知道,没找到机器。所以还是推荐大家继续使用copy来修饰block属性

2、 如果大家一起探讨这个问题,就要加上版本这个变量,新版本什么样,老版本什么样

http://www.lryc.cn/news/533342.html

相关文章:

  • 关于知识蒸馏的概念原理以及常见方法
  • C++轻量级桌面GUI库FLTK
  • C++20导出模块及使用
  • PID 算法简介(C语言)
  • Java中的继承及相关概念
  • 语言月赛 202308【小粉兔做麻辣兔头】题解(AC)
  • 云原生后端|实践?
  • GrassWebProxy
  • 6.Python函数:函数定义、函数的类型、函数参数、函数返回值、函数嵌套、局部变量、全局变量、递归函数、匿名函数
  • 青少年编程与数学 02-008 Pyhon语言编程基础 22课题、类的定义和使用
  • CosyVoice /F5-TTS /GPT-SoVITS /Fish-Speech 开源语音克隆与文本转语音(TTS)项目的对比整理
  • MySQL基于binlog和gtid主从搭建方案
  • 5 计算机网络
  • Vim跳转文件及文件行结束符EOL
  • 智能理解 PPT 内容,快速生成讲解视频
  • 【鸿蒙开发】第二十四章 AI - Core Speech Kit(基础语音服务)
  • Java/Kotlin双语革命性ORM框架Jimmer(一)——介绍与简单使用
  • 番外02:前端八股文面试题-CSS篇
  • Redis Copilot:基于Redis为AI打造的副驾工具
  • JavaScript遍历对象的7种方式
  • 如何避免NACK重传风暴
  • 并发工具CountDownLatch、CyclicBarrier、Semaphore
  • 十二. Redis 集群操作配置(超详细配图,配截图详细说明)
  • 网络工程师 (26)TCP/IP体系结构
  • TensorFlow域对抗训练DANN神经网络分析MNIST与Blobs数据集梯度反转层提升目标域适应能力可视化...
  • 保姆级教程--DeepSeek部署
  • 机器学习之心的创作纪念日
  • VeryReport和FastReport两款报表软件深度分析对比
  • libtorch的c++,加载*.pth
  • 去除 RequestTemplate 对象中的指定请求头