Block的使用
-
return_type
表示返回
的对象/关键字
等(可以是void,并省略) -
blockName
表示block的名称
-
var_type
表示参数
的类型
(可以是void,并省略) -
varName
表示参数名称
return_type表示返回的对象/关键字等(可以是void,并省略)
blockName表示block的名称
var_type表示参数的类型(可以是void,并省略)
Block声明及定义语法
- varName表示参数名称
return_type (^blockName)(var_type) = ^return_type (var_type varName) { // ... };
blockName(var);
- 当返回类型为void
void (^blockName)(var_type) = ^void (var_type varName) { // ... };
blockName(var);
可简写为:
void (^blockName)(var_type) = ^(var_type varName) { // ... };
blockName(var);
- 当参数类型为void
return_type (^blockName)(void) = ^return_type (void) { // ... };
blockName();
简写为:
return_type (^blockName)(void) = ^return_type { // ... };
blockName();
- 当返回类型和参数类型都为void
void (^blockName)(void) = ^void (void) { // ... };
blockName();
可简写为:
void (^blockName)(void) = ^{ // ... };
blockName();
- 匿名Block
Block实现时,等号右边就是一个匿名Block,它没有blockName,称之为匿名Block:
^return_type (var_type varName)
{ //... };
typedef简化Block的声明
- 声明
typedef return_type (^BlockTypeName)(var_type);
- 作为属性
//声明 typedef void(^ClickBlock)(NSInteger index); //block属性 @property (nonatomic, copy) clickBlock ClickBlock;
- 作方法参数
声明 typedef void (^handleBlock)(); //block作参数
- (void)requestForRefuseOrAccept:(MessageBtnType)msgBtnType messageModel:(MessageModel *)msgModel handle:(handleBlock)handle{
...
block循环引用的解决方案
环境准备
.m文件
typedef void(^QBlock)( void);
@interface ViewController ()
@property (nonatomic, copy) QBlock block;
@property (nonatomic, copy) NSString *name;
@property (nonatomic, strong) UITableView *tableView;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"lg_cooci";
self.block = ^(void){
NSLog(@"%@",self.name);
};
self.block();
// block
}
- (void)dealloc{
NSLog(@"dealloc 来了");
}
@end
1. weak-strong-dance (强弱共舞)
weak-strong
是中介者
模式
-
添加
__weak typeof(self) weakSelf = self;
__weak
是将对象存储到弱引用表
中
__weak typeof(self) weakSelf = self;
self.block = ^(void){
NSLog(@"%@",weakSelf.name);
};
self.block(self);
此时还是有问题:如果在block内的执行
延时
处理呢?
__weak typeof(self) weakSelf = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakSelf.name);
});
};
self.block();
-
通过代码执行就会发现
weakSelf
为nil
!!!- 执行
延时
操作期间,如果执行了析构函数
,当前的self
释放了,weakSelf
也释放
了,所以变成了nil
- 执行
-
在block内添加
__strong __typeof(weakSelf)strongSelf = weakSelf;
延长
weakSelf
的生命周期
__weak typeof(self) weakSelf = self;
self.block = ^(void){
__strong __typeof(weakSelf)strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
2. __block
- 添加
__block ViewController *vc = self;
typedef void(^KCBlock)(ViewController *);
__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;
});
};
self.block();
3. 当做参数传入
- 将当前
self
当做参数
传入
self.block = ^(ViewController *vc){
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);
4.proxy
TODO:后期添加
Block与内存管理
根据Block在内存中的位置分为三
种类型:
-
NSGlobalBlock
是位于全局区
的block,它是设置在程序的数据区域
(.data区)中。 -
NSStackBlock
是位于栈区
,超出变量作用域,栈上的Block
以及__block变量
都被销毁。 -
NSMallocBlock
是位于堆区
,在变量作用域结束时
不受影响。
1. 位于全局区:GlobalBlock
- 定义全局变量的地方有block语法时
void(^block)(void) = ^ { NSLog(@"Global Block");}; int main() {
}
- block语法的表达式中没有使用应截获的自动变量时
int(^block)(int count) = ^(int count) { return count;
};
block(2);
2、 位于栈内存:StackBlock
- block语法的表达式中使用截获的自动变量时
NSInteger i = 10;
block = ^{ NSLog(@"%ld", i);
};
block;
3. 位于堆内存:MallocBlock
堆
中的block无法
直接创建,其需要由_NSConcreteStackBlock
类型的block拷贝
而来(也就是说block需要执行copy
之后才能存放
到堆中)。由于block的拷贝最终都会调用_Block_copy_internal
函数。
void(^block)(void); int main(int argc, const char * argv[]) { @autoreleasepool {
__block NSInteger i = 10;
block = [^{
++i;
} copy];
++i;
block(); NSLog(@"%ld", i);
} return 0;
}
block分析
1. block的本质
- 新建一个
.c
文件,声明
一个block
,执行clang
,查看.cpp
文件
#include "stdio.h"
int main(){
void(^block)(void) = ^{
printf("qqq - %d",a);
};
// block();
return 0;
}
.c
文件与.cpp
文件的对比
-
查看
__main_block_impl_0
的类型,结构体
: 明显的发现block
是一个结构体
匿名函数
:__main_block_impl_0
还是一个构造函数
-
函数的
调用
执行
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
可简化为:
- 其中
block
作为隐藏参数
block->FuncPtr(block);
2. block自动捕获外界变量
- 添加一个
a
变量
#include "stdio.h"
int main(){
int a = 11;
void(^block)(void) = ^{
printf("qqq - %d",a);
};
block();
return 0;
}
-
clang
一下- 在声明
block
时,传入
参数a
- 在编译时,
__main_block_impl_0
结构体
,新增
一个变量a - 在方法
__main_block_func_0
执行函数中,新建a
变量,并对传入的进行值拷贝
。
- 在声明
-
如果在
__main_block_func_0
的执行方法中,对a
进行操作(+1)
,就会发生代码歧义
,导致报错
++++++++++++++++++华丽丽的的分割线+++++++++++++++++++++
- 在
a
变量上添加__block
#include "stdio.h"
int main(){
__block int a = 11;
void(^block)(void) = ^{
a++;
printf("qqq - %d",a);
};
block();
return 0;
}
-
clang一下
- 在
block
中新建
一个__Block_byref_a_0
类型的a变量
- 新建
block
时,传入a
的地址
- 在
函数调用
时,新建
一个__Block_byref_a_0
类型的a变量
,并将block
中a
的指针
赋值
给新建
的__Block_byref_a_0
类型的a变量,如果对a进行了操作
,block
也会发生改变
- 在
3. block底层探索
3.1 找到源码
-
新建一个工程,写一个
全局block
,并且在block处打一个断点
-
打开汇编调试
-
运行查看,就会发现
objc_retainBlock
和objc_storeStrong
-
添加符号断点
objc_retainBlock
,并进入查看,发现执行的是_Block_copy
- 向下执行,就会发现
_Block_copy
在libsystem_blocks.dylib
,下载block源码
3.2 MallocBlock(堆区block)第一次拷贝
-
读取当前寄存器,查看一下当前是在什么位置,在
全局区
位置- 验证了上述block的
内存区域
位置
- 验证了上述block的
-
修改代码,在外部
添加
一个变量
,并在block
中使用
,查看寄存器- 发现竟然在
栈
区,我们知道此时block应该位于堆
区
- 发现竟然在
-
继续向下执行
_Block_copy
,在最后一行retq
打一个断点,再读取一下寄存器- 发现竟然跑到
堆
区中了
- 发现竟然跑到
小结:
引用外部变量的block第一次
拷贝流程:
stackBlock
栈区block =>_block_copy
=>mallocBlock
堆区block
3.3 源码分析
1. block的结构
-
在cpp文件中,我们发现block来自
from Block_private.h
文件中
-
在
Block_private.h
文件中,我们发现方法的传入参数都是Block_layout
类型,猜测block的底层可能是Block_layout
结构体。查看Block_layout
- 第一个参数:
isa
- 第二个参数:
flags
– 标识符 - 第三个参数:
reserved
– 感觉没意义 - 第四个参数:
invoke
– 方法 - 第五个参数:
descriptor
– block的descriptor
- 第一个参数:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
-
flags 标识符,注意三个标识符
- 是否有析构 : BLOCK_DEALLOCATING = (0x0001)
- 是否进行复制:BLOCK_HAS_COPY_DISPOSE = (1 << 25)
- 是否有签名:BLOCK_HAS_SIGNATURE = (1 << 30)
-
descriptor
–Block_descriptor_1
、Block_descriptor_2
和Block_descriptor_3
,其中Block_descriptor_2
和Block_descriptor_3
是可选的 -
Block_descriptor_1
Block_descriptor_1
的属性分别为:reserved
接收者和size
大小
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
Block_descriptor_1
方法实现
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
-
Block_descriptor_2
Block_descriptor_2
只有当flags
是BLOCK_HAS_COPY_DISPOSE
时,才会存在- 属性分别为:
copy
、dispose
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
Block_descriptor_2
方法实现
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;
desc += sizeof(struct Block_descriptor_1);
return (struct Block_descriptor_2 *)desc;
}
-
Block_descriptor_3
Block_descriptor_3
只有当flags
是BLOCK_HAS_SIGNATURE
时,才会存在- 属性分别为:
signature
方法签名、layout
#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
};
Block_descriptor_3
方法实现
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;
}
2. _block_copy 从栈区拷贝到堆区
-
查看
_Block_copy
源码-
判断
flags
是否是BLOCK_NEEDS_FREE(释放)
- 是
BLOCK_NEEDS_FREE
, 返回 栈区的aBlock
- 是
-
判断
flags
是否是全局block
- 是
全局block
,返回 栈区的aBlock
- 是
-
以上都不是,那就会
copy
到堆
区-
在
堆
区开辟
一个空间result
-
将
栈
区的aBlock
拷贝
到堆
区的result
block
的isa
指向_NSConcreteMallocBlock
函数指针
的赋值flags
的赋值
-
-
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
//判断flags是否是BLOCK_NEEDS_FREE(释放)
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
//判断flags是否是全局block
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock; // 不需要
}
else { // 栈
// Its a stack block. Make a copy.
//在堆区开辟一个空间
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
//将栈区的aBlock拷贝到堆区的result
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
#if __has_feature(ptrauth_calls)
// Resign the invoke pointer as it uses address authentication.
//函数指针的赋值
result->invoke = aBlock->invoke;
#endif
// reset refcount
//flags的赋值
result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING); // XXX not needed
result->flags |= BLOCK_NEEDS_FREE | 2; // logical refcount 1
_Block_call_copy_helper(result, aBlock);
// Set isa last so memory analysis tools see a fully-initialized object.
result->isa = _NSConcreteMallocBlock;
return result;
}
}
3. block对外部变量的捕获
-
查看
Block_byref
的源码:Block_byref、Block_byref_2和Block_byref_3。其中Block_byref_2和Block_byref_3是可选- 只有是BLOCK_BYREF_HAS_COPY_DISPOSE时,才会向
Block_byref
添加Block_byref_2
- 只有是
BLOCK_BYREF_LAYOUT_EXTENDED
时,才会向Block_byref
添加Block_byref_3
- 只有是BLOCK_BYREF_HAS_COPY_DISPOSE时,才会向
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; // 结构体 __block 对象
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
- 添加
block
捕获外部由__block
修饰的对象类型
的变量,
__block NSString *name = [NSString stringWithFormat:@"qqqwww"];
void (^block1)(void) = ^{
name = @"www";
NSLog(@"qqq_Block - %@",name);
};
block1();
-
clang一下查看编译后的源码
-
使用
__block
修饰的变量,在生成byref
对象时,会添加
两个函数指针
属性:__Block_byref_id_object_copy
和__Block_byref_id_object_dispose
-
其中
__Block_byref_id_object_copy
函数中执行的是_Block_object_assign
,传入的是对象类型
的参数。- 由于
__Block_byref_name_0
存在__isa
、__forwarding
、__flags
、__size
、(*__Block_byref_id_object_copy)(void*, void*)
、(*__Block_byref_id_object_dispose)(void*)
和name
,最后
才是字符串类型的name
,所以需要首地址
平移40
才可以获取到name
- 由于
-
-
在上面cpp文件中,可以看到
__main_block_copy_0
和__main_block_dispose_0
函数中调用的都是_Block_object_assign
-
在源码中查看
_Block_object_assign
void _Block_object_assign(void *destArg, const void *object, const int flags) {
const void **dest = (const void **)destArg;
switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
//对象类型
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
// objc 指针地址 weakSelf (self)
// arc
_Block_retain_object(object);
// 持有
*dest = object;
break;
//Block对象类型
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 被一个 block 捕获
*dest = _Block_copy(object);
break;
//__weak 和 __block
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
*dest = object;
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK | BLOCK_FIELD_IS_WEAK:
*dest = object;
break;
default:
break;
}
}
- 其中 枚举为:
enum {
// see function implementation for a more complete description of these fields and combinations
//对象类型
BLOCK_FIELD_IS_OBJECT = 3, // id, NSObject, __attribute__((NSObject)), block, ...
//block类型
BLOCK_FIELD_IS_BLOCK = 7, // a block variable
//__block修饰
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
//__weak修饰
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.
};
BLOCK_FIELD_IS_OBJECT
对象类型
//对象类型
case BLOCK_FIELD_IS_OBJECT:
/*******
id object = ...;
[^{ object; } copy];
********/
// objc 指针地址 weakSelf (self)
// arc
_Block_retain_object(object);
// 持有
*dest = object;
break;
-
执行
_Block_retain_object
,查看源码_Block_retain_object
函数中什么都没有操作
static void (*_Block_retain_object)(const void *ptr) = _Block_retain_object_default;
static void _Block_retain_object_default(const void *ptr __unused) { }
计数加一
持有
状态:新建一个指针指向变量地址
*dest = object;
BLOCK_FIELD_IS_BLOCK
block类型的对象
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
// block 被一个 block 捕获
*dest = _Block_copy(object);
break;
- 继续执行
_Block_copy
BLOCK_FIELD_IS_BYREF
、BLOCK_FIELD_IS_WEAK
__block和__weak修饰的对象
//__weak 和 __block
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
*dest = _Block_byref_copy(object);
break;
-
查看
_Block_byref_copy
方法-
在
cpp
文件中,可以看出,在编译时,将外接捕获
的对象
变成了Block_byref
类型的结构体
。所以传入的对象arg
是Block_byref
结构体类型的对象 -
开辟一个
内存空间copy
,将传入
的arg
对象copy一份
,且arg->forwarding
和copy->forwarding
都指向同一个内存空间copy
。这是第二次拷贝!!!
-
由于在
编译
时,生成了Block_byref_2
对象,由执行了一遍_Block_object_assign
,传入的是一个对象类型
的参数,又进行了一次copy
。这是第三次拷贝!!!
-
static struct Block_byref *_Block_byref_copy(const void *arg) {
// Block_byref 结构体
struct Block_byref *src = (struct Block_byref *)arg;
if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
// src points to stack
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
copy->isa = NULL;
// byref value 4 is logical refcount of 2: one for caller, one for stack
copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
// 问题 - block 内部 持有的 Block_byref 锁持有的对象 是不是同一个
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
copy->size = src->size;
if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
// Trust copy helper to copy everything of interest
// If more than one field shows up in a byref block this is wrong XXX
struct Block_byref_2 *src2 = (struct Block_byref_2 *)(src+1);
struct Block_byref_2 *copy2 = (struct Block_byref_2 *)(copy+1);
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;
if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {
struct Block_byref_3 *src3 = (struct Block_byref_3 *)(src2+1);
struct Block_byref_3 *copy3 = (struct Block_byref_3*)(copy2+1);
copy3->layout = src3->layout;
}
(*src2->byref_keep)(copy, src);
}
else {
// Bitwise copy.
// This copy includes Block_byref_3, if any.
memmove(copy+1, src+1, src->size - sizeof(*src));
}
}
// already copied to heap
else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}
return src->forwarding;
}
小结:
block
是一个结构体
block
在捕获外界变量时:执行的copy次数-
在
_block_copy
中执行了一次copy
,从栈区
copy到堆区
-
对象类型
一共执行两次copy
:BLOCK_FIELD_IS_OBJECT
: 值拷贝 -
block类型对象
:BLOCK_FIELD_IS_BLOCK
,继续执行_Block_copy
-
__weak、__block修饰的对象
,一共执行三次copy
:BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK
_Block_byref_copy
执行一次- 在
keep
中调用_Block_object_assign
,执行了一次值拷贝
-