欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2024-03-20 13:21:46
...

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的地方打上断点。
 23、iOS底层分析 - Block(二)
 然后打开汇编调试,运行就会定位到汇编代码:
23、iOS底层分析 - Block(二)
 我们看到汇编中有个objc_retainBlock,我们按住control键,然后Step into跳转进去。此时使用LLDB命令,register read x0(读取x0的时候应该使用真机调试),读取到block为GlobalBlock。

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


 然后我们将block代码改为访问外界变量 a 的block。

int a = 10;
void (^block1)(void) = ^{
    NSLog(@"LG_Block - %d", a);
};
block1();

然后重新使用汇编调试,跳转到objc_retainBlock,打印x0信息,可以看到此时block变成了StackBlock类型

23、iOS底层分析 - Block(二)
 继续往下走,会走到Block_copy函数,此方法应该是拷贝block的方法,我们在该方法的最下面的return的地方打个断点,跳转到这个地方,然后register read此时的x0(此时的x0即为返回值)。可以看到此时的x0变成了mallocBlock。(堆block)
 23、iOS底层分析 - 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
 "aaa@qq.com?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,得到签名  "aaa@qq.com?0"。po [NSMethodSignature signatureWithObjCTypes:"aaa@qq.com?0"] 可以看到block 的签名为  @?

 (lldb) po (char *)0x00000001027e34d0
 "aaa@qq.com?0"

 (lldb) po [NSMethodSignature signatureWithObjCTypes:"aaa@qq.com?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的释放过程和拷贝过程类似,也是一层一层的去释放。
 

 

相关标签: iOS底层分析