原文发布在个人博客
clang工具
block
分类block 结构
block调用
block类型以及ARC对block的影响
外部变量对block的影响
参考文章:
Block技巧与底层解析
Block底层实现分析
iOS Block底层探索
Block-ABI-Apple
clang工具
clang结构化编译器前端,简单理解为可以编译llvm
架构的代码工具
Clang 对源程序进行词法分析和语义分析,并将分析结果转换为 Abstract Syntax Tree ( 抽象语法树 ) ,最后使用 LLVM 作为后端代码的生成器。
使用方法:
clang -rewrite-objc 文件名
新建一个工程,执行clang -rewrite-objc main.c
会生成一个main.cpp
文件
block 结构
先看一个简单的block
:
int main(int argc, const char * argv[]) {
^{
printf("hello world");
}();
}
clang
之后看一下main.cpp
, 把多余代码删掉主要看以下代码:
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;
__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_desc_0结构体时,同时创建了__main_block_desc_0_DATA 并给它赋值,以供在main函数中对__main_block_impl_0进行初始化
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)};
//对应源代码中block内部代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("hello world");
}
//对应源代码的main函数
int main(int argc, const char * argv[]) {
((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA))();
return 0;
}
通过对比可以看到,block
对应struct __main_block_impl_0
这个结构体,意思就是main
函数中第0
个block
实现,这个结构体包含
struct __block_impl impl
-
void *isa
- 指向对应类型的指针
-
int Flags
- 标志变量,在实现block的内部操作时会用到
-
int Reserved
- 保留字段
-
void *FuncPtr
-
block
执行时调用的函数的指针
-
struct __main_block_desc_0
-
size_t reserved
- 保留字段
-
size_t Block_size
-
block
大小
-
__main_block_impl_0
显式的构造函数
这里有一个纠结的地方block
到底是__main_block_impl_0
还是__block_impl
,目前理解为__block_impl
为系统定义block
的实现,__main_block_impl_0
是实际block
实现,相当于在block
本质实现的基础上新增了特性。
对比官方定义的block
/* Revised new layout. */
struct Block_descriptor {
unsigned long int reserved;
unsigned long int size;
void (*copy)(void *dst, void *src);
void (*dispose)(void *);
};
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct Block_descriptor *descriptor;
/* Imported variables. */
};
其中invoke
和FuncPtr
是一样的只是clang
生成的变量名不同,copy
和dispose
时捕获外部变量时使用,在下面会讨论。
所以得出一个结论block
是一个包含调用函数指针、block外部上下文变量的结构体,其次内部包含isa
指针,说明block
也是一个对象
block
调用
创建简单block
void(^testblock)() =^{
printf("hello world");
};
testblock();
执行clang
,其他生成代码都和上面基本一致主要看main
函数
struct __main_block_impl_2 {
struct __block_impl impl;
struct __main_block_desc_2* Desc;
__main_block_impl_2(void *fp, struct __main_block_desc_2 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
void(*testblock)() =((void (*)())&__main_block_impl_2((void *)__main_block_func_2, &__main_block_desc_2_DATA));
((void (*)(__block_impl *))((__block_impl *)testblock)->FuncPtr)((__block_impl *)testblock);
1. 调用__main_block_impl_2
显式构造函数
2. &
将1结果地址赋值给testblock
3. 将testblock
强转成__block_impl
调用FuncPtr
也就是__main_block_func_2
这里有一个问题,理论上testblock
的类型是__main_block_impl_2
为什么可以强转成__block_impl
?
这是因为&
取得是起始地址,结构体的起始地址和他第一个元素的起始地址是一致的也就是说&__main_block_impl_2
和&(__main_block_impl_2->__block_impl)
地址是一样的,所以这里可以强制转化
block
类型以及ARC
对block
的影响
block
的常见类型有3种:
NSConcreteStackBlock
(栈)NSConcreteGlobalBlock
(全局)NSConcreteMallocBlock
(堆)
我们先简单创建两个block
#import "TestBlock.h"
void (^globalBlock)(void) = ^{
};
@implementation TestBlock
- (void)testStackBlock{
void(^stackBlock)(void) = ^{
NSLog(@"stackBlock");
};
stackBlock();
}
@end
对其进行编译转换后得到以下缩略代码:
...
struct __globalBlock_block_impl_0 {
struct __block_impl impl;
struct __globalBlock_block_desc_0* Desc;
__globalBlock_block_impl_0(void *fp, struct __globalBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
struct __TestBlock__testStackBlock_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__testStackBlock_block_desc_0* Desc;
__TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
...
观察一下上面简单block
发现****impl.isa
****便是对应的block
类型;可以看到globalBlock
属于NSConcreteGlobalBlock
,stackBlock
属于NSConcreteStackBlock
。
然而我们实际输出:
stackblock
也属于globalBlock
,why
???
参照唐巧博客解释
由于 clang 改写的具体实现方式和 LLVM 不太一样,并且这里没有开启 ARC。所以这里我们看到 isa 指向的还是_NSConcreteStackBlock。但在 LLVM 的实现中,开启 ARC 时,block 应该是 _NSConcreteGlobalBlock 类型
详细的LLVM
解析看llvm对于Block的编译规则(我没全部看完)。
可以理解为由于block中的代码没有捕获任何外部变量,这个block
不存在任何内存泄漏的风险,也不需要引用计数,所以类型为__NSGlobalBlock__
。
所以如果block
内部引用了外部变量就不会变成__NSGlobalBlock__
,新增以下代码:
- (void)testStackBlock {
...
int a = 0;
void(^blockWithVar)(void) = ^{
NSLog(@"%d", a);
};
blockWithVar();
...
}
clang 之后:
struct __TestBlock__testStackBlock_block_impl_2 {
struct __block_impl impl;
struct __TestBlock__testStackBlock_block_desc_2* Desc;
int a;
__TestBlock__testStackBlock_block_impl_2(void *fp, struct __TestBlock__testStackBlock_block_desc_2 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
发现impl.isa
指向NSConcreteStackBlock
,然而我们输出发现:
创建block
时是在__NSStackBlock__
,而赋值给blockWithVar
后,blockWithVar
属于__NSMallocBlock__
,这是因为ARC
环境下
在 ARC 下,block 类型通过=进行传递时,会导致调用objc_retainBlock->_Block_copy->_Block_copy_internal方法链。并导致 NSStackBlock 类型的 block 转换为 NSMallocBlock 类型。
原文地址:https://www.jianshu.com/p/0855b68d1c1d
NSObject.mm源代码可以看到
//
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//
id objc_retainBlock(id x) {
#if ARR_LOGGING
objc_arr_log("objc_retain_block", x);
++CompilerGenerated.blockCopies;
#endif
return (id)_Block_copy(x);
}
_Block_copy
是在runtime.c中实现的
void *_Block_copy(const void *arg) {
return _Block_copy_internal(arg, WANTS_ONE);
}
...
#if 0
#pragma mark Copy/Release support
#endif /* if 0 */
/* Copy, or bump refcount, of a block. If really copying, call the copy helper if present. */
static void *_Block_copy_internal(const void *arg, const int flags) {
struct Block_layout *aBlock;
const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
//printf("_Block_copy_internal(%p, %x)\n", arg, flags);
if (!arg) return NULL;
// The following would be better done as a switch statement
aBlock = (struct Block_layout *)arg;
// 堆block引用计数加1
if (aBlock->flags & BLOCK_NEEDS_FREE) {
// latches on high
latching_incr_int(&aBlock->flags);
return aBlock;
}
else if (aBlock->flags & BLOCK_IS_GC) {
// GC refcounting is expensive so do most refcounting here.
if (wantsOne && ((latching_incr_int(&aBlock->flags) & BLOCK_REFCOUNT_MASK) == 1)) {
// Tell collector to hang on this - it will bump the GC refcount version
_Block_setHasRefcount(aBlock, true);
}
return aBlock;
}
//全局类型直接返回
else if (aBlock->flags & BLOCK_IS_GLOBAL) {
return aBlock;
}
------------------------------------------------------------
// Its a stack block. Make a copy.
------------------------------------------------------------
if (!isGC) {
struct Block_layout *result = malloc(aBlock->descriptor->size);
if (!result) return (void *)0;
memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
// reset refcount
result->flags &= ~(BLOCK_REFCOUNT_MASK); // XXX not needed
// 添加需要释放的flag
result->flags |= BLOCK_NEEDS_FREE | 1;
result->isa = _NSConcreteMallocBlock;
if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
//printf("calling block copy helper %p(%p, %p)...\n", aBlock->descriptor->copy, result, aBlock);
(*aBlock->descriptor->copy)(result, aBlock); // do fixup
}
return result;
}
else {
...
}
}
关闭ARC
测试下:
此时输出blockWithVar
是属于__NSStackBlock__
https://huanghehg.github.io/images/block/5.png
我们打开ARC
继续看,ARC
环境下所有的block
赋值给变量都会copy
到堆上吗?
发现使用__weak
修饰时并不会复制到堆上。所以如果使用要注意!!!
ARC对类型为strong且捕获了外部变量的block进行了copy。并且当block****类型为strong,但是创建时没有捕获外部变量,block最终会变成NSGlobalBlock类型
外部变量对block
的影响
捕捉局部变量的影响
首先看下面的代码
int a = 1;
void(^blockWithVar)(void) = ^{
NSLog(@"%d", a);
};
void(^blockWithNonVar)(void) = ^{
NSLog(@"test");
};
转化后
struct __TestBlock__testStackBlock_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__testStackBlock_block_desc_0* Desc;
int a;
__TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_91f955_mi_0, a);
}
...
struct __TestBlock__testStackBlock_block_impl_1 {
struct __block_impl impl;
struct __TestBlock__testStackBlock_block_desc_1* Desc;
__TestBlock__testStackBlock_block_impl_1(void *fp, struct __TestBlock__testStackBlock_block_desc_1 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestBlock__testStackBlock_block_func_1(struct __TestBlock__testStackBlock_block_impl_1 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_91f955_mi_1);
}
...
static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
int a = 1;
void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, a));
void(*blockWithNonVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_1((void *)__TestBlock__testStackBlock_block_func_1, &__TestBlock__testStackBlock_block_desc_1_DATA));
}
对比发现blockWithVar
在转化后多了一个int a
的变量,同时在显式构造函数里多了int _a
,后面的: a(_a)
相当于a = _a
,是c++
中的初始化列表。通过
static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
int a = 1;
void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, a));
}
static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_91f955_mi_0, a);
}
发现a
传递到block
中是值传递,在调用里会生成另外一个a
(int a = __cself->a;
) 所以我们在block
中更改a
的值是不会生效的。同时编译器也会报错
看报错提示加上__block
,那我们加上__block
看会有什么影响:
- (void)testStackBlock{
__block int a = 1;
void(^blockWithVar)(void) = ^{
NSLog(@"pre => %d", a);
a = 3;
};
blockWithVar();
NSLog(@"res => %d", a);
void(^blockWithNonVar)(void) = ^{
NSLog(@"test");
};
}
转化后关键代码:
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __TestBlock__testStackBlock_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__testStackBlock_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_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 __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_63aeec_mi_0, (a->__forwarding->a));
(a->__forwarding->a) = 3;
}
// 辅助copy函数,下面会用到
static void __TestBlock__testStackBlock_block_copy_0(struct __TestBlock__testStackBlock_block_impl_0*dst, struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
// 辅助dispose函数,下面会用到
static void __TestBlock__testStackBlock_block_dispose_0(struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __TestBlock__testStackBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __TestBlock__testStackBlock_block_impl_0*, struct __TestBlock__testStackBlock_block_impl_0*);
void (*dispose)(struct __TestBlock__testStackBlock_block_impl_0*);
} __TestBlock__testStackBlock_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__testStackBlock_block_impl_0), __TestBlock__testStackBlock_block_copy_0, __TestBlock__testStackBlock_block_dispose_0};
// 对应的是testStackBlock
static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 1};
void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
}
先看最后的
_I_TestBlock_testStackBlock
,发现加上__block
关键字之后a
已经不是int
类型而是对应__Block_byref_a_0
类型再观察
__Block_byref_a_0
包含:
void *__isa
说明是一个对象__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
这里面的a
对应的就是block
外面赋的值
- 继续观察
(__Block_byref_a_0 *)&a
,在block
编译时将(__Block_byref_a_0 *)&a
传给了block
,所以不再是值传递而是内存地址传递,所以在block
内可以操纵a
那么如果直接传递内存地址而不使用__block
可以吗?
将代码修改如下
- (void)testStackBlock{
int a = 1;
int *p = &a;
void(^blockWithVar)(void) = ^{
NSLog(@"pre => %d", *p);
*p = 3;
};
blockWithVar();
NSLog(@"res => %d", *p);
}
运行发现可以修改,但这样很明显可以看出来如果a
释放了,p
就变成了野指针,如果block是作为参数或者返回值,这些类型都是跨栈的,也就是说再次调用会造成野指针错误。例如下面的代码:
- (void)testStackBlock{
int a = 1;
int *p = &a;
void(^blockWithVar)(void) = ^{
NSLog(@"pre => %d", *p);
*p = 3;
};
// blockWithVar();
NSLog(@"res => %d", *p);
[self.blockArray addObject:blockWithVar];
}
- (void)testBlock:(void(^)(void))block {
block();
}
捕捉局部静态变量的影响
- (void)testStackBlock{
static int a = 1;
void(^blockWithVar)(void) = ^{
a = 3;
};
NSLog(@"pre a => %d", a);
blockWithVar();
NSLog(@"res a => %d", a);
}
转化后
struct __TestBlock__testStackBlock_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__testStackBlock_block_desc_0* Desc;
int *a;
__TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
int *a = __cself->a; // bound by copy
//地址访问
(*a) = 3;
}
static struct __TestBlock__testStackBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
} __TestBlock__testStackBlock_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__testStackBlock_block_impl_0)};
static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
static int a = 1;
void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, &a));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_188fa3_mi_0, a);
((void (*)(__block_impl *))((__block_impl *)blockWithVar)->FuncPtr)((__block_impl *)blockWithVar);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_188fa3_mi_1, a);
}
可以看到此时a
是地址传递,在block内部也可以成功更改a
的值,
需要注意一点的是静态局部变量是存储在静态数据存储区域的,也就是和程序拥有一样的生命周期,也就是说在程序运行时,都能够保证block访问到一个有效的变量。但是其作用范围还是局限于定义它的函数中,所以只能在block通过静态局部变量的地址来进行访问。
捕捉全局变量的影响
int b = 3;
static int c = 4;
- (void)testStackBlock{
void(^blockWithVar)(void) = ^{
b = 5;
c = 6;
};
NSLog(@"pre b => %d", b);
NSLog(@"pre c => %d", c);
blockWithVar();
NSLog(@"pre b => %d", b);
NSLog(@"pre c => %d", c);
}
转化后
int b = 3;
static int c = 4;
...
static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
b = 5;
c = 6;
}
...
static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_0, b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_1, c);
((void (*)(__block_impl *))((__block_impl *)blockWithVar)->FuncPtr)((__block_impl *)blockWithVar);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_2, b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_d646c1_mi_3, c);
}
可以看到全局变量都是直接访问变量的,是因为全局变量存储在静态数据存储区,在程序结束前不会被销毁
实例变量
@interface TestBlock ()
{
NSString *_str;
int _a;
}
@end
- (void)testStackBlock{
void(^blockWithVar)(void) = ^{
_a = 5;
_str = @"test";
};
NSLog(@"res a => %d", _a);
NSLog(@"res str => %@", _str);
blockWithVar();
NSLog(@"res a => %d", _a);
NSLog(@"res str => %@", _str);
}
这里编译器会给我们警告,意思就是有隐式的self
引用,我们转化一下
struct __TestBlock__testStackBlock_block_impl_0 {
struct __block_impl impl;
struct __TestBlock__testStackBlock_block_desc_0* Desc;
TestBlock *self;//TestBlock 类
__TestBlock__testStackBlock_block_impl_0(void *fp, struct __TestBlock__testStackBlock_block_desc_0 *desc, TestBlock *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __TestBlock__testStackBlock_block_func_0(struct __TestBlock__testStackBlock_block_impl_0 *__cself) {
TestBlock *self = __cself->self; // bound by copy
// self+实例变量a的偏移值
(*(int *)((char *)self + OBJC_IVAR_$_TestBlock$_a)) = 5;
(*(NSString **)((char *)self + OBJC_IVAR_$_TestBlock$_str)) = (NSString *)&__NSConstantStringImpl__var_folders_11__5wr7xmx2d944s1pgmnw7mn80000gn_T_TestBlock_0453bc_mi_0;
}
static void __TestBlock__testStackBlock_block_copy_0(struct __TestBlock__testStackBlock_block_impl_0*dst, struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_assign((void*)&dst->self, (void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __TestBlock__testStackBlock_block_dispose_0(struct __TestBlock__testStackBlock_block_impl_0*src) {_Block_object_dispose((void*)src->self, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static struct __TestBlock__testStackBlock_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __TestBlock__testStackBlock_block_impl_0*, struct __TestBlock__testStackBlock_block_impl_0*);
void (*dispose)(struct __TestBlock__testStackBlock_block_impl_0*);
} __TestBlock__testStackBlock_block_desc_0_DATA = { 0, sizeof(struct __TestBlock__testStackBlock_block_impl_0), __TestBlock__testStackBlock_block_copy_0, __TestBlock__testStackBlock_block_dispose_0};
static void _I_TestBlock_testStackBlock(TestBlock * self, SEL _cmd) {
//self 传进去
void(*blockWithVar)(void) = ((void (*)())&__TestBlock__testStackBlock_block_impl_0((void *)__TestBlock__testStackBlock_block_func_0, &__TestBlock__testStackBlock_block_desc_0_DATA, self, 570425344));
...
}
通过上面看到block
内部会生成一个TestBlock *self
,它的值便是_I_TestBlock_testStackBlock
中的self
所以可以更改实例变量,当然这里会有一个循环引用的问题,也就是说block
引用实例变量也会强引用self
总结:
block
本质上是一个包含调用函数指针、block外部上下文变量的结构体block
有根据存储位置不同分为三种类型NSConcreteStackBlock(栈)NSConcreteGlobalBlock(全局)NSConcreteMallocBlock(堆)
ARC
模式下会把不引用外部变量的block
转化成NSConcreteGlobalBlock
,引用外部变量的block
会在赋值时转化为NSConcreteMallocBlock
block
引用外部局部变量和静态局部变量或实例变量时会在block
内部生成对应的变量。在引用全局变量时并不会生成对应变量。Block
会对内部的变量形成强引用,而如果同时该变量又持有这个Block
,就会导致循环引用而无法释放,从而导致内存泄露。注意隐式的循环引用