Block 的本质
问题
1、为什么可以使用 %@ 打印?
因为block 是个对象 结构体
也叫匿名函数
2、Block 自动捕获外界变量
自动生成一个同名的属性来保存。copy一份外界变量进去
3、Block为什么需要 block() 来触发调用
函数申明,具体的函数实现是在需要调用的地方进行调用。
4、__block 的原理
生成相应结构体,保存原始变量 的 指针 和 值。然后传递一个指针地址给 block。block内部生成一个同名属性,进行指针copy,然后在block 内部修改变量的时候,内部生成的变量的指针和外部变量的指针指向的是同一片内存空间,所以在内部修改的时候就修改了外部变量。实现了在block 内部修改外部变量的目的。
验证:
1、为什么可以使用 %@ 打印? (结构体)
创建一个.c文件
#include "stdio.h"
int main(){
__block int a = 10;
void(^block)(void) = ^{
a++;
printf("LG_Cooci - %d",a);
};
block();
return 0;
}
在终端打开当前文件所在文件夹:cd /Users/liujilou/Desktop/Block/Block原理探究
然后输入:
clang -x objective-c -rewrite-objc -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/iPhoneSimulator.platform/Developer/SDKs/iPhoneSimulator.sdk block.c
回车,就会在当前block.c 所在文件夹下就会生成一个 block.cpp 的文件。内容很长,直接拉到最后面去看,对应的就是我们的代码。
int main(){
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
void(*block)(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 *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
//((void (*)())&、(void *) 这些都是强转,可以去掉。然后就成了下面的样子
void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a, 570425344));
后面这是一个构造函数。因为他没有名字,所以又叫匿名函数,代码块。对应的就是我们代码中的block。然后搜索 __main_block_impl_0 。可以看到是一个结构体,这就是block 的本质是一个结构体,所以可以通过%@ 打印。
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;
}
};
3、block 为什么需要调用
构造函数
__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;
}
里面有几个参数:fp 、desc、_a、flags=0 4个参数
fp 传的是 __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)++;
printf("LG_Cooci - %d",(a->__forwarding->a));
}
将一个函数保存在一个属性(FuncPtr)中的写法叫做函数式。在需要的时候随时调用。
impl.FuncPtr = fp; 函数式,在想要调用的地方直接调用。所以block需要调用
下面是对block 的调用。
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
//简化后
block->FuncPtr(block);
//也等于
__main_block_func_0(block);
2、Block 自动捕获外界变量
为了方便看,修改一下.c文件代码
#include "stdio.h"
int main(){
int a = 10;
void(^block)(void) = ^{
printf("LG_Cooci - %d",a);
};
block();
return 0;
}
clang 得到的结果
int main(){
int a = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 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; 传进来了一个 int _a,然后a(_a) 对变量a 进行赋值。
可以知道,block 自动生成了一个属性,来捕获外部变量(通过赋值)
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("LG_Cooci - %d",a);
}
struct __main_block_impl_0 *__cself 就是传过来的block,可以简化为
static void __main_block_func_0(block) {
int a = block->a; // bound by copy
printf("LG_Cooci - %d",a);
}
block内部创建了一个int a 拷贝一份外部变量的int a。两个变量地址是不一样的,所以不能进行修改变量的操作。需要修改的话看下面的分析 __block ;
4、__block 的原理
还是用最初的那个代码clang
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
//简化
__Block_byref_a_0 a =
{(void*)0,
(__Block_byref_a_0 *)&a,
0,
sizeof(__Block_byref_a_0),
10};
//构造函数。相当于是结构体的初始化。
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
//传入值的对照关系
//1、isa (void*)0 看不到
//2、__forwarding &a 相当于 int a = 10 编译器优化;
//3、__flags 0 标记为0
//4、__size sizeof(a)
//5、a 10 a的值
这时候传的参数就和没有 __block 修饰的外部变量不一样了
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));
看第三个参数:
没有__block 修饰 a
__block 修饰 (__Block_byref_a_0 *)&a 传的是一个结构体指针。
在构造函数了创建的属性就不再是 int a,而是__Block_byref_a_0 *a; // by ref
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref 这里进行的是指针拷贝。指向的空间是一样的,所以这时候再进行修改操作的时候,就会改变外部的变量。
(a->__forwarding->a)++;
printf("LG_Cooci - %d",(a->__forwarding->a));
}
二、Block 底层原理
既然说block 是一个对象,那么block 是否有签名呢?
全局block、堆block、栈block又是什么时候进行切换的呢?
1、全局block、堆block、栈block又是什么时候进行切换
我们可以在openSource上找到block的开源源码libclosure。下面来看下源码内容:
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
// 可选
#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_layout 结构体就是block的结构。Block_descriptor_2 和 Block_descriptor_3 是block的可选属性,block中是否存在这两个属性需要由 Block_layout 结构体中的 flags (表示位)属性来决定。
flags的定义如下:
// Values for Block_layout->flags to describe block objects
enum {
BLOCK_DEALLOCATING = (0x0001), // runtime 释放标记 一般常用 BLOCK_NEEDS_FREE 做位与操作,一同传入 flags ,告知该 block 可释放。
BLOCK_REFCOUNT_MASK = (0xfffe), // runtime 存储引用计数的值,是一个可选用参数
BLOCK_NEEDS_FREE = (1 << 24), // runtime 第16是否有效的标志,程序根据它来决定是否增加或减少引用计数位的值
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler 是否拥有拷贝辅助函数 (a copy helper function)决定Block_descriptor_2
BLOCK_HAS_CTOR = (1 << 26), // compiler: helpers have C++ code 是否拥有block c++ 析构函数
BLOCK_IS_GC = (1 << 27), // runtime 标志是否有垃圾回收 OS X
BLOCK_IS_GLOBAL = (1 << 28), // compiler 标志是否是全局 block
BLOCK_USE_STRET = (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE 与 BLOCK_HAS_SIGNATURE相对,判断是否当前 block 拥有一个签名。用于 runtime 时动态调用
BLOCK_HAS_SIGNATURE = (1 << 30), // compiler 是否有签名 决定 Block_descriptor_3
BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31) // compiler 是否有拓展
};
其中的 BLOCK_HAS_COPY_DISPOSE (1 << 25)用来标识是否存在 Block_descriptor_2 ;
BLOCK_HAS_SIGNATURE(1<<30) 用来标识是否存在 Block_descriptor_3;
BLOCK_IS_GLOBAL 用来标识是否存在为 全局的block。
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;
}
如果flags & BLOCK_HAS_COPY_DISPOSE为假,Block_descriptor_2返回为NULL。否则就可以通过 Block_descriptor_1 进行内存偏移访问到 Block_descriptor_2.
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由栈到堆
下面我们使用 Debug -> Debug Workflow -> Always show Disassembly 通过汇编和LLBD进行调试,看下怎么从StackBlock(栈block) 变成 MallocBlock(堆block) 的。我们在block的地方打上断点。
然后打开汇编调试,运行就会定位到汇编代码:
我们看到汇编中有个objc_retainBlock,我们按住control键,然后Step into跳转进去。此时使用LLDB命令,register read x0(读取x0的时候应该使用真机调试),读取到block为GlobalBlock。
然后我们将block代码改为访问外界变量 a 的block。
int a = 10;
void (^block1)(void) = ^{
NSLog(@"LG_Block - %d", a);
};
block1();
然后重新使用汇编调试,跳转到objc_retainBlock,打印x0信息,可以看到此时block变成了StackBlock类型
继续往下走,会走到Block_copy函数,此方法应该是拷贝block的方法,我们在该方法的最下面的return的地方打个断点,跳转到这个地方,然后register read此时的x0(此时的x0即为返回值)。可以看到此时的x0变成了mallocBlock。(堆block)
objc_retainBlock 从全局block -> 堆block
_Block_copy 堆block ->栈block
怎么从全局 -> 堆的,因为在捕获外界变量的时候,会有一个标识 flag BLOKC_IS_GLOBAL
这就是上一篇 iOS底层分析 - Block(一)中所说的
- NSGlobalBlock (全局block )不访问外部变量
- NSStackBlock (栈block ) 堆block copy之前是栈block
- NSMallocBlock (堆block) 访问外部变量的时候
签名
在看clang .cpp的时候,可以发现,在Block_private.h 中声明一些 assign dispose 等的函数和 全局Block和堆block,那接下来就要看看这个 Block_private.h(源码 libclosure-73)
// Runtime copy/destroy helper functions (from Block_private.h)
#ifdef __OBJC_EXPORT_BLOCKS
extern "C" __declspec(dllexport) void _Block_object_assign(void *, const void *, const int);
extern "C" __declspec(dllexport) void _Block_object_dispose(const void *, const int);
extern "C" __declspec(dllexport) void *_NSConcreteGlobalBlock[32];
extern "C" __declspec(dllexport) void *_NSConcreteStackBlock[32];
想要获取签名,通过指针偏移。
签名 signature。位于Block_descriptor_3中
#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可以根据 Block_layout中的flags判断是否存在。下面我们通过汇编来追踪下signature 的内容。
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
我们还可以通过地址偏移来追踪signature的内容。注意用真机调试,打印出的x0才是我们需要的。
(lldb) register read x0
x0 = 0x000000016d62bc88
(lldb) po 0x000000016d62bc88
<__NSStackBlock__: 0x16d62bc88>
(lldb) x/4gx 0x000000016d62bc88
0x16d62bc88: 0x0000000232613a20 0x00000000c0000000
0x16d62bc98: 0x00000001027dfacc 0x00000001027e4228
(lldb) x/4gx 0x00000001027e4228
0x1027e4228: 0x0000000000000000 0x0000000000000024
0x1027e4238: 0x00000001027e34d0 0x00000001027e21fd
(lldb) po (char *)0x00000001027e34d0
"v8@?0"
10进制 1左移25位,换成16进制,然后与(AND)上 打印出来的flag 0xc0000000
得到的结果是0x0 说明没有 Block_descriptor_2
10进制 1左移30位,换成16进制,然后与(AND)上 打印出来的flag 0xc0000000
得到的结果是0x40000000 说明有 Block_descriptor_3
第4位0x00000001027e4228 是 descriptor. x/4gx ,
struct Block_descriptor_1 {
uintptr_t reserved; //第1位
uintptr_t size; //第2位
};
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature; //第3位
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
因为没有 Block_descriptor_2,所以 Block_descriptor_1 后面直接 就是 Block_descriptor_3。第3位就是签名。po (char *)0x00000001027e34d0,得到签名 "v8@?0"。po [NSMethodSignature signatureWithObjCTypes:"v8@?0"] 可以看到block 的签名为 @?
(lldb) po (char *)0x00000001027e34d0
"v8@?0"
(lldb) po [NSMethodSignature signatureWithObjCTypes:"v8@?0"]
<NSMethodSignature: 0x282d3d740>
number of arguments = 1
frame size = 224
is special struct return? NO
return value: -------- -------- -------- --------
type encoding (v) 'v'
flags {}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 0, size adjust = 0}
memory {offset = 0, size = 0}
argument 0: -------- -------- -------- --------
type encoding (@) '@?'
flags {isObject, isBlock}
modifiers {}
frame {offset = 0, offset adjust = 0, size = 8, size adjust = 0}
memory {offset = 0, size = 8}
现在我们知道了Block的签名形式为 @? 的形式,@代表对象,?代表未知的。就是指的block对象。
block进行copy
// 栈 -> 堆 研究拷贝
void *_Block_copy(const void *arg) {
struct Block_layout *aBlock;
。。。
else if (aBlock->flags & BLOCK_IS_GLOBAL) {//是否是全局block
return aBlock;
}
else {//访问外界变量
//在堆区申请一片内存空间
struct Block_layout *result =
(struct Block_layout *)malloc(aBlock->descriptor->size);
if (!result) return NULL;
//将aBlock 原来栈区的block copy到新p开辟的堆区
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;
}
}
copy
invoke;
flags 标志位
isa = _NSConcreteMallocBlock; //堆block
创建一个main.m
int main(int argc, char * argv[]) {
NSString * appDelegateClassName;
@autoreleasepool {
// Setup code that might create autoreleased objects goes here.
appDelegateClassName = NSStringFromClass([AppDelegate class]);
//这个地方要注意,不能直接用__block NSString *lg_name = @"cooci";因为clang的时候会不成功报错,因为clang 并不能与xcode 完美的适配,所以需要使用一下写法。尽量少用字面量。
__block NSString *lg_name = [NSString stringWithFormat:@"cooci"];
void (^block1)(void) = ^{ // block_copy
lg_name = @"LG_Cooci";
NSLog(@"LG_Block - %@",lg_name);
};
block1();
}
return UIApplicationMain(argc, argv, nil, appDelegateClassName);
}
终端打开到 main.m 文件目录下,使用clang命令
xcrun xcrun -sdk iphonesimulator clang -rewrite-objc main.m
就会在这个目录下生成一个 main.cpp文件。
查看.cpp 文件,很长直接跳到最后面看。其中copy相关的函数
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);}
这个函数又调用了_Block_object_assign函数(对捕获变量的内存管理)。我们到closure (runtime.cpp)源码中查找这个函数
//判断自动捕获的外界变量 也就是main.m 中的 lg_name
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://判断是否是object类型
_Block_retain_object(object);
*dest = object;
break;
case BLOCK_FIELD_IS_BLOCK://判断是否是block类型
*dest = _Block_copy(object);
break;
// 是否是__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_FIELD_IS_BLOCK = 7, //block变量 a block variable
BLOCK_FIELD_IS_BYREF = 8, //__block 修饰的结构体 the on stack structure holding the __block variable
BLOCK_FIELD_IS_WEAK = 16, //__weak 修饰的变量 declared __weak, only used in byref copy helpers
BLOCK_BYREF_CALLER = 128, //处理Block_byref内部对象内存的时候会加的一个额外标记,配合上面的枚举一起使用 called from __block (byref) copy/dispose support routines.
};
switch判断,
如果是 BLOCK_FIELD_IS_BLOCK,也就是对象类型,调用了_Block_retain_object,但是该方法中什么都没有做,因为如果是对象类型的话会交给ARC来处理。
如果是 BLOCK_FIELD_IS_BYREF 类型的话,也就是__block修饰的结构体变量,调用了_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
//通过malloc创建一个相同大小的 copy
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 的和原来的指向同一个内存空间,这样就实现了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 修饰变量 block具有修改能力
//将copy 的和原来的指向同一个内存空间,这样就实现了block 修改外部变量的能力
copy->forwarding = copy; // patch heap copy to point to itself
src->forwarding = copy; // patch stack to point to heap copy
原来的__block结构体变量 和 拷贝的新的 __block结构体变量,都指向了copy的对象。这样就block 内部就有了修改外部变量的能力。因为他们都指向了同一块堆区的地址。
在堆__block结构体进行拷贝的方法中调用了下面的这句代码,从而完成了对__block结构体中变量进行了拷贝。
(*src2->byref_keep)(copy, src);
src中的byref_keep函数定义如下
void(*BlockByrefKeepFunction)(struct Block_byref*, struct Block_byref*);
src2的类型是Block_byref结构体。他的定义类型与Block结构体的定义类似,下面我们找打它的byref_keep函数位于Block_byref_2中,也就是第5个参数。
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;
};
我们到clang生成的cpp中找到对应的Block_byref_lg_name_0的第5个参数,即__Block_byref_id_object_copy_131。
int main(int argc, char * argv[]) {
......
__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, == keep
__Block_byref_id_object_dispose_131,
......
};
......
}
__Block_byref_id_object_copy_131``的定义如下,它又调用了我们上面分析的_Block_object_assign,传递的参数通过地址偏移找到了__block结构体中保存的外界___block变量。然后进行拷贝操作。最终完成了__block变量的修改。
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
这里为什么要 +40 呢?内存偏移,找到 __Block_byref_lg_name_0,可以知道,如果想要访问到lg_name,需要偏移40 字节。然后找到了lg_name 对象,进行内存copy。到此三层kcopy 完成。
struct __Block_byref_lg_name_0 {
void *__isa; //8
__Block_byref_lg_name_0 *__forwarding; //8
int __flags; //4
int __size; //4
void (*__Block_byref_id_object_copy)(void*, void*); //8
void (*__Block_byref_id_object_dispose)(void*); //8
NSString *lg_name;
};
三层copy
1、block()结构体自身的copy
2、__Block_byref_lg_name_0 生成一个堆区的内存空间
3、__Block_byref_id_object_copy_131 __block 修饰的变量
总结:
Block的三层拷贝:
- Block结构体的拷贝;
- __block结构体的拷贝;
- __block结构体中的__block变量的拷贝 ;
上面讲述了block的copy过程,block的释放过程和拷贝过程类似,也是一层一层的去释放。