Block(三)类型与copy
00 分钟
2022-11-8
2022-11-8
type
status
date
slug
summary
tags
category
icon
password
上篇文章讲述了Block对象如何捕获不同类型的变量,现在开始要追踪Block本身类型的变化,而且Block对象类型和捕获是有关联的。

一、类型

1.1 类型归纳

notion image

1.2 验证

下面是验证代码,代码在这里
注意!!!
我们此处验证是通过class方法获取其类型,也可以通过重写代码,但是重写代码得到的C++代码,并不能正确反映Block的类型,因为编译期和运行期还是有区别,重写并不能百分百对应运行期的逻辑。
根据以上代码,我们得到以下验证结果:
  • globalBlock1/globalBlock2
  • MRC:__NSGlobalBlock__
  • ARC:__NSGlobalBlock__
  • stackBlock1
  • MRC:**__NSStackBlock__**
  • ARC:**__NSMallocBlock__ **
  • stackBlock2
  • MRC:**__NSStackBlock__**
  • ARC:**__NSStackBlock__**
我们看到,针对__NSGlobalBlock__MRCARC下的情形是一致的。
但是__NSStackBlock代码中赋值给stackBlock1在ARC下会自动转换为__NSMallocBlock
我们先梳理一下,什么情况下会将Block对象分配在栈上(__NSGlobalBlock),又是什么时候分配在数据段(__NSStackBlock)。
  • 访问了auto变量就分配在__NSStackBlock
  • 未访问auto变量将分配在__NSGlobalBlock__
  • __NSStackBlock进行copy,则会拷贝到__NSMallocBlock
为什么会这样?
这跟变量的内存位置有关,我们先复习上篇Block(二)本质与变量捕获中对变量捕获的情形:
notion image
从上面的捕获情况,我们看看变量与Block类型存在什么关系?
如果一个变量,可以通过直接访问(全局变量),或者指针访问(static局部变量),那么,通常只有一份拷贝,所有访问,都会访问该变量不变的内存地址,事实上也是这样。
全局变量,存储于全局存储区,static全局变量也存在该区域(参考内存管理(一)引入)。这部分区域,由于该区域不需要在运行时才进行分配的地址,所以,在编译期就能确定,即加入Block捕获的是以上这种类型的变量,Block类型,同样为global性质,即__NSGlobalBlock__类型。
现在理解捕获auto变量,Block类型对应为__NSStackBlock__NSMallocBlock,是不是简单多了。auto变量无法在编译期准确分配内存,在运行时捕获,当然会分配在对应栈或堆上。

二、copy

在开发中,我们捕获局部变量是很常见的事情,如果这种捕获导致,Block对象被分配到栈区,一旦脱离了变量的作用域,也就不能使用Block了,Block的使用就没那么便捷了。
如果分配在栈上的Block对象,进行copy操作,将会将Block对象,从栈上拷贝到堆上。

2.1 自动复制

在ARC环境下,编译器会根据情况自动将栈上的Block复制到堆上,比如以下情况
  1. Block作为函数返回值时;
  1. 将Block赋值给__strong指针时;
  1. Block作为Cocoa API中,且方法名含有usingBlock的方法参数时;
  1. Block作为GCD API的方法参数时;
下面我们就针对以上1、2情况做如下验证,3、4不作验证:

2.1.1 函数返回值

以上代码在MRC下,会报错:
notion image
可以看到,这种情况下Block在栈空间,会报错。
以上,在ARC情况下,再次运行,返回的Block类型,就是__NSMallocBlock__

2.1.2 赋值给__strong指针

以上代码,在ARC环境下,会输出:
  • *Block类型与copy[82825:4315549] _NSStackBlock*
  • *Block类型与copy[82825:4315549] _NSMallocBlock*

2.2 手动复制

通过调用copy方法,手动进行对Block完成从栈到堆的赋值。

2.2.1 属性写法

MRC下block属性的建议写法
ARC下block属性的建议写法

三、 总结

notion image

参考

示例代码

  1. Block类型与copy