iOS-Block源码分析

前言

iOS-深入研究Block这篇文章结合实例介绍了Block的类型,循环引用等问题,接着我们通过Block的源码分析一下,它的底层是怎么操作的?

1 通过Clang分析Block

Block通过Clang将会编译成什么样的结构呢,它的invokeisa,签名的原理是什么,我们来研究下。

#include "stdio.h"

int main(){

//    __Block_byref_a_0 int a = 18;
    int a = 8;
    void(^block)(void) = ^{

//        a++;
        printf("ro_robert - %d",a);
    };
    
     block();
    return 0;
}

我们通过xcrun -sdk iphonesimulator clang -S -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.5 block.c命令执行一个得到block.cpp文件.
我们打开block.cpp文件,找到main函数,如下

int main(){
    int a = 8;
    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;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

从上可以看出,我们要研究的 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));*是这行代码。
我们经过调整下,如下

 void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));

void(*block)(void) 这是一个函数指针,
这里的__main_block_impl_0就是一个函数调用。

  ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);

把这行也调下,如下所示

 block->FuncPtr(block);

我们先看下__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;
  }
};

这是一个结构体,

 void(*block)(void) = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, a));

就是这个结构体的构造函数,传了三个参数,其中有一个我们传的参数是a,这个__main_block_impl_0这个结构体中也有一个变量a,他们有什么关系呢,我们来看下。
我们在block.c中把这个变量a去掉,看下是什么效果。

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_impl_0结构体,我们发现这里的没有a这个变量了,它的构造函数同样也没有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;
  }

这里: a(_a) 是C++语法,默认会对传过来的参数a赋值,_a会传给a,说白了就是赋值操作,block在底层会把变量捕获进来,变成自己的成员变量。
我们通过真实的App代码,如下

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *objc = [NSObject alloc];

    __block NSObject *objc1 = [NSObject alloc];
    void (^block1)(void) = ^{
        NSLog(@"ro_Block %@ ",objc1);
    };
    block1();
}

我们打开ViewController.cpp文件,找到__ViewController__viewDidLoad_block_impl_0这个结构体,如下

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  NSObject *__strong objc1;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, NSObject *__strong _objc1, int flags=0) : objc1(_objc1) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

这里面有一个NSObject *__strong objc1;对象,这说明我们的Block在捕获取变量时会生成相应的成员变量。
在编译价段,impl.isa = &_NSConcreteStackBlock;这里是一个stackBlock;。
之前我们介绍过,捕获局部或者属性变量,又不是弱引用,应该是这个MallocBlock,为什么这里是StatckBlock,我们来看下。

在这里我们是在编译时是StackBlock,程序还要经过运行时,才会变成堆,是如何变成堆呢,需要我们再研究下。
这里有一个fp参数,是第一个参数,经过查找,它是__ViewController__viewDidLoad_block_func_0,而它又是这个

static void __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself) {
  NSObject *__strong objc1 = __cself->objc1; // bound by copy

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_r7_rnm6hs1x2jg8bqy7sjwsskx00000gn_T_ViewController_2b1512_mi_0,objc1);
    }

函数,就是传一个函数过去,之后进行了FuncPtr的执行,就是去执行__ViewController__viewDidLoad_block_func_0这个函数,就是运行起来。
block函数式保存,如果不调用执行,永远也没可能执行它的功能逻辑。
我们切换到block.c代码中,为了防止干扰。

#include "stdio.h"

int main(){

//    __Block_byref_a_0 int a = 18;
    int a = 18;
    void(^block)(void) = ^{

//        a++;
        printf("ro_robert - %d",a);
//        printf("ro_robert");

    };
    
     block();
    return 0;
}

通过命令编成cpp,我们看下__main_block_func_0

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int a = __cself->a; // bound by copy
   printf("ro_robert - %d",a);

    }

这里csef就是传过来的就是自己,
int a = __cself->a;这里的int a__cself->a,就是赋值操作,内容相同,但是地址不同,说白就是值拷贝。
接下来,我们把int a加上__block看下效果,代码如下

#include "stdio.h"

int main(){

//    __Block_byref_a_0 int a = 18;
    __block int a = 18;
    void(^block)(void) = ^{

        a++;
        printf("ro_robert - %d",a);
//        printf("ro_robert");

    };
    
     block();
    return 0;
}

再次编译,我们再分析下__block做了什么操作?
我们看下__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;
  }
};

我们发现int a 变成 *__Block_byref_a_0 a;
我们再下main函数

int main(){


    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 18};
    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;
}

经过整理,如下

int main(){


    __Block_byref_a_0 a = {(void*)0,
        (__Block_byref_a_0 *)&a,
        0,
        sizeof(__Block_byref_a_0),
        18};
    
    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;
}

这里__Block_byref_a_0结构体初始化,* (__Block_byref_a_0 )&a,这里取a的地址。

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

这里是__Block_byref_a_0这个结构体,__forwarding这里指向a的地址,在这个__main_block_impl_0结构体中把__forwarding传给了参数a,在__main_block_func_0这个函数中

 __Block_byref_a_0 *a = __cself->a;

这里的__cself->a;与外界的a是一样的,指向同一块内存区域,说明的就是指针拷贝。
__block生成的__Block_byref_a_0这样的结构体,传给的block的是指针地址,这就是为什么加了__block可以修改外部变量。

2 Block的签名和copy过程

我们在block.cpp文件发现__main_block_copy_0__main_block_dispose_0__main_block_desc_0这些结构体,他们是干什么用的呢,我们来分析下block的copy过程。
我们通过断点,分析下汇编流程,调试代码如下

- (void)viewDidLoad {
    [super viewDidLoad];
    NSObject *objc = [NSObject alloc];

    NSObject *objc1 = [NSObject alloc];
    void (^block1)(void) = ^{
        NSLog(@"robert_Block %@ ",objc1);
    };
    block1();
    
}

我们在block打断点,调试,如下

1

这里调用了objc_retainBlock函数,走进这个函数(也可以通过符号断点),发现调用了_Block_copy这个函数,通过符号断点,是在libsystem_blocks.dylib中,这个是没有开源的,我们找替找工程libclosure(这里用79版本),可以查看到,我们看下它的源码,如下

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) {
        return aBlock;
    }
    else {// 栈 - 堆 (编译期)
        // Its a stack block.  Make a copy.
        size_t size = Block_size(aBlock);
        struct Block_layout *result = (struct Block_layout *)malloc(size);
        if (!result) return NULL;
        memmove(result, aBlock, size); // bitcopy first
#if __has_feature(ptrauth_calls)
        // Resign the invoke pointer as it uses address authentication.
        result->invoke = aBlock->invoke;

#if __has_feature(ptrauth_signed_block_descriptors)
        if (aBlock->flags & BLOCK_SMALL_DESCRIPTOR) {
            uintptr_t oldDesc = ptrauth_blend_discriminator(
                    &aBlock->descriptor,
                    _Block_descriptor_ptrauth_discriminator);
            uintptr_t newDesc = ptrauth_blend_discriminator(
                    &result->descriptor,
                    _Block_descriptor_ptrauth_discriminator);

            result->descriptor =
                    ptrauth_auth_and_resign(aBlock->descriptor,
                                            ptrauth_key_asda, oldDesc,
                                            ptrauth_key_asda, newDesc);
        }
#endif
#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;
    }
}

我们知道Block是一个结构体,struct Block_layout *aBlock;Block_layout类型的,它的源码如下

struct Block_layout {
    void * __ptrauth_objc_isa_pointer isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
  • 这里面有isa指针
  • flags标识
  • invoke调用函数
  • descriptor其它相关描述,是否正在析构等。
    这个flags标识符的定义有
// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime 正在析构
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime 掩码
    BLOCK_INLINE_LAYOUT_STRING = (1 << 21), // compiler

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    BLOCK_SMALL_DESCRIPTOR =  (1 << 22), // compiler
#endif

    BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
    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_copy符号断点,然后通命令查看,如图

2

这是一个全局block。我们再改下代码,让这个block捕获外部变量,如图
3

这是一个stackBlock,这个block应该是MallocBlock,因为这里还没有经过copy操作,当我们执行完copy操作后,如图
4

这里成为了MallocBlock,我们再看上面_Block_copy的源码,

*aBlock = (struct Block_layout *)arg;*

这里转换Block_layout类型。

 if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }

这是是引用计数据相关处理。

else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }

如果是GlobalBlock直接返回。
经过编译期过来的是不能生成堆的,当发现是一个stackBlock,又捕获了外界变量,

size_t  size = Block_size(aBlock);
struct Block_layout *result = (struct Block_layout *)malloc(size);

这里就会根据原来的大小,就会开辟一段内存空间,然后把原始的数据拷贝到新的Block_layout中,isa指针标记为_NSConcreteMallocBlock,这里就变成了堆Block。

上图中所示的invoke是函数调用者,signatrue就是Block的签名。
signatrue的解释 v8@?0 v代表返回值,8代表8字节, @?代表Block类型,0代表从0号位置开始

3 Blocklayout的结构

当我们invoke的时候,这个消息会失效或有问题,会进入消息转发流程,在最后慢速转发流程时,必须要获取签名才能进行invocation.
我再次看下blocklayout的结构体

struct Block_layout {
    void * __ptrauth_objc_isa_pointer isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

我们看下descriptor这个类型,如下

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved; 
    uintptr_t size; // 大小
};

descriptor是可选参数,内存连续可选,因为我们的类型不一样,所以结构也不一样,因为有stackBlock,MallocBlock,GlobalBlock的区分,每个类型的结构体不一样,通过标识符判断。

我们在源码找到

#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
};

这些结构体,这些都是通过标识符号来判断。

static struct Block_descriptor_2 * _Block_descriptor_2(struct Block_layout *aBlock)
{
    uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
    desc += sizeof(struct Block_descriptor_1);
    return (struct Block_descriptor_2 *)desc;
}

这里通过内存平移来获取到Block_descriptor_2,我们分析下。

static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
    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_descriptor_2,有的话,再加上Block_descriptor_2的大小就是Block_descriptor_3的起始位置。

我们看下Block_descriptor_3的结构,如

struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature;
    const char *layout;     // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};

这里有signature,只要有signature的打印,就说明肯定有Block_descriptor_3,我们可以通过x/6gx命令,查看内存。

4 Block的捕获变量生命周期

Block捕获变量时都做了哪些操作,_Block_copy这个函数做了什么?带着这些疑问,我们继续分析底层原理。
我们先看下ViewController.cpp文件,在文件中搜索__ViewController__viewDidLoad_block_desc_0,这个函数,源码如下

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};

这里copy和dispose两个函数,__ViewController__viewDidLoad_block_dispose_0这个就是 Block_descriptor_2中的dispose__ViewController__viewDidLoad_block_copy_0_Block_copy函数的指针,它做了什么事情呢,我们来看下,__ViewController__viewDidLoad_block_copy_0这个函数里面会调用_Block_object_assign这个函数,相当于copy函数。
我们继续分析
_Block_object_assign,看看它的操作流程, 我们在源码中搜下_Block_object_assign*这个函数,如下

The flags parameter of _Block_object_assign and _Block_object_dispose is set to
    * BLOCK_FIELD_IS_OBJECT (3), for the case of an Objective-C Object, // 普通object对象,未使用__block修饰的
    * BLOCK_FIELD_IS_BLOCK (7), for the case of another Block, and
    * BLOCK_FIELD_IS_BYREF (8), for the case of a __block variable.对应__block修饰
If the __block variable is marked weak the compiler also or's in BLOCK_FIELD_IS_WEAK (16)

这里是三个标识符号,捕获变量的判断和相应处理。我们看下_Block_object_assign函数的源码,如

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_default = fn (arc)
        _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;
    }
}

如果是BLOCK_FIELD_IS_OBJECT普通对象类型,执行

 _Block_retain_object(object);
 *dest = object;

_Block_retain_object调用了_Block_retain_object_default,这个函数空实现,默认交给系统级别的ARC操作。
对象账值给目标dest,具备相同的内存空间。
BLOCK_FIELD_IS_BLOCK Block类型,执行

*dest = _Block_copy(object);

_Block_copy调用。
BLOCK_FIELD_IS_BYREF类型,执行

 *dest = _Block_byref_copy(object);

_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
        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;
        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;
}
  • if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) 引用计数相关的处理,
    arc自己找。
    struct Block_byref *copy = (struct Block_byref )malloc(src->size);这里copy一份
    copy->isa = NULL;为空。
    copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4; 标识符赋值
    copy->forwarding = copy; forwarding拷贝一份
    src->forwarding = copy;copy赋给原始的forwarding,这说明原来的
    forwardingcopy后的forwarding是同一个,都是栈到堆,forwarding传给了objc1这个对象。
    if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE)这里判断
    BLOCK_BYREF_HAS_COPY_DISPOSE*被捕获的变量进到这个判断里面。
    接着执行
copy2->byref_keep = src2->byref_keep;
copy2->byref_destroy = src2->byref_destroy;

原始对象与copy对象一样。

(*src2->byref_keep)(copy, src);

这里byref_keep进行调用,我们捕获取外界的变量,对它进行相关内存的处理,赋值操作,我们看下byref_keep源码

struct Block_byref_2 {
    // requires BLOCK_BYREF_HAS_COPY_DISPOSE
    BlockByrefKeepFunction byref_keep; 
    BlockByrefDestroyFunction byref_destroy;
};

对外界变量生命周期的保存,如果外界的变量变为nil了,block内部的变量也成为nil了。
byref_keep相当于copy函数,
byref_destroy相当于dipose函数
最后调用的就是_Block_object_assign这个函数,在ViewController.cpp中搜下这个函数,找到如下代码

static void __Block_byref_id_object_copy_131(void *dst, void *src) {
 _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}

dst + 40就是copy,我们内部的变量,也是Block_byref类型,占用24字节,Block_byref_2占用16字节,刚好这里就是Block_byref_3这个结构体

struct Block_byref_3 {
    // requires BLOCK_BYREF_LAYOUT_EXTENDED
    const char *layout;
};

这是Block_byref_3的结构体,这里相当于传的是object1这个对象。
__block修饰的变量流程

  • copy,从栈拷贝到栈上
  • block捕获变量 _Block_byref类型
  • 对_Block_byref对象进行一次copy操作
  • 针对_Block_byref里面修饰的object进行copy操作
  • byref_keep调用这个函数把里面的对象保存一下
  • 释放调用_Block_object_dispose这个函数
void _Block_object_dispose(const void *object, const int flags) {
    switch (os_assumes(flags & BLOCK_ALL_COPY_DISPOSE_FLAGS)) {
      case BLOCK_FIELD_IS_BYREF | BLOCK_FIELD_IS_WEAK:
      case BLOCK_FIELD_IS_BYREF:
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);
        break;
      case BLOCK_FIELD_IS_BLOCK:
        _Block_release(object);
        break;
      case BLOCK_FIELD_IS_OBJECT:
        _Block_release_object(object);
        break;
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT | BLOCK_FIELD_IS_WEAK:
      case BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_BLOCK  | BLOCK_FIELD_IS_WEAK:
        break;
      default:
        break;
    }
}

这里_Block_object_dispose函数的源码

  • BLOCK_FIELD_IS_OBJECT普通对象,执行
_Block_release_object(object);

调用_Block_release_object_default这个函数,空实现

  • BLOCK_FIELD_IS_BLOCK如果是Block类型,执行
_Block_release(object);

_Block_release的源码

void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    if (! (aBlock->flags & BLOCK_NEEDS_FREE)) return;

    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

_Block_destructInstance销毁实例对象, free(aBlock);释放block。

  • BLOCK_FIELD_IS_BYREF byref类型(被_block捕获的类型),执行
_Block_byref_release(object);

_Block_byref_release的源码

static void _Block_byref_release(const void *arg) {
    struct Block_byref *byref = (struct Block_byref *)arg;

    // dereference the forwarding pointer since the compiler isn't doing this anymore (ever?)
    byref = byref->forwarding;
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {
        int32_t refcount = byref->flags & BLOCK_REFCOUNT_MASK;
        os_assert(refcount);
        if (latching_decr_int_should_deallocate(&byref->flags)) {
            if (byref->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                (*byref2->byref_destroy)(byref);
            }
            free(byref);
        }
    }
}

这里调用了byref_destroy这个函数,free(byref);释放byref这个变量,byref里面的对象也会释放。

else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
latching_incr_int(&src->forwarding->flags);
}如果不是正常对象 latching_incr_int执行这个函数,自己处理。

总结

这篇文章我们通过源码分析了Block的底层是如何工作的,它的copy流程,释放流程,捕获对象做了比较详细的分析。通过这篇文章我们对Block的底层有了很深层次的认识,有疑问,欢迎大家随时来交流学习。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容