前言
block的类型
从一段代码开始
// 全局变量 n,name
static int n = 100;
static NSString *name = @"zezefamily";
- (void)demo10
{
//0.没有引用外部变量的 testBlock0
void (^testBlock0)(BOOL ok);
testBlock0 = ^(BOOL ok){
};
NSLog(@"testBlock0 == %@",testBlock0);
//1.引用全局静态变量的 testBlock1
void (^testBlock1)(BOOL ok);
testBlock1 = ^(BOOL ok){
n;
name;
};
NSLog(@"testBlock1 == %@",testBlock1);
//2.引用了局部变量的 testBlock2
int a = 10;
void (^testBlock2)(BOOL ok);
testBlock2 = ^(BOOL ok){
a;
};
NSLog(@"testBlock2 == %@",testBlock2);
//3.引用了局部静态变量的 testBlock3
static int c = 15;
void (^testBlock3)(BOOL ok);
testBlock3 = ^(BOOL ok){
c;
};
NSLog(@"testBlock2 == %@",testBlock3);
//4.没有引用外部变量且弱引用修饰的 testBlock4
void (^__weak testBlock4)(void) = ^{
};
NSLog(@"testBlock4 == %@",testBlock4);
//5.引用了局部变量的且弱引用修饰的 testBlock5
NSString *b = @"string";
void (^__weak testBlock5)(void) = ^{
b;
};
NSLog(@"testBlock5 == %@",testBlock5);
//6.引用了全局静态变量且弱引用修饰的 testBlock6
void (^__weak testBlock6)(void) = ^{
n;
name;
};
NSLog(@"testBlock6 == %@",testBlock6);
}
看下打印信息:
TestApp[69115:8949095] testBlock0 == <__NSGlobalBlock__: 0x10ef24230>
TestApp[69115:8949095] testBlock1 == <__NSGlobalBlock__: 0x10ef24250>
TestApp[69115:8949095] testBlock2 == <__NSMallocBlock__: 0x6000004d4930>
TestApp[69115:8949095] testBlock2 == <__NSGlobalBlock__: 0x10ef24290>
TestApp[69115:8949095] testBlock4 == <__NSGlobalBlock__: 0x10ef242d0>
TestApp[69115:8949095] testBlock5 == <__NSStackBlock__: 0x7ffee0cdd100>
TestApp[69115:8949095] testBlock6 == <__NSGlobalBlock__: 0x10ef242f0>
首先我们可以看到,block
有3中类型,分别为:NSGlobalBlock
,NSMallocBlock
,NSStackBlock
。
1.当没有引用任何外部变量的,或引用了全局静态变量的,或引用了局部静态变量的block
为NSGlobalBlock
(全局block
);
2.当引用了局部变量且没有__weak
修饰的block
,为NSMallocBlock
(堆block
);
3.当引用了局部变量且__weak
修饰的block
,为NSStackBlock
(栈block
);
block的本质
我们在main.m
中注入几行简单的block
代码片段,然后通过Clang
看一下编译后的情况,下面是简单的block
应用:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 测试代码
void (^testBlock)(void) = ^{
NSLog(@"hello block");
};
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
通过Clang
来看下编译后的样子:
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")));
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 UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
上面代码看着比较凌乱,我们稍微的整理一下:
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")));
void (*testBlock)(void) = _main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
testBlock->FuncPtr(testBlock);
}
return UIApplicationMain(argc, argv, __null, appDelegateClassName);
}
简化之后我们可以看到,我们的block
代码部分被转化成了一个__main_block_impl_0 ()
的函数,通过传入了__main_block_func_0
,__main_block_desc_0_DATA
2个参数。
testBlock();
调用部分被转化为了testBlock->FuncPrt(testBlock)
,调用FuncPrt
函数,并将testBlock
自己传入。
首先我们先看下__main_block_func_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;
}
};
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
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)};
首先__main_block_impl_0
是一个结构体,包含impl
,Desc
,和一个同名的构造函数,而我们的block
最终访问的就是这个__main_block_impl_0()
构造函数,内部对impl
和Desc
进行了赋值操作,这里重点看下impl.FuncPtr = fp
, 这里的fp
就是外界出入的__main_block_func_0
接下来我们看下构造函数里的参数:
参数1:__main_block_func_0
也就是这个fp
,同样是impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_yy_htpy_x9s09v1zf7ms0jgytwr0000gn_T_main_9dacc5_mi_0);
}
这里就是block
代码块。
__main_block_func_0
是什么时候被调用的呐?这里看一下main
函数
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
这里将testBlock
强转为__block_impl*
类型,并调用了FuncPtr()
,也就是__main_block_func_0 ()
函数,之后就执行了整个block
;
到这里,我们也就知道了,我们的block
,在编译时被包装成了一个xx_block_impl_0
的结构体,并执行了同名的构造函数,将block
块转换成__xx_block_func_0
函数作为参数传入,同时还传入了一些描述信息。执行block
时,被转为调用FuncPtr()
函数,即__xx_block_func_0
函数。
block变量捕获
我们在来看下block
的变量捕获,首先给我们的代码添加一个变量:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 测试代码
int a = 1024;
void (^testBlock)(void) = ^{
NSLog(@"a = %d",a);
};
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
Clang
看下编译后有什么不同,直接将main
函数中简化的代码片段看一下:
int main(int argc, char * argv[]) {
{
int a = 1024;
void (*testBlock)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a);
testBlock->FuncPtr(testBlock);
}
}
这里可以看到我们定义的变量a
,同时可以看到__main_block_impl_0
函数末尾也多了一个参数a
; 再看下__main_block_impl_0
结构体:
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;
}
};
这个结构体中也明显的看到了多一个成员int a;
,同时构造函数也多了一个参数,并将传入的_a
赋值给a
;
这就意味着,在程序编译时,自动将外部变量捕获并添加到了__main_block_impl_0
结构体内;
再看下__main_block_func_0()
代码块:
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
NSLog((NSString *)&"a = %d",a);
}
这里可以注意到,NSLog
输出的这个变量a
,并不是block
外界的那个int a
,而是来自于__cself->a
,即__main_block_impl_0
结构体中的int a
变量,这里我的理解是发生了一次深拷贝的a
, 这样也就解释了为什么非__block
修饰的变量无法在block
内部修改,因为他们完全不是同一样变量(值和指针都不是同一个)。
接下来使用__block
修饰一下int a
,看编译结果:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 测试代码
__block int a = 1024;
void (^testBlock)(void) = ^{
a++;
NSLog(@"a = %d",a);
};
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
编译后:
int main() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
1024
};
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);
}
这里很明显的看到使用__block
修饰的变量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()
函数:
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;
}
};
这个可以看到a = _a->__forwarding
,即外界变量a
的指针地址,再看下__main_block_func_0
代码块:
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)++;
NSLog((NSString *)&"a = %d",(a->__forwarding->a));
}
这里的a++
操作,是对__forwarding->a
的操作,即外界的那个变量a。所以通过__block
修饰后的变量为指针拷贝(浅拷贝),都指向同一片地址空间。
我们可以用更直接的方式验证一下:
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
// block 测试代码
__block int a = 1024;
int b = 100;
NSString *c = @"我是c";
__block NSString *d = @"我是d";
NSLog(@"a = %d",a);
NSLog(@"b = %d",b);
NSLog(@"c = %@, c = %p, &c = %p",c,c,&c);
NSLog(@"d = %@, d = %p, &d = %p",d,d,&d);
void (^testBlock)(void) = ^{
NSLog(@"block内,c = %@, c = %p, &c = %p",c,c,&c);
NSLog(@"block内,d = %@, d = %p, &d = %p",d,d,&d);
NSLog(@"block内,a = %d, a = %d, &a = %p",a,a,&a);
NSLog(@"block内,b = %d, b = %d, &b = %p",b,b,&b);
};
a++;
b++;
c = @"我修改了c";
d = @"我修改了d";
NSLog(@"block外,c = %@, c = %p, &c = %p",c,c,&c);
NSLog(@"block外,d = %@, d = %p, &d = %p",d,d,&d);
NSLog(@"block外,a = %d",a);
NSLog(@"block外,b = %d",b);
//执行block
testBlock();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
打印结果:
TestApp[6068:9618080] c = 我是c, c = 0x10f1cb640, &c = 0x7ffee0a38d08
TestApp[6068:9618080] d = 我是d, d = 0x10f1cb660, &d = 0x7ffee0a38d00
TestApp[6068:9618080] block外,c = 我修改了c, c = 0x10f1cb740, &c = 0x7ffee0a38d08
TestApp[6068:9618080] block外,d = 我修改了d, d = 0x10f1cb760, &d = 0x6000030b3268
TestApp[6068:9618080] block外,a = 1025
TestApp[6068:9618080] block外,b = 101
TestApp[6068:9618080] block内,c = 我是c, c = 0x10f1cb640, &c = 0x600002ba9be0
TestApp[6068:9618080] block内,d = 我修改了d, d = 0x10f1cb760, &d = 0x6000030b3268
TestApp[6068:9618080] block内,a = 1025, a = 1025, &a = 0x600003e925f8
TestApp[6068:9618080] block内,b = 100, b = 100, &b = 0x600002ba9bf8
再结合刚才的源码是不是瞬间清晰了很多。而且在block
内部访问的参数其实是有一个__cself
的隐藏参数间接访问的,尽管说直观看到的block
内外变量的形式并无差别。
底层原理
上面我们都是编译时的探索,接下我们通过汇编来看下block
运行时的情况。
先来一个简单的代码段:
- (void)viewDidLoad {
[super viewDidLoad];
int a = 10;
void (^block)(void) = ^{
a;
NSLog(@"hello block");
};
block();
}
文章一开始就介绍,当一个没有引用任何外部变量时,是一个__NSGlobalBlock__
类型的block
,引用局部变量时,是一个__NSMallocBlock__
,但在Clang
编译后发现无论怎么引用外部变量,其中的isa
都指向_NSConcreteStackBlock
类型,这也就说明其他的类型是由运行时动态决定的,但它们是在什么时候发生的改变呐?接下来详细看下:
下一个符号断点
objc_retainBlock
,看到其内部跳转了_Block_copy
指令:
看下
_Block_copy
内部实现:
可以看到这个指令来自于
libsystem_blocks.dylib
库。
从
libobjc.A.dylib
的objc_retainBlock
指令,进入了libsystem_blocks.dylib
的_Block_copy
指令;
首先看下我们的代码片段,根据之前我们知道的当
block
引用了局部变量为__NSMallocBlock__
,这里我们观察一下objc_retainBlock
的block
变化情况:
(lldb) register read x0
x0 = 0x000000016b11b850
(lldb) po 0x000000016b11b850
<__NSStackBlock__: 0x16b11b850>
signature: "v8@?0"
invoke : 0x104cedbc0 (/private/var/containers/Bundle/Application/92CCF1EA-8000-48E0-A52B-4B8ECDDFC227/TestApp.app/TestApp`__main_block_invoke)
(lldb)
通过寄存器输出,我们看到当前block
为__NSStackBlock__
类型
向下执行一步,然后再次查看寄存器的情况:
(lldb) register read x0
x0 = 0x00000002833a02d0
(lldb) po 0x00000002833a02d0
<__NSMallocBlock__: 0x2833a02d0>
signature: "v8@?0"
invoke : 0x104cedbc0 (/private/var/containers/Bundle/Application/92CCF1EA-8000-48E0-A52B-4B8ECDDFC227/TestApp.app/TestApp`__main_block_invoke)
(lldb)
此时其实观察x0
,地址已经发生了变化,同时block
类型由原来的__NSStackBlock__
变为__NSMallocBlock__
,也就是我们NSLog
输出的类型。
下一步我们看下objc_retainBlock
指令内部操作:
此时打印寄存器输出:
(lldb) register read x0
x0 = 0x000000016ae13850
(lldb) po 0x000000016ae13850
<__NSStackBlock__: 0x16ae13850>
signature: "v8@?0"
invoke : 0x104ff5bc0 (/private/var/containers/Bundle/Application/BC10EC5A-B715-4785-843E-50181B210D15/TestApp.app/TestApp`__main_block_invoke)
(lldb)
这里说明真正发生改变的还在_Block_copy
指令:
这里我们忽略掉
_Block_copy
中间的执行部分,直接在retab
指令时下断点,并查看寄存器信息:
(lldb) register read x0
x0 = 0x0000000281d68000
(lldb) po 0x0000000281d68000
<__NSMallocBlock__: 0x281d68000>
signature: "v8@?0"
invoke : 0x104ff5bc0 (/private/var/containers/Bundle/Application/BC10EC5A-B715-4785-843E-50181B210D15/TestApp.app/TestApp`__main_block_invoke)
(lldb)
此时的block
类型已经变为了__NSMallocBlock__
类型,也就是说是在_Block_copy
时发生了改变。即:__NSStackBlock__
-> _Block_copy
-> __NSMallocBlock__
那我们就看一下_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) { //是否为Global Block
return aBlock;
}
else { // ARC 下,StackBlock ,copy to MallocBlock ,
// 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;
}
}
比较醒目的注释
// Its a stack block. Make a copy.
如果是stack block
执行一次拷贝
- 通过
malloc()
开辟一个Block_layout
类型的内存空间result
; - 将
aBlock
的内容拷贝给result
- 给
result
赋值,将result->isa
指向_NSConcreteMallocBlock
; - 返回
result
;
从源码可以看到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
};
typedef void(*BlockInvokeFunction)(void *, ...);
#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
};
结构体中的isa
指向了block
类型; flags
存放了很多的标识符信息,类似于isa_t
; invoke
是block
代码块,编译时看到的那个fp->FuncPtr->_block_func_0
,descriptor
指附加属性,这里可以看到他是一个Block_descriptor_1
的结构体,往下可以看到Block_descriptor_2
,Block_descriptor_3
, 这些都是由flags
内部标识动态决定的。
下面是flags
的标识定义:
// 注释: flag 标识
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime
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
};
BLOCK_HAS_COPY_DISPOSE
决定了是否包含Block_descriptor_2
;
BLOCK_HAS_SIGNATURE
决定了是否包含Block_descriptor_3
;
对应的实现:
#if 0
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;
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;
}
通过flags
+指针平移向下取值;
我们从编译时的源码看到,使用__block
修饰的变量,会被包装成一个Block_byref
的结构体对象:
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
// 注释: __Block 修饰的结构体 byref_keep 和 byref_destroy 函数 - 来处理里面持有对象的保持和销毁
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;
};
这里也会根据flags
标识,动态的插入Block_byref_2
和Block_byref_3
的属性
// Values for Block_byref->flags to describe __block variables
enum {
// Byref refcount must use the same bits as Block_layout's refcount.
// BLOCK_DEALLOCATING = (0x0001), // runtime
// BLOCK_REFCOUNT_MASK = (0xfffe), // runtime
BLOCK_BYREF_LAYOUT_MASK = (0xf << 28), // compiler
BLOCK_BYREF_LAYOUT_EXTENDED = ( 1 << 28), // compiler
BLOCK_BYREF_LAYOUT_NON_OBJECT = ( 2 << 28), // compiler
BLOCK_BYREF_LAYOUT_STRONG = ( 3 << 28), // compiler
BLOCK_BYREF_LAYOUT_WEAK = ( 4 << 28), // compiler
BLOCK_BYREF_LAYOUT_UNRETAINED = ( 5 << 28), // compiler
BLOCK_BYREF_IS_GC = ( 1 << 27), // runtime
BLOCK_BYREF_HAS_COPY_DISPOSE = ( 1 << 25), // compiler
BLOCK_BYREF_NEEDS_FREE = ( 1 << 24), // runtime
};
block
捕获外部变量的核心操作->_Block_object_assign
// 注释: Block 捕获外界变量的操作
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_FIELD_IS_BYREF = 8, // the on stack structure holding the __block variable
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.
};
该方法会通过传入的不同类型(id,NSObject
,__block
,__weak
)的数据做响应的处理;
++++++++++++++++++++++start++++++++++++++++++++++++++
以下内容是我对Block
的总结:
block 在编译时会 被转换为一个 Block_layout 结构体
同时会将变量捕获到该结构体内部 指针捕获,值捕获
__block 修饰的变量 会被包装为 Block_byref 的结构体
Block_byref 内部的flags 也会根据类型 来决定是否包含 Block_byref1 2
同时动态生成的 flags 值 来确定是否包含 Block_descriptor_1 2 3
运行时,runtime 会调用 Block_copy()
根据类型来判断生成什么类型的Block_layout
判断是否为BLOCK_NEEDS_FREE类型 如果是 直接返回
判断是否为BLOCK_IS_GLOBAL类型 如果是 直接返回
否则 从 栈空间 copy 到 堆空间
1. 通过malloc()开辟一个Block_layout类型的result
2. memmove 将编译时的Block_layout 映射给 result
3. 对 result 做一些初始化操作
4. _Block_call_copy_helper(result, aBlock);
4.1 判断是否包含Block_descriptor_2,如果存在,执行desc2->copy(result, aBlock)->
_Block_object_assign(void *destArg, const void *object, const int flags)
4.1.1 判断当前捕获的参数类型
BLOCK_FIELD_IS_OBJECT 普通对象类型
_Block_retain_object(object);
*dest = object;
BLOCK_FIELD_IS_BLOCK block对象类型
*dest = _Block_copy(object);
BLOCK_FIELD_IS_BYREF __block修饰类型
*dest = _Block_byref_copy(object); //对Block_byref类型进行处理
1.拿到编译期生成的Block_byref类型的src
2.通过malloc创建Block_byref类型的copy
3.对copy进行初始化赋值操作
4.判断src 是否包含Block_byref1 Block_byref2
4.1如果包含
4.1.1 对copy进行Block_byref1赋值
4.1.2 调用Block_byref1->byref_keep->_Block_object_assign()
4.2 否则 memmove()
BLOCK_FIELD_IS_WEAK __weak修饰类型
*dest = _Block_byref_copy(object);
BLOCK_BYREF_CALLER called from __block (byref)
*dest = object;
5. result.isa = _NSConcreteMallocBlock
6. 返回 result
响应block:通过reuslt->invoke()->_block_func_0()
Block即将销毁时,runtime调用_Block_release()
1.获取当前Block_layout aBlock;
2.判断是否为空,是否为GlobalBlock 是否不需要Free 直接return
3._Block_call_dispose_helper(aBlock);
3.1 获取Block_descriptor_2 数据 desc
3.2 desc->dispose()->__main_block_dispose_0->_Block_object_dispose()
3.2.1 是否为BLOCK_FIELD_IS_BYREF __block 修饰
_Block_byref_release(object)
1.判断是否存在 Block_byref2 存在 调用byref2->byref_destroy()->__Block_byref_id_object_dispose()->
_Block_object_dispose()
2.free(byref)
3.2.2 是否为BLOCK_FIELD_IS_BLOCK block对象
_Block_release(object);
3.2.3 其他类型不处理
4._Block_destructInstance(aBlock); //这个里面其实什么都没做
5.free(aBlock)
==============>以下编译时Block生成的数据结构<====================
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
Block_layout {
//基本结构
&_NSConcreteStackBlock, // isa
570425344, // flags
0, // reserved
__main_block_func_0, // invoke
//根据flags 动态添加 [Block_descriptor_1]
0, // Block_descriptor_1 -> reserved
sizeof(struct __main_block_impl_0), // Block_descriptor_1 -> size
//根据flags 动态添加 [Block_descriptor_2]
__main_block_copy_0, // Block_descriptor_2 -> copy
__main_block_dispose_0 // Block_descriptor_2 -> dispose
//变量捕获
NSString *age; // 对象类型 age
int *b; // 基本数据类型 b
__Block_byref *a; // __block修饰类型
__Block_byref *string; // __block修饰类型
}
Block_byref a = {
//基本结构
(void*)0, //isa
(Block_byref *)&a, //forwarding
0, //flags
sizeof(__Block_byref_a_0), //size
//变量捕获
10 //变量a的值
};
Block_byref string = {
(void*)0, //isa
(__Block_byref *)&string,, //forwarding
33554432, //flags
sizeof(__Block_byref), //size
//根据flag动态添加
__Block_byref_id_object_copy, //Block_byref_2 -> byref_keep
__Block_byref_id_object_dispose //Block_byref_2 -> byref_destroy
};
*/
在Block
源码中重要就是有以下几个重要的结构体类型 和 函数:
//创建
_Block_copy()
_Block_call_copy_helper()
_Block_object_assign()
//销毁
_Block_release()
_Block_call_dispose_helper()
_Block_object_dispose()
//结构体
Block_layout
Block_descriptor_1
Block_descriptor_2
Block_descriptor_3
Block_byref
Block_byref_2
Block_byref_3
++++++++++++++++++++++end+++++++++++++++++++++++++++
block循环引用问题
循环引用的产生
先上一段代码:
typedef void(^TestBock2)(void);
@interface SecondViewController ()
@property (nonatomic,copy) TestBock2 block;
@property (nonatomic,copy) NSString *name;
@end
@implementation SecondViewController
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
NSLog(@"self.name == %@",self.name);
};
self.block();
}
- (void)dealloc
{
NSLog(@"%s",__func__);
}
当我们这样写完之后其实编译器已经提示警告了:
Capturing 'self' strongly in this block is likely to lead to a retain cycle
这个写法发生了一个循环引用,即当前self
持有block
,block
内部又持有了self
(self -> block -> self
),从而导致该页面无法正常释放而导致内存泄露问题。
解决循环引用
解决这个问题,我们需要做的就是掐断这个循环引用链,要嘛处理掉self
对block
的强引用,要嘛处理掉block
对self
的强引用。
方式一: 使用__weak
来处理掉block
对self
的强引用
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.block = ^{
NSLog(@"self.name == %@",weakSelf.name);
};
self.block();
}
这里利用了__weak
的特性,当__weak
修饰的变量,当指向的对象释放时,该变量自动置nil
。
此时循环引用问题得到了解决,但是这里还是存在一个问题就是,当block
内部如果执行的了一个耗时异步任务,比如下面这样:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
__weak typeof(self) weakSelf = self;
self.block = ^{
//模拟延时任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",weakSelf.name);
});
};
self.block();
}
当我们进入页面,退出时,耗时任务还没执行完,但此时我们已经页面已经dealloc
了,这就导致了这个耗时任务是去了意义。
TestApp[154:9509667] -[SecondViewController dealloc]
TestApp[154:9509667] self.name == (null)
这里就需要我们来延长一下weakSelf
的生命周期到延时任务的block
执行完。通过__strong
来延长生命周期:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
//模拟延时任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",strongSelf.name);
});
};
self.block();
}
此时delloc
会在延时任务执行完之后才触发
TestApp[281:9511903] self.name == zezefamily
TestApp[281:9511903] -[SecondViewController dealloc]
方式二: 上面我们利用了__weak
自动置nil
的特性,我们这里是不是也可以使用手动的方式去置空呐,比如下面这样:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
__block SecondViewController *vc = self;
self.block = ^{
//模拟延时任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",vc.name);
vc = nil;
});
};
self.block();
}
看打印结果:
TestApp[453:9514212] self.name == zezefamily
TestApp[453:9514212] -[SecondViewController dealloc]
其实这个方式的本质跟__weak
是一样的,只不过这里需要我们手动置nil
。
方式三: 以参数的方式传递给block
,作为block
的局部变量,在block
执行完之后释放
typedef void(^TestBock2)(SecondViewController *);
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"zezefamily";
self.block = ^(SecondViewController *vc){
//模拟延时任务
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"self.name == %@",vc.name);
});
};
self.block(self);
}
再看输出:
TestApp[619:9516826] self.name == zezefamily
TestApp[619:9516826] -[SecondViewController dealloc]
同样完美执行了delloc
。
方式四:NSProxy
(待完善)