iOS开发:对Block使用的一次研究总结
Posted wuwuFQ
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS开发:对Block使用的一次研究总结相关的知识,希望对你有一定的参考价值。
在开发中Block是经常使用的,那我们就得知其然,知其所以然。
Block是什么?
Block可以封装一个匿名函数为对象,并捕获上下文所需的数据,并传给目标对象在适当的时候回调。我们使用Block
的目的其实就是回调传值,那我们去看看Block
的底层,再深入了解一下Block
。
Block的底层
Block
的底层实现是结构体,和类的底层实现类似,都有isa
指针,可以把Block当成是一个对象。
Block_layout
是block
结构体的底层结构,其源码如下:
// Block 结构体
struct Block_layout
//指向表明block类型的类
void *isa;//8字节
//用来作标识符的,类似于isa中的位域,按bit位表示一些block的附加信息
volatile int32_t flags; // contains ref count 4字节
//保留信息,可以理解预留位置,用于存储block内部变量信息
int32_t reserved;//4字节
//函数指针,指向具体的block实现的调用地址
BlockInvokeFunction invoke;
//block的附加信息
struct Block_descriptor_1 *descriptor;
// imported variables
;
isa
:指向所属类的指针,也就是Block的类型;flags
:标志变量,用于标记Block的类型、状态等// flags 标识 // Values for Block_layout->flags to describe block objects enum //释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该block可释放 BLOCK_DEALLOCATING = (0x0001), // runtime //存储引用引用计数的 值,是一个可选用参数 BLOCK_REFCOUNT_MASK = (0xfffe), // runtime //低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值 BLOCK_NEEDS_FREE = (1 << 24), // runtime //是否拥有拷贝辅助函数,(a copy helper function)决定block_description_2 BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler //是否拥有block C++析构函数 BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code //标志是否有垃圾回收,OSX BLOCK_IS_GC = (1 << 27), // runtime //标志是否是全局block BLOCK_IS_GLOBAL = (1 << 28), // compiler //与BLOCK_HAS_SIGNATURE相对,判断是否当前block拥有一个签名,用于runtime时动态调用 BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE //是否有签名 BLOCK_HAS_SIGNATURE = (1 << 30), // compiler //使用有拓展,决定block_description_3 BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler ;
reserved
:保留变量invoke
:block执行时调用的函数指针,Block调用实际上就是调用的invoke
descriptor
:block的详细描述,包含 copy/dispose 函数,处理block引用外部变量时使用,有三类Block_descriptor_1
是必选的Block_descriptor_2
和Block_descriptor_3
都是可选的
#define BLOCK_DESCRIPTOR_1 1 struct Block_descriptor_1 uintptr_t reserved;//保留信息 uintptr_t size;//block大小 ; //当block引用对象时,就会新增如下结构: #define BLOCK_DESCRIPTOR_2 1 struct Block_descriptor_2 // requires BLOCK_HAS_COPY_DISPOSE BlockCopyFunction copy;//拷贝函数指针 BlockDisposeFunction dispose; ; // 当block有签名时,增加Block_descriptor_3: #define BLOCK_DESCRIPTOR_3 1 struct Block_descriptor_3 // requires BLOCK_HAS_SIGNATURE const char *signature;//签名 const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT 布局 ;
以上关于descriptor
的可以从其构造函数中体现,其中Block_descriptor_2
和Block_descriptor_3
都是通过Block_descriptor_1
的地址内存平移
得到的
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
return aBlock->descriptor;//默认打印
#endif
// Block 的描述 : copy 和 dispose 函数
static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
if (! (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;//descriptor_1的地址
desc += sizeof(struct Block_descriptor_1);//通过内存平移获取
return (struct Block_descriptor_2 *)desc;
// Block 的描述 : 签名相关
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
if (! (aBlock->flags & BLOCK_HAS_SIGNATURE)) return NULL;
uint8_t *desc = (uint8_t *)aBlock->descriptor;
desc += sizeof(struct Block_descriptor_1);
if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE)
desc += sizeof(struct Block_descriptor_2);
return (struct Block_descriptor_3 *)desc;
- variables:block范围外的变量,如果block没有调用任何外部变量,该变量就不存在
Block类型
block
主要有三种类型
- 全局块(
_NSConcreteGlobalBlock
) - 栈块(
_NSConcreteStackBlock
) - 堆块(
_NSConcreteMallocBlock
)
全局区Block
- (void)checkBlock
//不引用任何变量
void(^block1)(void) = ^
NSLog(@"wuwuFQ:checkBlock");
;
//有形参有返回值
int(^block2)(int, int) = ^(int a, int b)
//a + b
return a + b;
;
//引用静态变量
static int a = 0;
void(^block3)(void) = ^
NSLog(@"wuwuFQ:%d", a);
;
NSLog(@"wuwuFQ:block1%@", block1);
NSLog(@"wuwuFQ:block2%@", block2);
NSLog(@"wuwuFQ:block3%@", block3);
Block
在不引用外部变量或只引用全局变量或者静态变量就是全局Block
堆区Block
- (void)checkBlock
int a = 10;
void(^block)(void) = ^
NSLog(@"wuwuFQ:%d", a);
;
NSLog(@"wuwuFQ:%@", block);
此时的block
访问外部变量,默认是强引用发生了copy
操作,所以是堆区Block
栈区Block
- (void)checkBlock
//引用局部变量a
int a = 0;
// Assigning block literal to a weak variable; object will be released after assignment
void (^__weak block)(void) = ^
NSLog(@"wuwuFQ:%d", a);
;
NSLog(@"wuwuFQ:%@", block);
通过__weak
不进行强持有,block
就还是栈区block
总结
- 未引用外部变量时,block无论作为属性还是参数,都在全局区。
- 引用外部变量时,block声明在栈区,但在block调用、赋值给其它strong修饰的变量时,会发生copy操作,从栈区copy到堆区
Block捕获外部变量
普通变量的捕获
Block捕获外部普通变量(不是__block
等修饰的变量)会自动生成一个属性来保存。
#import <UIKit/UIKit.h>
int main(int argc, char * argv[])
int a = 11;
void (^block)(void) = ^(void)
NSLog(@"%@",@(a));
;
block();
Block编译时自动生成一个__main_block_impl_0
结构体
struct __block_impl
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
;
struct __main_block_impl_0
struct __block_impl impl;// 封装了函数实现的结构体
struct __main_block_desc_0* Desc;// 里面有内存管理函数,Block_size表示block的大小
int *static_k; // 捕获到的局部静态变量
int a; //自动生成a属性来保存捕获的变量a:捕获到的普通局部变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) // 构造函数
impl.isa = &_NSConcreteStackBlock;//isa指针
impl.Flags = flags;//
impl.FuncPtr = fp;//fp函数作为参数传入,保存在FuncPtr指针,这样block()才能调用函数
Desc = desc;
;
__main_block_impl_0
结构体自定义临时变量int a用于捕获的外部变量a,这里面的a实际上已经不是block外面的变量a,这同时也解释了为什么在Block里面无法对a进行修改。
__block变量的捕获
__block int a = 11;
void (^block)(void) = ^(void)
a++;
NSLog(@"%@",@(a));
;
block();
此时的Block的结构体__main_block_impl_0
代码如下:
struct __main_block_impl_0
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding)
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
;
此时的__main_block_impl_0
和捕获普通对象时的结构基本是一样的,唯一不同在于对外部变量的捕获。多了一个结构体__Block_byref_a_0
,这个结构体实际上Block用来捕获__block int a
变量的,其结构体源码如下:
struct __Block_byref_a_0
void *__isa;
__Block_byref_a_0 *__forwarding;// 保存变量a的指针
int __flags;
int __size;
int a;// 保存变量a的值
;
可以看出Block不仅捕获__block int a
变量的值,还捕获了a
的指针,通过__Block_byref_a_0
结构体分别保存在属性int a
和 __Block_byref_a_0 *__forwarding
中,这样Block
内部和外部指向同一个地址,所以可以修改外部变量。
关于Block的理解
看了很多的文章,也扒了Block
底层,那你对Block
是怎么理解的呢?
Block
分为三种类型:全局块(_NSConcreteGlobalBlock
)、栈块(_NSConcreteStackBlock
)、堆块(_NSConcreteMallocBlock
)、Block
不访问外部变量,或访问全局变量、静态变量,Block内存在全局区Block
访问外部变量,Block
声明在栈区,使用时如果发生copy
(__weak
修饰block
不会发生copy
),Block
内存在堆区Block
引用局部变量(int a
;NSString *str
),Block
内存在堆区,进行值拷贝,不能修改变量的值Block
引用__block修饰的局部变量,Block内存在堆区,触发_Block_byref_copy
函数发生指针拷贝,可以修改变量的值Block
引用普通对象(self.view
;self.name
),Block
内存在堆区,触发ARC
的copy
进行指针拷贝,并retain
使得引用计数+1
(这里引申一下:其实Block对局部变量也会持有,但是局部变量的作用域小,会随着Block
释放掉,只要对象没有持有Block
,对象其实也会随着Block
释放掉)
变量或对象在Block内部是
__main_block_impl_0
结构体,调用执行__main_block_func_0
函数,在__main_block_func_0
函数里面会判断引用类型(全局变量、局部变量、对象、__block
修饰、__weak
修饰、Block
),执行不同的copy
原则。
以上是关于iOS开发:对Block使用的一次研究总结的主要内容,如果未能解决你的问题,请参考以下文章