1. block 的类型
我们通过下面的代码,来分析一下block
的类型:
static int n = 99;
static NSString *name = @"test";
- (void)testBlockType{
// 0.没有引用外部变量的 block0
void(^block0)(void) = ^{
NSLog(@"block0");
};
NSLog(@"block0 => %@",block0);
// 1.引用静态变量的 block1
void(^block1)(void) = ^{
NSLog(@"%@",name);
NSLog(@"block1");
};
NSLog(@"block1 => %@",block1);
// 2.引用局部变量的 block2
int a = 1;
void(^block2)(void) = ^{
NSLog(@"%d",a);
NSLog(@"block2");
};
NSLog(@"block2 => %@",block2);
// 3.没有引用外部变量且弱引用修饰的 block3
void(^__weak block3)(void) = ^{
NSLog(@"block3");
};
NSLog(@"block3 => %@",block3);
// 4.引用静态变量且弱引用修饰的 block4
void(^__weak block4)(void) = ^{
NSLog(@"%@",name);
NSLog(@"block4");
};
NSLog(@"block4 => %@",block4);
// 5.引用局部变量且弱引用修饰的 block5
void(^__weak block5)(void) = ^{
NSLog(@"%@",a);
NSLog(@"block5");
};
NSLog(@"block5 => %@",block5);
}
输出结果:
2021-01-24 21:47:57.223205+0800 block[10936:934202] block0 => <__NSGlobalBlock__: 0x107102048>
2021-01-24 21:47:57.223361+0800 block[10936:934202] block1 => <__NSGlobalBlock__: 0x107102068>
2021-01-24 21:47:57.223444+0800 block[10936:934202] block2 => <__NSMallocBlock__: 0x600000d25200>
2021-01-24 21:47:57.223518+0800 block[10936:934202] block3 => <__NSGlobalBlock__: 0x1071020a8>
2021-01-24 21:47:57.223605+0800 block[10936:934202] block4 => <__NSGlobalBlock__: 0x1071020c8>
2021-01-24 21:47:57.223719+0800 block[10936:934202] block5 => <__NSStackBlock__: 0x7ffee8afe0f8>
可以看到block
有 3 中类型:NSGlobalBlock
、NSMallocBlock
、NSStackBlock
。
- 没有引用任何外部变量,或者引用了
静态变量
的block
都为NSGlobalBlock
; - 引用了
局部变量
且没有__weak
修饰的block
为NSMallocBlock(堆 block)
; - 引用了
局部变量
且__weak
修饰的block
,为NSStackBlock(栈 block)
。
2. block 的循环引用
我们来看下面这两段代码中是否会产生循环引用:
//代码一
NSString *name = @"CJL";
self.block = ^(void){
NSLog(@"%@",self.name);
};
self.block();
//代码二
UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
};
结果:
代码一
发生了循环引用
,因为在block
内部使用了外部变量 name
,导致 block 持有 self
,而 block
又是 self
的属性,所以导致了 self 和 block 相互持有
。
代码二中没有循环引用,虽然也使用了外部变量,但是self 中并没有持有 animation 的 block
,没有相互引用。
解决循环引用常见的方式有以下几种:
weak-strong
-
__block
修饰对象(需要注意的是在block 内部需要置空对象
,且block 必须调用
) - 传递对象
self
作为block
的参数,提供给block
内部使用 - 使用
NSProxy
2.1 weak-strong
__weak 修饰 self
即可,这也是我们日常开发中使用最多的:
typedef void(^CJLBlock)(void);
@property(nonatomic, copy) CJLBlock cjlBlock;
__weak typeof(self) weakSelf = self;
self.cjlBlock = ^(void){
NSLog(@"%@",weakSelf.name);
}
2.2 __block修饰变量
通过__block
修饰对象,主要是因为__block
修饰的对象是可以改变的:
__block ViewController *vc = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;//手动释放
});
};
// 这里必须调用 block,否则 block 不执行,其内部持有的 vc 不会被释放
self.block();
需要注意的是这里的block必须调用
,如果不调用block
,vc
就不会置空,那么依旧是循环引用
,self
和block
都不会被释放。
2.3 对象 self 作为参数
主要是将对象self
作为参数,提供给block
内部使用,不会有引用计数问题:
typedef void(^TestBlock)(ViewController *);
@property(nonatomic, copy) TestBlock block;
self. block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
2.4 使用NSProxy虚拟类
具体使用可以看这篇文章:NSTimer循环引用的解决办法。
3. block 底层分析
定义一个block.c
文件:
#include <stdio.h>
int main(){
void(^testBlock)(void) = ^{
printf("123456");
};
testBlock();
return 0;
}
通过下面的命令,将 block.c编译成 block.cpp文件:
xcrun -sdk iphonesimulator clang -arch x86_64 -rewrite-objc block.c
block在底层被编译成了下面的形式:
// block代码块的结构体类型
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
// 构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
// block 的内部实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("123456");
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
return 0;
}
// ********main()函数的简化*********
void(*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA)); //构造函数
testBlock->FuncPtr(testBlock); // 调用 block 执行
首先是testBlock
赋值,其实是调用了__main_block_impl_0
结构体同名的构造函数,内部对impl
和 Desc
进行了赋值操作,重点是impl.FuncPtr = fp
,fp
就是外界传入的__main_block_func_0
。
然后是testBlock
的调用,将 testBlock
强制转换为__block_impl*
,并调用了FuncPtr()
,也就是__main_block_func_0 ()
函数,也就执行了整个block
。
所以block
,在编译时被包装成了一个__main_block_impl_0
的结构体,并执行了同名的构造函数,将 block
块转换成__main_block_func_0
函数作为参数
传入,同时还传入了一些描述信息。执行 block
时,转为调用FuncPtr()
函数,即__main_block_func_0
函数。
4. block变量捕获
我们定义一个int
变量a
,并在block
中调用:
int main(){
int a = 10;
void(^testBlock)(void) = ^{
printf("%d",a);
};
testBlock();
return 0;
}
编译成cpp
文件,查看源码:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("%d",a);
}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main(){
int a = 10;
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
return 0;
}
从上面可以看到__main_block_impl_0
结构体中多了一个参数a
,并且构造函数中也多了一个参数a
:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
说明程序在编译时,自动将外部变量a
捕获并添加到了__main_block_impl_0
结构体内,并且将参数a的值
赋值给结构体中的变量 a
。
我们继续看下__main_block_func_0
函数:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("%d",a);
}
可以看到这里输出的a
,并不是 block
外界的那个a
,而是来自于__cself->a
,也就是__main_block_impl_0
结构体中的int a
成员变量。这样也就解释了为什么没有__block
修饰的变量无法在block
内部修改,因为它们完全不是同一个东西。
我们现在用__block 修饰 int a
:
int main(){
__block int a = 10;
void(^testBlock)(void) = ^{
a++;
printf("%d",a);
};
testBlock();
return 0;
}
编译后查看源码:
// 变量 a 被包裹成一个结构体
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
printf("%d",(a->__forwarding->a));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main(){
// 包装 int a变量为__Block_byref_a_0结构体对象
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
// 这里将包装后的 __Block_byref_a_0 结构体传入__main_block_impl_0构造函数
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
// 调用
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
return 0;
}
我们发现变量int a
被包装成了一个__Block_byref_a_0
结构体对象:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
并且这个结构体内部保存了变量 a
的指针地址和值
,然后将__Block_byref_a_0
对象传入__main_block_impl_0()
构造函数中:
void(*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
之后在__main_block_func_0
函数中,通过变量 a
的指针地址修改a
的值:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
(a->__forwarding->a)++;
printf("%d",(a->__forwarding->a));
}
这里的a++
操作,是对a->__forwarding->a
的操作,即外界的那个变量a
。
总结:
- 使用
__block
修饰外部变量,外部变量会被包装成一个__Block_byref_a_0
结构体对象; - 结构体用来保存原始变量的
指针和值
; - 将变量生成的结构体对象的
指针地址传给 block
,然后在block
内部就可以对外界变量进行操作了。
网友评论