一、block的使用
1、作为变量
格式:
返回值类型 (^block的名称)(参数类型) = ^返回值类型(参数) {...};
例子:
//有参数和有返回值的block
int (^blockName11)(NSString *name) = ^int(NSString *name){
NSLog(@"%@",name);
return 10;
};
blockName11(@"zhao");
//没参数和没返回值的block
void (^blockName22)() = ^(){
NSLog(@"123");
};
blockName22();
2、作为属性
格式:
@property (nonatomic, copy) 返回值类型 (^block的名称)(参数类型);
例子:
//有返回值有参数的属性
@property (nonatomic, copy) int (^blockName)(NSString *name);
//没返回值没参数的属性
@property (nonatomic, copy) void (^blockName1)();
3、作为方法声明的参数
格式:
-(void)方法名:(返回值类型 (^)(参数类型))block的名称;
例子:
-(void)testBlock:(int(^)(NSString *name))complateBlock;
4、作为方法实现的参数
格式:
[对象/类 方法名:^返回值类型 (参数) {...}];
例子:
[self testBlock:^int(NSString *name) {
}];
5、声明替换 -- typeDef
格式:
typedef 返回值类型 (^类型名称)(参数类型);
例子:
typedef int(^testBlock)(NSString *str);
6、block的循环引用
__weak typeof(self) weakSelf = self; // weakSelf(弱引用表) -> self
// strongSelf(nil) -> weakSelf -> self(引用计数不处理)--nil -> block -> weakSelf
self.block = ^{
// 持有 不能是一个永久持有 - 临时的
// strong-weak-dance
__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);
});
};
self.block();
- 如果在block中使用self,那么block会持有该self,假如同时self也持有block,就造成了循环引用
- 我们可以在block外面创建一个弱引用weakSelf来替代self,这样block保存的weakSelf不会对self的引用计数造成影响,这样就不会造成循环引用了
- block内部使用weakself可能是延迟使用,有可能当bock使用weakself时候self已经被释放了,这个时候weakself也变成nil了
- 为了保证block内部使用weakself正常,这个时候就需要在block内部创建一个strongself指向weakself,这个时候strongself是block内部的临时变量,block会释放他,并且strongself和block的生命周期是一样的,所以可以保证block内部使用self正常
还有一种情况是self昨晚参数传到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);
当self昨晚参数传进去时候,block自己会有个指针指向self,所以不会造成循环引用
二、block的分类
block有6种,这个我们可以在libclosure-73源码中可以看到
- a、全局block
我们可以做一下测试:
void (^block1)(void) = ^{
NSLog(@"GlobalBlock--");
};
block1();
NSLog(@"GlobalBlock:%@",block1);
2020-03-08 20:34:13.013553+0800 BYTestBlock[33883:588331] GlobalBlock:<__NSGlobalBlock__: 0x10bdfd248>
打印出来的是全局block
- b、堆block
int a = 0;
void (^block)(void) = ^{
NSLog(@"GlobalBlock--%d",a);
};
NSLog(@"GlobalBlock:%@",block);
2020-03-08 20:34:13.013699+0800 BYTestBlock[33883:588331] GlobalBlock:<__NSMallocBlock__: 0x600001bcb510>
打印出来的是堆block
- c、栈block
NSLog(@"GlobalBlock11:%@",^{ NSLog(@"GlobalBlock--%d",a);});
2020-03-08 20:34:13.013840+0800 BYTestBlock[33883:588331] GlobalBlock11:<__NSStackBlock__: 0x7ffee3e13478>
打印出来的是栈block
上面三种是我们常用的三种,还有另外三种一般是系统使用的,暂时不做讨论
三、clang分析
我们首先看一下编译器会把block的OC代码转成什么样的c++代码
- 1、在OC源文件block.m写好代码。
- 2、打开终端,cd到block.m所在文件夹。
- 3、输入clang -rewrite-objc block.m,就会在当前文件夹内自动生成对应的block.cpp文件。
oc代码:
int main(int argc, char * argv[]) {
@autoreleasepool {
void (^blockName22)() = ^(){
printf("Block\n");
};
blockName22();
}
}
c++代码
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
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 argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void (*blockName22)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
a、block为什么能用%@打印
首先我们看看blockName22这句代码的转换:
void (*blockName22)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
去掉多余的强转
void (*blockName22)() = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
我们发现blockName22本身是__main_block_impl_0,有点像构造函数,我们再看看__main_block_impl_0的实现
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;
}
};
我们发现__main_block_impl_0其实是一个结构体,所以block本质上是一个OC对象,所以可以用 %@ 来打印
b、block是如何调用的
我们再看一下__main_block_impl_0里面的构造函数__main_block_impl_0,有三个参数:
- 1、fp:我们发现传的是__main_block_func_0,而她__main_block_func_0其实就是一个函数,传进来后会把他保存在impl.FuncPtr上面,这种写法叫做函数式编程,然后我们看到下面一句代码
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
去掉强转符号:
(__block_impl *)block->FuncPtr
所以block()其实就是调用一个函数的过程,这就是block的调用过程
c、block捕获变量
oc代码:
NSInteger a = 9;
__block NSInteger b = 9;
NSMutableArray *array = [NSMutableArray array];
[array addObject:[NSObject new]];
void (^block)(void) = ^{
// index++;//报错
b++;
[array addObject:@"1"];
NSLog(@"%ld %ld %ld",a,b,array.count);
};
block();
a++;
block();
NSLog(@"%ld %ld %ld",a,b,array.count);
2020-03-06 14:58:07.102646+0800 BYTestBlock[96733:7875542] 9 10 2
2020-03-06 14:58:07.103382+0800 BYTestBlock[96733:7875542] 9 11 3
2020-03-06 14:58:07.103481+0800 BYTestBlock[96733:7875542] 10 11 3
然后我们把这段代码转换成C++代码
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
NSInteger b;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
NSInteger a;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__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_b_0 *b = __cself->b; // bound by ref
NSMutableArray *array = __cself->array; // bound by copy
NSInteger a = __cself->a; // bound by copy
(b->__forwarding->b)++;
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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 argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSInteger a = 9;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 9};
NSMutableArray *array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
a++;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_2,a,(b.__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我们在c++代码中发现__block NSInteger b = 9;代码被转换成了__Block_byref_b_0结构体
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 9};
而__Block_byref_b_0结构是:
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
NSInteger b;
};
然后我们看一下通过构造函数创建的__Block_byref_b_0传参
- 1、__isa:传的是(void*)0,其实就是空
- 2、__forwarding:传的是外面b变量的指针,也就是self自己
- 3、__flags:传的是0
- 4、__size:传的是__Block_byref_b_0的大小
- 5、b:传的是变量b的值
我们再看一下__main_block_impl_0的结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
NSInteger a;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们发现传进来的参数都直接赋给impl的属性了
再看一下构造函数的调用:
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));
去掉类型强转:
void (*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));
总共变成了6个参数
- 1、__main_block_func_0
- 2、&__main_block_desc_0_DATA
- 3、array
- 4、a
- 5、(__Block_byref_b_0 *)&b
- 6、570425344
再看一下__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
NSMutableArray *array = __cself->array; // bound by copy
NSInteger a = __cself->a; // bound by copy
(b->__forwarding->b)++;
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
- 我们发现调用__main_block_func_0其实就是block对象__main_block_impl_0,在实现函数里面会将block对象的参数属性都进行指针copy一份,
- 如果外面变量没加__block,那copy过来的参数跟外面是一样的,,假如直接修改该参数就会造成冲突,不知道是修改block内部的变量还是外面的变量,所以会报错,
- 而加了__block系统其实会自动在创建一个结构体,将新创建的指针传进来,这样就不会和外面的发生冲突
- 当修改外面变量时候调用的是 (b->__forwarding->b)++;而新创建的结构体里面有外卖变量的指针,在block内部所以修改变量时候其实修改的就是外面的变量
四、block的内存变化
原理
首先我们先了解一下数据结构,我们在转的.cpp文件中搜索一下Block_private
我们发现很多关于block的函数都提示在block_private.h里面,我们打开源码libclosure-73,然后在block_private.h,我们先找到基础block空间Block_layout,这个结构跟我们在cpp里面看的block结构是一样的,类似于block 的父类
struct Block_layout {
void *isa;//
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
- 1、isa:指向父类的指针
- 2、flags:记录状态的
- 3、reserved:
- 4、invke:函数
- 5、descriptor:
关于flags我们可以看到上面有个描述:
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime =(0x0001)释放标记,一般常用
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 存储引用计数的值
BLOCK_NEEDS_FREE = (1 << 24), // runtime
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code
BLOCK_IS_GC = (1 << 27), // runtime
BLOCK_IS_GLOBAL = (1 << 28), // compiler
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler
};
关于Flags标示:
- 第1位:释放标记,一般常用BLOCK_NEEDS_FREE做位与操作,一同传入Flags,告知该block可释放
- 第16位:存储引用计数的值,是一个可选用参数
- 第24位:第16位是否有效的标志,程序根据它来决定是否增加或是减少引用计数位的值
- 第25位:是否拥有拷贝辅助函数
- 第26位:是否拥有block析构函数
- 第27位:标志是否有垃圾回收
- 第28位:标志是否是全局block
- 第30位:与BLOCK_USE_STRET相对,判断是否当前block拥有一个签名。用于runtime时动态调用
我们在Block_layout里面看到一个Block_descriptor_1,并且在上面还看到了:
#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
// 可选
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
#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_2和Block_descriptor_3,这是因为这两个是可选的,我们来到runtime.cpp文件搜索一下Block_descriptor_2找到下面:
#if 0
static struct Block_descriptor_1 * _Block_descriptor_1(struct Block_layout *aBlock)
{
return aBlock->descriptor;
}
#endif
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;
}
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;
}
-
我们发现在获取Block_descriptor_2时候,是要先判断(aBlock->flags & BLOCK_HAS_COPY_DISPOSE),也就是要拿到block的flags和BLOCK_HAS_COPY_DISPOSE与一下,看看是否拥有copy辅助函数存在,如果不存在就返回空
-
同样我们看到Block_descriptor_3也是需要判断一下(aBlock->flags & BLOCK_HAS_SIGNATURE),来判断这个block是否有签名,因为Block_descriptor_3里面有个属性是signature
-
那怎么获取Block_descriptor_2和Block_descriptor_3呢,我们看到获取_Block_descriptor_2时候是在_Block_descriptor_1的指针上加上_Block_descriptor_1的大小,
-
而_Block_descriptor_3是在_Block_descriptor_1的指针上加上_Block_descriptor_1和_Block_descriptor_2的大小,这说明三者是连续的,这是内存偏移原理
测试已经block的copy过程:
下面我们用实例来测试下,我们先写一段代码,在block哪里加上断点
打开汇编调试运行:
,因为调用该函数是block调用的,调用者是x0,所以x0就是我们定义的block,我们打印下x0
我们发现此时block是__NSGlobalBlock__ ,我们再点击in键一步一步往下走
再往下点击
我们进入到了_Block_copy函数里面,因为调用该函数是block调用的,调用者是x0,所以x0就是我们定义IDEblock,我们打印下x0
发现该block还是__NSGlobalBlock__,说明block性质没有发生变化
我们把上面代码做一下更改:
然后按照上面顺序执行:
我们发现此时block是__NSStackBlock__栈block, 继续往下走
我们发现调动的函数_Block_copy,我们可以在符号断点地方给_Block_copy加个断点
我们进入到_Block_copy函数里面
其实我们研究是栈block通过copy如何变成堆block的,所以我们直接看一下return到底返回的是啥就知道了
我们发现刚进入到_Block_copy时候的block是栈block,然后我们直接在最后的return地方打个断点,然后看下返回的是啥就知道最终返回的是啥block了
我们最后发现在return地方x0的内存地址发生了变化,并且变成了一个堆block
那么为什么这个时候栈block最后变成了堆block呢,我们看一下_Block_copy源码
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;
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
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;
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
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;
}
}
并在在里面会对block的flags做这个判断,
如果block是全局的,那么就直接返回block本身,如果不是,那么就往下走,往下走就是通过malloc在堆区开辟内存空间,然后将原block的信息都copy到堆创建的block中,其中我们注意一下
static int32_t latching_incr_int(volatile int32_t *where) {
while (1) {
int32_t old_value = *where;
if ((old_value & BLOCK_REFCOUNT_MASK) == BLOCK_REFCOUNT_MASK) {
return BLOCK_REFCOUNT_MASK;
}
if (OSAtomicCompareAndSwapInt(old_value, old_value+2, where)) {
return old_value+2;
}
}
}
当flags&BLOCK_NEEDS_FREE有值的时候给block的引用计数器加2,为啥加2呢,因为==1时候是作为释放标示的,这个在下面,将block从栈区copy到堆区时候也可以看到
block的调用
继续上面代码,加上断点,然后打开汇编
运行调试:
我们发现在objc_retainBlock后面还有个objc_release,objc_retainBlock算是block创建完成,从堆区copy到栈区,,而objc_release是将block释放了,所以block的调用肯定在这两行之间,所以我们在第29行出加个断点,然后跳到这,我们先看一下block对象,也就是x0
我们发现此时已经是一个堆block了,我们在看一下29行
-> 0x104852120 <+108>: ldr x8, [x0, #0x10]
意思就是调用函数的意思,[x0, #0x10]意思就是从x0指针右移16个字节,为啥是右移16字节呢,我们看下Block_layout结构
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
block的指针就是isa的位置,而invoke就是需要调用的block的函数指针,而invoke距离isa的位置就是16个自己,所以block位移16个自己就是block的函数指针的位置
这个时候位移16个自己得到x8,我们往下跳一步后,打印一下x8
我们发现确实是_block_invoke,我们进到这个步里面去看一下
确实是block的函数实现
block的签名
依然像上面代码一样,加一个断点,然后在block retain后打印出block的地址,再通过x/5gx打印出该对象的内存情况
我们先看下block的结构Block_layout
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
而且我们知道block的签名在Block_descriptor_3里面,并且需要aBlock->flags & BLOCK_HAS_SIGNATURE都成立才有Block_descriptor_3,我们来测试下是否成立
- 我们flag在isa后面,所以此时flags是0x00000000c1000002的后8位c1000002 我们用mac自带的计算器,先找到BLOCK_HAS_SIGNATURE的值
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler
也就是10进制下1左移30位得到16进制的0x40000000,然后&上flags值c1000002得到0x40000000,不为空,所以Block_descriptor_3是有值的,
- 我们还可以判断一下aBlock->flags & BLOCK_HAS_COPY_DISPOSE,我们先拿到BLOCK_HAS_COPY_DISPOSE值
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler
也就是10进制下1左移30位得到16进制的0x40000000,然后&上flags值c1000002得到0x0,为空,也就是没有Block_descriptor_2
我们再来拿到Block_descriptor_3中的signature值
- 我们先得到block的内存地址,然后打印出block的内存情况,因为descriptor是block的第五个属性,但是第二个和第三个都是4字节,共用一个8字节的内存地址,所以descriptor的内存地址是block的第四块0x0000000102f70018,
- 而descriptor地址就是就是Block_descriptor_1的地址,我们尝试打印下Block_descriptor_1的信息,发现打印不出来,所以我们只能尝试找到signature的内存地址,然后打印出signature的值,
- 从上面我们知道Block_descriptor_1、Block_descriptor_2、Block_descriptor_3,地址是连续的,但是这次Block_descriptor_2不存在,所以Block_descriptor_3内存是在Block_descriptor_1后面
- 我们通过x/4gx打印出Block_descriptor_1后面的所有内存块情况,因为Block_descriptor_1大小是16字节,所有Block_descriptor_3就是内存地址是0x0000000102f70028,而这块内存里面前半部分的值是0x0000000102f6f366
- 所以确定signature值就是0x0000000102f6f366,然后po出来得到了"v8@?0"
然后我们再打印出descriptor的内存情况,上面我们测试知道Block_descriptor_2是不存在的,所以Block_descriptor_3就是第三块内存0x0000000100703366,然后我们打印出 Block_descriptor_3的值就得到block的签名
__block修饰,已经block的三层拷贝
授信我们在main文件写一个带__block参数的block
然后通过命令xcrun -sdk iphonesimulator clang -rewrite-objc main.m转成.cpp文件
但是我们发现转换失败,提示clang不支持,这是因为__block修饰的变量直接赋值常量字符串导致的,我们把它改一下,然后再转换
然后拿到了main.cpp文件,这是因为clang没法和当前Xcode没法完美适配,所以这种情况下, 字面量尽量少用
然后我们拉到最下面,看到生成的c++代码
struct __Block_byref_lg_name_0 {
void *__isa;
__Block_byref_lg_name_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *lg_name;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_lg_name_0 *lg_name; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_lg_name_0 *_lg_name, int flags=0) : lg_name(_lg_name->__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_lg_name_0 *lg_name = __cself->lg_name; // bound by ref
(lg_name->__forwarding->lg_name) = (NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_bebf93_mi_1;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_bebf93_mi_2,(lg_name->__forwarding->lg_name));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->lg_name, (void*)src->lg_name, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->lg_name, 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 argc, char * argv[]) {
NSString * appDelegateClassName;
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
appDelegateClassName = NSStringFromClass(((Class (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("AppDelegate"), sel_registerName("class")));
__attribute__((__blocks__(byref))) __Block_byref_lg_name_0 lg_name = {(void*)0,(__Block_byref_lg_name_0 *)&lg_name, 33554432, sizeof(__Block_byref_lg_name_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((NSString * _Nonnull (*)(id, SEL, NSString * _Nonnull, ...))(void *)objc_msgSend)((id)objc_getClass("NSString"), sel_registerName("stringWithFormat:"), (NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_bebf93_mi_0)};
void (*block1)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_lg_name_0 *)&lg_name, 570425344));
((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我们来一点点分析,先看一下:
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};
我们发现结构体__main_block_desc_0_DATA是__main_block_desc_0类型的
- 第一个参数是0
- 第二个参数是__main_block_impl_0的大小,而__main_block_impl_0就是我们定义的block转换成的c++函数
- 第三个参数是__main_block_copy_0
- 第四个参数是__main_block_dispose_0
而__main_block_copy_0和__main_block_dispose_0这两个函数又跟我们上面说的Block_byref_2一一对应
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep;
BlockByrefDestroyFunction byref_destroy;
};
而__main_block_copy_0函数里面还调用了_Block_object_assign函数
我们在分析一下_Block_object_assign函数,看一下源码
//
// When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
// to do the assignment.
// hold objects - 自动捕获到变量
// lgname
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];
********/
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK:
/*******
void (^object)(void) = ...;
[^{ object; } copy];
********/
*dest = _Block_copy(object);
break;
case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
case BLOCK_FIELD_IS_BYREF:
/*******
// copy the onstack __block container to the heap
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__block ... x;
__weak __block ... x;
[^{ x; } copy];
********/
*dest = _Block_byref_copy(object);
break;
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
/*******
// copy the actual field held in the __block container
// Note this is MRC unretained __block only.
// ARC retained __block is handled by the copy helper directly.
__block id object;
__block void (^object)(void);
[^{ object; } copy];
********/
*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:
/*******
// copy the actual field held in the __block container
// Note this __weak is old GC-weak/MRC-unretained.
// ARC-style __weak is handled by the copy helper directly.
__weak __block id object;
__weak __block void (^object)(void);
[^{ object; } copy];
********/
*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_FIELD_IS_BLOCK = 7, // a block variable ,block变量
BLOCK_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable,__block修饰的结构体
BLOCK_FIELD_IS_WEAK = 16, // declared __weak, only used in byref copy helpers ,__weak修饰的变量
BLOCK_BYREF_CALLER = 128, // called from __block (byref) copy/dispose support routines.,处理block_bref内部对象内存的时候会加一个额外的标记,配合上面的枚举一起使用
};
如果是BLOCK_FIELD_IS_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) { }
我们发现什么都没做,只是因为对象的生命周期是交给ARC做了,这边只需要将指针赋值过去即可
但是这次传进来的是用__block修饰的变量,所以我们看一下是BLOCK_FIELD_IS_BYREF的话是怎么处理的,我们发现是调用了_Block_byref_copy函数然后返回一个对象过来的,我们进入到_Block_byref_copy函数实现看看:
static struct Block_byref *_Block_byref_copy(const void *arg) {
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具有修改能力
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;
}
我们发现在这个函数里面通过malloc在堆区创建了传进来参数相同的内存空间,并且将原来block的forwarding指向了新block,因为原block的forwarding指向的是自己,这样就实现了在block内部可以修改原来变量的能力,这是为什么呢,我们来看看,
首先我们在.cpp文件中,我们发现原来:
- 我们定义的block被转换成了__main_block_impl_0
- 而用__block修饰的变量lg_name也被转换成了__Block_byref_lg_name_0结构体
- 而在__main_block_impl_0结构体中有个属性lg_name是__Block_byref_lg_name_0类型的,并且在使用构造函数__main_block_impl_0的时候将栈区的__Block_byref_lg_name_0
- 在_Block_object_assign函数调用时是将__main_block_impl_0的lg_name传进去的,在_Block_object_assign会将原来的栈区的__Block_byref_lg_name_0结构体malloc到堆区,并且将栈区的__Block_byref_lg_name_0的__forwarding指向了堆区的__Block_byref_l,而堆区的__Block_byref_lg_name_0才是block内部的变量
- 因为此时栈区的__Block_byref_lg_name_0的__forwarding指针指向的是堆区的__Block_byref_lg_name_0,所以当栈区的__Block_byref_lg_name_0修改lg_name其实修改的是堆区的__Block_byref_lg_name_0的lg_name
我们再往下看:
Block_byref_2 *src2 和Block_byref_2 *copy2都是在Block_byref的指针的基础上 加1得到的,所以Block_byref和Block_byref_2的内存是连续的,我们再在.cpp文件中看一下__Block_byref_lg_name_0结构
并且Block_byref的结构是这样的
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;
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
所以src2里面两个属性的值也就是__Block_byref_id_object_copy_131和__Block_byref_id_object_dispose_131
当src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE成立时候,src2调用了byref_keep函数,而byref_keep也就是__Block_byref_id_object_copy_131,我们回到.cpp里面再看看__Block_byref_id_object_copy_131的实现:
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
发现调用了_Block_object_assign,我们看到参数为啥加40呢?
- 第一调用时候(*src2->byref_keep)(copy, src);传进来的参数是copy和src,也就是堆区的__Block_byref_lg_name_0和栈区的__Block_byref_lg_name_0,
- 而__Block_byref_lg_name_0的结构是
struct __Block_byref_lg_name_0 {
void *__isa;
__Block_byref_lg_name_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
NSString *lg_name;
};
加上40个字节正好是__Block_byref_lg_name_0的lg_name的内存地址。而_Block_object_assign上面已经分析过了
总结:
- 当有__block修饰的变量,并且把该变量用于自定义block时候,系统会自动将变量类型改为__Block_byref_变量名_0类型的结构体,自定义block改为__main_block_impl_0类型的结构体
- 并且__main_block_impl_0结构在创建时候是将__main_block_func_0、__main_block_desc_0_DATA、__Block_byref_lg_name_0保存起来
- 然后系统调用了__main_block_desc_0_DATA,再调用了_Block_object_assign函数,传进去的参数是栈区block的__Block_byref_lg_name_0和堆区block的__Block_byref_lg_name_0的指针
- 在_Block_object_assign函数里面判断传进来的参数如果用了__block修饰了,那么就使用_Block_byref_copy把栈区block的__Block_byref_lg_name_0属性copy到堆区,并且同时将栈区对象的forwarding指向到堆区的对象,
- copy过程中。判断该对象的flags如果是BLOCK_BYREF_HAS_COPY_DISPOSE,则调用该参数的byref_keep属性方法,也就是__Block_byref_id_object_copy_131
- 然后在__Block_byref_id_object_copy_131方法里面载调用_Block_object_assign方法,但是此时传进去的值是生成的变量结构体的NSString *lg_name值,这一步就是将栈区就结构体的变量值传堆区结构体的变量属性
- 栈区的__Block_byref_lg_name_0属性copy到堆区完成后,再将堆区的__Block_byref_lg_name_0指针赋给堆区block的__Block_byref_lg_name_0的指针
当系统将栈区block拷贝到堆区后,也会将栈区用__block修饰的参数拷贝到堆区,因为__block修饰的参数系统会自动把他变为结构体形式,其中参数名只是结构体的一个属性,栈区参数拷贝到堆区后,又会将结构体的参数对象也copy一下,这就是block牛逼的三层copy
另一种分析
首先我们看一下从原来的block值(OC代码块)转化而来的C++代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("Block\n");
}
这里,*__cself 是指向Block的值的指针,也就相当于是Block的值它自己(相当于C++里的this,OC里的self),而且很容易看出来,__cself 是指向__main_block_impl_0结构体实现的指针。 结合上句话,也就是说Block结构体就是__main_block_impl_0结构体。Block的值就是通过__main_block_impl_0构造出来的。
下面来看一下__main_block_impl_0这个结构体的声明:
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;
}
};
可以看出,__main_block_impl_0结构体有三个部分:
- 1、第一个是成员变量impl,它是实际的函数指针,它指向__main_block_func_0。来看一下它的结构体的声明:
struct __block_impl {
void *isa;
int Flags;
int Reserved; //今后版本升级所需的区域
void *FuncPtr; //函数指针
};
我们发现这个结构体跟OC对象的结构体是一样的,所以block本质上就是一个对象,并且isa指向的是&_NSConcreteStackBlock,我们可以在libclosure-73源码中找到
这里我们发现block其实是有6中的,其中_NSConcreteGlobalBlock、_NSConcreteMallocBlock、_NSConcreteStackBlock是我们经常用到的,其他三种只是在系统中才用到
- 2、第二个是成员变量是指向__main_block_desc_0结构体的Desc指针,是用于描述当前这个block的附加信息的,包括结构体的大小等等信息
static struct __main_block_desc_0 {
size_t reserved; //今后升级版本所需区域
size_t Block_size;//block的大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
- 3、第三个部分是__main_block_impl_0结构体的构造函数,__main_block_impl_0 就是该 block 的实现
__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;
}
在这个结构体的构造函数里,isa指针保持这所属类的结构体的实例的指针。__main_block_imlp_0结构体就相当于Objective-C类对象的结构体,这里的_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现。
2、Block截获自动变量和对象
a、变量的使用
NSInteger a = 9;
__block NSInteger b = 9;
NSMutableArray *array = [NSMutableArray array];
[array addObject:[NSObject new]];
void (^block)(void) = ^{
// index++;//报错
b++;
[array addObject:@"1"];
NSLog(@"%ld %ld %ld",a,b,array.count);
};
block();
a++;
block();
NSLog(@"%ld %ld %ld",a,b,array.count);
2020-03-06 14:58:07.102646+0800 BYTestBlock[96733:7875542] 9 10 2
2020-03-06 14:58:07.103382+0800 BYTestBlock[96733:7875542] 9 11 3
2020-03-06 14:58:07.103481+0800 BYTestBlock[96733:7875542] 10 11 3
- 1、block可以使用外部的参数,但是如果要在block内部修改外部的参数,需要在参数前添加block
- 2、即使在Block外部修改这些变量,存在于Block内部的这些变量也不会被修改
- 3、这里的修改是指整个变量的赋值操作,变更该对象的操作是允许的,比如在不加上__block修饰符的情况下,给在block内部的可变数组添加对象的操作是可以的
b、原理
我们将.m文件转换成.cpp文件
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
NSInteger b;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
NSInteger a;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__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_b_0 *b = __cself->b; // bound by ref
NSMutableArray *array = __cself->array; // bound by copy
NSInteger a = __cself->a; // bound by copy
(b->__forwarding->b)++;
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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 argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
NSInteger a = 9;
__attribute__((__blocks__(byref))) __Block_byref_b_0 b = {(void*)0,(__Block_byref_b_0 *)&b, 0, sizeof(__Block_byref_b_0), 9};
NSMutableArray *array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("new")));
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, a, (__Block_byref_b_0 *)&b, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
a++;
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_2,a,(b.__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
我们先看看
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSMutableArray *array;
NSInteger a;
__Block_byref_b_0 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSMutableArray *_array, NSInteger _a, __Block_byref_b_0 *_b, int flags=0) : array(_array), a(_a), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 1、我们可以看到,在block内部语法表达式中使用的自动变量(a、array、b)被作为成员变量追加到了__main_block_impl_0结构体中(注意:block没有使用的自动变量不会被追加)。
- 2、在初始化block结构体实例时(请看__main_block_impl_0的构造函数),还需要截获的自动变量a、b、array来初始化__main_block_impl_0结构体实例,因为增加了被截获的自动变量,block的体积会变大。
- 3、b变量声明时候加了__block,在block内部拿到的是b变量的指针,所以在block内部能修改b变量
但是我们发现b变量变成了__Block_byref_b_0,我们看看这个结构体类型:
struct __Block_byref_b_0 {
void *__isa;
__Block_byref_b_0 *__forwarding;
int __flags;
int __size;
NSInteger b;
};
我们可以看到,这个结构体最后的成员变量就相当于原来自动变量。 这里有两个成员变量需要特别注意:
- 1、b:保存了最初的b变量,也就是说原来单纯的int类型的b变量被__block修饰后生成了一个结构体。这个结构体其中一个成员变量持有原来的b变量。
- 2、__forwarding:通过__forwarding,可以实现无论__block变量配置在栈上还是堆上都能正确地访问__block变量,也就是说__forwarding是指向自身的。
再来看一下函数体的代码:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_b_0 *b = __cself->b; // bound by ref
NSMutableArray *array = __cself->array; // bound by copy
NSInteger a = __cself->a; // bound by copy
(b->__forwarding->b)++;
((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id _Nonnull)(NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_0);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yw_jqr80dsn6vd20y1y_cy0rkq00000gn_T_main_f47636_mi_1,a,(b->__forwarding->b),((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
}