23、iOS底层分析 - Block(二)

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(一)中所说的

  1. NSGlobalBlock (全局block )不访问外部变量 
  2. NSStackBlock  (栈block )     堆block  copy之前是栈block
  3. 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的三层拷贝:

  1. Block结构体的拷贝;
  2. __block结构体的拷贝;
  3. __block结构体中的__block变量的拷贝 ;

 上面讲述了block的copy过程,block的释放过程和拷贝过程类似,也是一层一层的去释放。
 

发布了83 篇原创文章 · 获赞 12 · 访问量 18万+

猜你喜欢

转载自blog.csdn.net/shengdaVolleyball/article/details/104786835