iOS进阶笔记(四) Block - 原理篇

📣 iOS进阶笔记目录


本章目录

一、BlocK对象本质分析
1、通过clang分析block结构
2、通过LLVM开源的block源码分析block结构
3、根据Block本质及变量捕获原理,通过Objective-C++实现block及变量捕获过程
4、小结

二、Block类型分析
1、Block类型及继承链
2、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock所处内存区域分析
3、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock的Copy操作分析
4、分别位于栈和堆上的Block与__block变量
5、小结

三、Block捕获变量及相应内存管理分析
1、全局变量
2、static变量
3、auto变量
    1)值类型变量捕获
    2)ObjC对象类型变量捕获
    3)汇编查看值类型和对象类型区别
4、修改auto变量值的异常问题
    1)在block块后面修改auto变量值
    2)block块内部修改外部auto变量值,Xcode编译器报错分析
5、小结

四、__block修饰的auto变量的copy及dispose分析
1、__block修饰值类型变量
2、__block修饰对象类型变量
3、__block修饰__weak对象类型变量
4、__forwarding指针的用意
5、小结

五、Block的方法签名探索
1、普通Objc对象的Signature
2、Block的方法签名分析
    1)从Block源码角度分析
    2)从汇编角度分析

六、附录:Block相关源码解读
1、struct Block_layout
2、Block_layout的flags
3、descriptor
4、signature
5、Block的copy和dispose的参数
6、_Block_object_assign
7、_Block_copy
8、_Block_byref_copy
9、_Block_release
10、_Block_byref_release


一、BlocK对象本质分析

1、通过clang分析block结构

以下面这段代码为例:(未使用__block的情况)

int a = 1;
void (^block)(void) = ^{
    NSLog(@"a %d",a);// 结果 1
};
block();

clang编译后如下:clang -rewrite-objc main.m -o main.cpp

// block本质结构(定义)
 struct __block_impl {
      void *isa;// block内部也有isa指针,无疑是对象了
      int Flags;// 标识,用来作标识符的(包括引用计数信息),类似于isa中的位域,按bit位表示一些block的附加信息
      int Reserved;// 系统保留位,用于储存block内部变量信息
      void *FuncPtr;// 函数指针,指向了封装block块内部代码的函数
};

// block实现
struct __Block_impl_0 {
      struct __block_impl impl; // block实现
      struct __Block_desc_0* Desc;  // block的描述信息
      int a ;// block 捕获的外部变量

       // 结构体构造函数
       __Block_impl_0(void *fp, struct __Block_desc_0 *desc, int _a, int flags=0) : a(_a) {
          impl.isa = &_NSConcreteStackBlock;// 栈block
          impl.Flags = flags;
          impl.FuncPtr = fp;// 函数指针
          Desc = desc;// 描述
       }
};

// 封装了block块内部代码的函数(即block要执行的内容)
static void __block_func_0(struct __block_impl_0 *__cself) {
    // 通过__cself指针传入a,赋值给了一个与外部变量a同名的内部变量a
    int a = __cself->a; // bound by copy
}

// block描述信息
static struct __block_desc_0 {
    size_t reserved;// 系统保留字段
    size_t Block_size;// block大小
} __block_desc_0_DATA = { 0, sizeof(struct __block_impl_0)};// 这里直接进行了结构体初始化

int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { 
    __AtAutoreleasePool __autoreleasepool; 
        int a = 1;
        
        void (*block)(void) = ((void (*)())&__block_impl_0((void *)__block_func_0, &__block_desc_0_DATA, a));

        ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    }
    return 0;
}

  • __block_impl

    通过clang可以看出Block本质为__block_impl结构体(Objc对象),包含了指向其类型的isa指针、Flags(用来作标识符)、Reserved(系统保留位)及FuncPtr(封装block块内部代码的函数的指针)。

  • __Block_impl_0

    __Block_impl_0结构体为是对Block的实现,是对其进一步封装。内部包含了Block、Block的描述信息及捕获的auto变量。

  • __block_func_0

    该函数内容正是Block块真正执行的内容。在main函数中通过初始化__Block_impl_0结构体构造函数与__Block_impl_0->FunPtr进行绑定。

  • __block_desc_0

    保存Block的描述信息的结构体,如Block_size。

  • main函数

    ① __autoreleasepool

    创建自动释放池对象,通过objc_autoreleasePoolPush()和objc_autoreleasePoolPop函数维护AutoreleasePoolPage。

    ② __Block_impl_0

    void (block)(void) = ((void ()())&__Block_impl_0((void *)__block_func_0, &__block_desc_0_DATA, a))

    • 定义一个名为block的函数指针,并指向__Block_impl_0结构体构地址。
    • 第一个参数:block结构体。 在此过程中将__Block_func_0函数地址,通过__Block_impl_0结构体构造函数,传给了__Block_impl->FuncPtr。
    • 第二个参数:block描述信息的结构体。将__Block_desc_0_DATA(包含Block_size信息)传给__Block_impl_0->Desc。
    • 第三个参数:捕获的外部变量a

    Tips:这里clang翻译的C++代码,直接编译会报错,原因是__block_impl_0返回的是一个临时地址 (Taking the address of a temporary object of type '__block_impl_0')。本节下文会讲到解决办法


    ③block的调用

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

    • 这里由于__Block_impl__Block_impl_0结构体的第一成员,所以__Block_impl_0__Block_impl地址相同,因此可以将block强转为__Block_impl类型。

    • 由于block->FuncPtr指向了__Block_func_0函数,此时block->FuncPtr类型为void*,需要强转为((void (*)(__Block_impl *, int))类型的函数指针,最后再调用__Block_func_0函数。

    • 以上可简写为:block->FuncPtr(block); 可以看出此过程实际是函数指针调用,执行block所指向函数。FuncPtr函数指针指向了__block_func_0函数,参数为block自身,以便__block_func_0函数内部访问block捕获的外部变量。


  • 小结:

通过clang可以知,block结构本质Objc对象的本质一致。Block结构本质为struct __block_impl结构体,其主要成员成员为isa和函数指针FuncPtr(指向了封装block块内部代码的函数)。


2、通过LLVM开源的block源码分析block结构


  • Block源码中block的结构

    struct Block_layout {
        // isa指针,通过isa可以找到block的类型
        void * __ptrauth_objc_isa_pointer isa;
        
        // 用来作标识符的(包括引用计数信息),类似于isa中的位域,按bit位表示一些block的附加信息
        volatile int32_t flags; // contains ref count
        
        // 保留信息,用于储存block内部变量信息
        int32_t reserved;
        
        // 函数指针,指向包装了block所执行代码块的函数地址
        // BlockInvokeFunction定义:typedef void(*BlockInvokeFunction)(void *, ...);
         BlockInvokeFunction invoke;
        
        // block描述信息( blockSize等信息)
         struct Block_descriptor_1 *descriptor;
        
        // 捕获的外部变量
    };
    

    对比block源码和clang结果,发现二者对block结构描述很相似。


3、根据Block本质及变量捕获原理,通过Objective-C++实现block及变量捕获过程

Objective-C代码:

@interface GGPerson : NSObject
@property (nonatomic, assign) int age;
@end
@implementation GGPerson
@end

void test()
{
    __block GGPerson *p = [[GGPerson alloc] init];
    p.age = 1;
    
    void (^block)(void) = ^{
        NSLog(@"age:%d",p.age);
    };
    // block调用
    block();
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
    }
    return 0;
}


Objective-C++ 代码:

#import <Foundation/Foundation.h>
#import <objc/runtime.h>
#import <objc/message.h>

@interface GGPerson : NSObject
@property (nonatomic, assign) int age;
@end

@implementation GGPerson

@end

#define __OFFSETOFIVAR__(TYPE, MEMBER) ((long long) &((TYPE *)0)->MEMBER)
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

// block对象本质
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// 堆
struct __Block_byref_p_0 {
    void *__isa;
    __Block_byref_p_0 *__forwarding;
    int __flags;
    int __size;
    // 此处对p为强引用,所以需要对处于堆上的p对象进行内存管理(copy和dispose)
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    GGPerson *__strong p;
};

// 从栈拷贝到堆的block
struct __test_block_impl_0 {
    struct __block_impl impl;
    struct __test_block_desc_0* Desc;
    __Block_byref_p_0 *p; // by ref
    
    __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_p_0 *_p, int flags=0) : p(_p->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
    __Block_byref_p_0 *p = __cself->p; // bound by ref
    int age = p->__forwarding->p.age;
    NSLog(@"age->%d",age);
    
//    NSLog((NSString *)&__NSConstantStringImpl__var_folders_75_mphjrxws47b6srgd_j5ypg000000gp_T_ab4144_mi_0,((int (*)(id, SEL))(void *)objc_msgSend)((id)(p->__forwarding->p), sel_registerName("age")));
}

static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {
    _Block_object_assign((void*)&dst->p, (void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __test_block_dispose_0(struct __test_block_impl_0*src) {
    _Block_object_dispose((void*)src->p, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __test_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    // 此处block对象(即__test_block_impl_0)处于堆上,所有需要对其进行相应的内存管理(copy和dispose)
    void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
    void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
    0,
    sizeof(struct __test_block_impl_0),
    __test_block_copy_0,
    __test_block_dispose_0
};

void test()
{
    // __block把p对象包装成__Block_byref_p_0类型结构体,初始化的结构如下
    __attribute__((__blocks__(byref))) __Block_byref_p_0 p = {
        (void*)0,// __isa
        (__Block_byref_p_0 *)&p,// __forwarding
        33554432,//__flags
        sizeof(__Block_byref_p_0),// __size
        __Block_byref_id_object_copy_131,// copy
        __Block_byref_id_object_dispose_131,// dispose
        
        // 实例化GGPerson对象,返回 GGPerson* __strong 类型指针p
        ((GGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)((GGPerson *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("GGPerson"),sel_registerName("alloc")), sel_registerName("init"))
    };
    
    // age = 1,向person对象发送setAge:消息
    ((void (*)(id, SEL, int))(void *)objc_msgSend)((id)(p.__forwarding->p), sel_registerName("setAge:"), 1);
    
    // 可以用临时变量impl_0接收函数返回值
    __test_block_impl_0 impl_0 = __test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_p_0 *)&p, 570425344);
    printf("%14p impl_0地址\n",&impl_0);
    
    // 再用指针指向impl_0地址,以达到访问__test_block_impl_0内部成员的目的
    __test_block_impl_0* impl_0_ptr = &impl_0;
    
    // 声明一个block指针,指向block的结构内存
    void (*block)(void) = (void (*)(void))impl_0_ptr;

    // 函数指针调用,执行__test_block_func_0函数
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        test();
    }
    return 0;
}

4、小结




(Block内存结构图 来源:Matt Galloway


Block本质就是Objc对象(结构体),它包装了以下内容:

  • 表示其类型,指向其父类的isa指针;

  • 标识block一些附件信息(包括引用计数)的flags

  • 用于储存block内部变量信息的reserved

  • 指向包装了block所执行代码块的函数地址的指针invoke

  • block描述信息Block_descriptor_1(根据具体情况可能还包含Block_descriptor_2(block捕获auto变量内存管理的descriptor)、Block_descriptor_3(block签名的descriptor)信息。Block_descriptor_2Block_descriptor_3是由Block_descriptor_1通过内存平移得到);

  • 捕获的外部变量。





二、Block类型分析


1、Block类型及继承链


Block类型共有6种:

类型 存储区域 源码定义
_NSConcreteGlobalBlock 全局变量/静态变量区

源码文件: Block.h
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32]

_NSConcreteStackBlock 栈区

源码文件: Block.h
BLOCK_EXPORT void * _NSConcreteStackBlock[32]

_NSConcreteMallocBlock 堆区

源码文件: Block_private.h
BLOCK_EXPORT void * _NSConcreteMallocBlock[32]

_NSConcreteAutoBlock 堆区

源码文件: Block_private.h
BLOCK_EXPORT void * _NSConcreteAutoBlock[32]

_NSConcreteFinalizingBlock 堆区

源码文件: Block_private.h
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32]

_NSConcreteWeakBlockVariable 堆区

源码文件: Block_private.h
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32]

注:其中后三种类型的Block,是系统级别属于ARC和GC范畴。接下来我们就研究前三种类型的Block(即:__NSGlobalBlock/__NSStackBlock/__NSMallocBlock)。


另外,以上类型的block均属于编译器层调用,我们不能直接使用它。在Block.h文件中,你可以看到这段注解

// Used by the compiler. Do not use these variables yourself



  • __NSGlobalBlock

没有访问外部auto变量,或访问了外部static或者全局变量类型的Block为__NSGlobalBlock,此种情况不会被block捕获。


代码验证:

1)没有访问auto变量的Block,也没访问static或者全局变量情况

void globalBlockTest(){
    void (^block)(id) = ^(id objc){
        NSLog(@"The Block type is %@",objc);// 输出类型为:__NSGlobalBlock__
    };
    block(object_getClass(block));
}

2)没有访问auto变量的Block,但访问了static或者全局变量情况

int a = 10;// 全局变量
void globalBlockTest(){
    static int b = 20;// 静态变量
    void (^block)(id) = ^(id objc){
        NSLog(@"%d",a+b);// block访问了全局变量和静态变量
        NSLog(@"The Block type is %@",objc);// 输出类型为:__NSGlobalBlock__
    };
    block(object_getClass(block));
}

  • __NSStackBlock

MRC环境,访问了auto变量类型的block为__NSStackBlock。

void stackBlockTest(){
    int a = 10;// auto 变量a
    void (^block)(id) = ^(id objc){
        NSLog(@"访问了auto变量a:%d block类型为:%@",a,objc);
    };
    block(object_getClass(block));
}

同样上述代码,ARC环境输出block类型为__NSMallocBlock。

原因:在ARC环境下,会自动为__NSStackBlock类型的block进行copy操作(由栈拷贝到堆)。目的是栈类型的Block生命周期为当前所处的作用域,防止其出了作用域再次访问会造成异常错误结果。在执行完copy操作后,将堆block的isa设置成了_NSConcreteMallocBlock(详见_Block_copy)。关于栈block拷贝到堆的相关过程请查看分别位于栈和堆上的Block与__block变量


验证这一说法:

MRC环境下,下面这段代码的出现坏内存访问.

void (^stackBlock)(void);
void stackBlockErrorTest(){
    int a = 10;// auto 变量a
    stackBlock = ^{
        NSLog(@"访问了auto变量a:%d",a);
    };
}
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        stackBlockErrorTest();
        stackBlock();
    }
    return 0;
}

同样在ARC环境下,运行上述代码会产生正确✅结果。


  • __NSMallocBlock

无论在MRC还是ARC环境下,对__NSStackBlock进行copy操作都会得到__NSMallocBlock。即在堆区生成一个与栈block相同的副本(由栈copy到堆)。


下面这段代码输出的block类型为__NSMallocBlock。

void mallocBlockTest(){
    int a = 10;// auto 变量a
    void (^block)(id) = [^(id objc){
        NSLog(@"访问了auto变量a:%d block类型为:%@",a,objc);
    } copy];
    block(object_getClass(block));
}

小结:

Block类型 条件
__NSGlobalBlock 没有访问外部auto变量,或访问了外部static或者全局变量
__NSStackBlock 访问了auto变量
__NSMallocBlock 对__NSStackBlock进行了copy操作


2、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock所处内存区域分析



接下来我们根据上图表示情况,通过代码进行验证(注意需要MRC环境下验证)

void blockMemoryTest() {
    // __NSGlobalBlock
    void (^globalBlock)(void) = ^{
        
    };
    globalBlock();
    NSLog(@"%@ 地址:   %p",[globalBlock class],globalBlock);
    
    // __NSMallocBlock
    int a = 1;
    void (^mallocBlock)(void) = [^{
        int c = a;
    } copy];
    
    mallocBlock();
    NSLog(@"%@ 地址:   %p",[mallocBlock class],mallocBlock);
    
    // __NSStackBlock
    int b = 1;
    void (^stackBlock)(void) = ^{
        int c = b;
//        NSLog(@"%d",a);
    };
    stackBlock();
    NSLog(@" %@ 地址:%p",[stackBlock class],stackBlock);
}

下面是我运行的结果:

首先我们需要了解应用内存分配地址的特点是程序区、数据区、堆、栈这四个区内存地址是依次增大的。


① 栈区的地址值比较大,一般以0x7F开头,__NSStackBlock__地址为0x7ffeefbff408,符合这一特点;


② 堆区存放的是iOS对象,对象内存地址有word_align对齐规则,其地址的低三位均为0(详见:iOS进阶笔记(一)对象本质相关源码解读)。上面__NSMallocBlock__的地址为:0x1028328b0,其低三位是0。同时我们知道对象的isa占用33位,第3-35位用来表示类信息,而该地址符合这一特点。故断定__NSMallocBlock__内存地址存放于堆空间。


③ 对于iOS系统数据区的变量特点是地址值比较小,最高位两个字节一般都是0(即4个0),__NSGlobalBlock__的地址为0x1000040d8也符合这一特点。


综上,__NSGlobalBlock__位于数据区,__NSMallocBlock__位于堆区,__NSStackBlock__位于栈区。


3、__NSGlobalBlock、__NSStackBlock、__NSMallocBlock的Copy操作分析

我们通过下面示例进行分析:

void blockCopyTest()
{
    // 1、对全局block进行copy
    void (^globalBlockCopy)(void) = [^{
        
    } copy];
    
    globalBlockCopy();
    NSLog(@" %@ 地址:%p",[globalBlockCopy class],globalBlockCopy);
    
    // 2、对栈block进行copy
    int a = 10;
    void (^stackBlockCopy)(void) = [^{
        int c = a;
    } copy];
    
    stackBlockCopy();
    NSLog(@" %@ 地址:%p",[stackBlockCopy class],stackBlockCopy);
    
    // 3、对堆block进行copy
    int b = 20;
    void (^mallocBlockCopy)(void) = [[^{
        int c = b;
    } copy] copy];// 先对栈block进行copy得到堆block,该堆block再进行copy即为mallocBlockCopy
    
    mallocBlockCopy();
    NSLog(@" %@ 地址:%p",[mallocBlockCopy class],mallocBlockCopy);
}

运行结果如下:


因此,对__NSGlobalBlock进行copy操作,结果依然是__NSGlobalBlock
__NSStackBlock__NSMallocBlock进行copy操作,结果是__NSMallocBlock



4、分别位于栈和堆上的Block与__block变量

  • 栈Block和__block变量

对于栈空间,变量作用域结束后,栈上的Block变量和__block变量都会被回收。


  • 从栈copy到堆上的Block及__block变量

在栈的作用域结束时,相应的Block及__block变量会被回收;

但将栈Block(和__block)拷贝到堆上的Block(和__block)变量不受此影响。(对象内存管理由程序员管理)


5、小结

Block类型 内存区域 copy后结果

NSGlobalBlock

数据区 数据区:什么也不做

NSStackBlock

栈区 堆区:从栈copy到堆

NSMallocBlock

堆区 堆区:引用计数增加


三、Block捕获变量及相应内存管理分析


1、全局变量


ObjC代码:

int a = 10;// 全局变量为全局可访问,不需要捕获
int main(int argc, const char * argv[]) {
    ^{
        int c = a;
    }();
    return 0;
}

clang代码:

int a = 10;
// __block_impl_0并没有捕获变量a
struct __block_impl_0 {
  struct __block_impl impl;
  struct __block_desc_0* Desc;

  __block_impl_0(void *fp, struct __block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __block_func_0(struct __block_impl_0 *__cself) {
    int c = a;// 这里直接访问全局变量,并没有通过__cself间接访问
}

static struct __block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __block_desc_0_DATA = { 0, sizeof(struct __block_impl_0)};

int main(int argc, const char * argv[]) {

    ((void (*)())&__block_impl_0((void *)__block_func_0, &__block_desc_0_DATA))();

    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

block不会捕获全局变量:全局变量为可在全局进行访问,不需要捕获。通过clang代码__block_desc_0结构体的描述信息,可以看出没有对全局变量进行内存管理。


2、static变量

ObjC代码:

int main(int argc, const char * argv[]) {
    static int a = 10;
    ^{
        int c = a;
    }();
    return 0;
}

clang代码

struct __block_impl_0 {
  struct __block_impl impl;
  struct __block_desc_0* Desc;
  int *a;// 外部变量a为静态变量,这里声明了一个指向该变量的指针a
  __block_impl_0(void *fp, struct __block_desc_0 *desc, int *_a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __block_func_0(struct __block_impl_0 *__cself) {
  // 先通过__cself指针找到指向外部变量a的指针
  int *a = __cself->a; // bound by copy
  // 对指针a进行取值(即拿到所捕获的变量a的值),并赋值给c
  int c = (*a);
}

static struct __block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __block_desc_0 = { 0, sizeof(struct _block_impl_0)};

int main(int argc, const char * argv[]) {
    static int a = 10;
    // 这里传入的是变量a的地址
    ((void (*)())&_block_impl_0((void *)_block_func_0, &__block_desc_0_DATA, &a))();// 这里匿名block,但在转成clang时,还是会将
    return 0;
}

block捕获static变量为指针传递:在__Block_impl_0结构体初始化时,是将静态变量的地址传入该结构体初始化函数内部,并赋值给结构体同名的局部函数指针。在执行block()时(即调用__block_func_0函数),通过形参__cself->amain()函数中静态变量a地址传入,并通过(*a)取值。

通过上述clang代码,在__block_desc_0中没有相关对指针变量a进行相关内存管理。


3、auto变量

1)值类型变量捕获

开篇第一个例子即为对值类型的捕获。据此我们可知:

Block捕获值类型auto变量是值传递:Block捕获值类型后,将值传给Block结构体构造函数__Block_impl_0(void *fp, struct __Block_desc_0 *desc, int _a, int flags=0) : a(_a)进行保存,当在执行block(即调用表示Block代码块内容的__block_func_0函数)时,会在__block_func_0(struct __block_impl_0 *__cself)内部,经由__cself指针传入所捕获的变量值,再在内部声明一个同名的局部变量来保存该值。

通过该例clang源码可以看出,在__block_desc_0中没有相关对值类型auto变量进行相关内存管理。


2)ObjC对象类型变量捕获

同样我们以示例进行分析

ObjC代码:

int main(int argc, const char * argv[]) {
    NSObject *objc = [[NSObject alloc] init];
    ^{
        objc;
    }();
    return 0;
}

clang代码:

// block layout
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

// block实现
struct __block_impl_0 {
  struct __block_impl impl;
  struct __block_desc_0* Desc;
  NSObject *objc;// 捕获对象类型auto变量

  __block_impl_0(void *fp, struct __block_desc_0 *desc, NSObject *_objc, int flags=0) : objc(_objc) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __block_func_0(struct __block_impl_0 *__cself) {
    // 与值类型变量一样操作一致
    NSObject *objc = __cself->objc; // bound by copy

    objc;
}

// 由于捕获的是对象类型,ARC环境下,会自动对栈Block进行copy到堆,因此需要对其进行相应的内存管理
// copy操作
static void __block_copy_0(struct __block_impl_0*dst, struct __block_impl_0*src)
{
    // 注意:这里第一个参数传入的是dst的地址,可以在_Block_object_assign函数内部操作dst
    // 由于所捕获的是普通Objc对象类型,这里第三个参数为BLOCK_FIELD_IS_OBJECT (即:3)
    // 该函数内部执行了_Block_retain_object(src->objc),即对Objc对象自身进行相应内存管理
    _Block_object_assign((void*)&dst->objc, (void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

// 释放操作
static void __block_dispose_0(struct __block_impl_0*src)
{
    _Block_object_dispose((void*)src->objc, 3/*BLOCK_FIELD_IS_OBJECT*/);
}

// 相比值类型的block实现中的Desc,增加了所捕获对象类型的内存管理描述
static struct __block_desc_0 {
  size_t reserved;
  size_t Block_size;

  void (*copy)(struct __block_impl_0*, struct __block_impl_0*);

  void (*dispose)(struct __block_impl_0*);

} __block_desc_0_DATA = {// 直接对结构体进行初始化
    0,
    sizeof(struct __block_impl_0),
    __block_copy_0,// 传入copy函数指针
    __block_dispose_0 // 传入dispose函数指针
};

int main(int argc, const char * argv[]) {
    //   [[NSOject alloc] init];
    NSObject *objc = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
    
    // block
    ((void (*)())&__block_impl_0((void *)__block_func_0, &__block_desc_0_DATA, objc, 570425344))();
    
    return 0;
}

上述clang代码中_Block_object_assign函数flag参数为BLOCK_FIELD_IS_OBJECT,因此内部执行的是block源码中_Block_object_assign函数内部的_Block_retain_object函数。由于捕获的是对象类型,因此对象需要进行内存管理,这也符合Objc对象内存管理的基本原则。这也是与值类型变量的区别。


Block捕获对象类型变量也是值传递:Block捕获该类型变量,也是通过__cself指针传入所捕获的值,并在__block_func_0函数内部声明一个同名的指针以保存捕获的对象。

不同的是,需要对该类型变量进行内存管理:通过_Block_object_assign_Block_object_dispose函数对其进行相应的内存管理。其中_Block_object_assign内部执行
_Block_retain_objectretain,_Block_object_dispose内部执行_Block_release_objectrelease

详情查看_Block_object_assign _Block_object_dispose源码解读。


3)汇编查看值类型和对象类型区别

  • 值类型auto变量捕获


  • 对象类型auto变量捕获


通过上面对比也可以看出,Block捕获值类型变量在执行完block调用后,函数直接退出了(retq);而Block捕获的对象类型变量,会有retain操作。


4、修改auto变量值的异常问题

  • 在block块后面修改auto变量值

    ObjC代码示例:

    int a = 1;
    void (^block)(void) = ^{
        NSLog(@"a %d",a);// 结果 1
    };
    a = 2;// 修改auto变量值
    block();
       
    

根据前面Clang代码可知:^{ }内部实际执行的是__block_func_0函数。

   static void __block_func_0(struct __block_impl_0 *__cself) {
       int a = __cself->a; // bound by copy
   }

通过__block_func_0函数参数__cself指针带入的函数体的a,赋值给了一个同名的内部变量a。这两个变量a,分别属于main()__block_func_0()两个不同的作用域。修改main()a的值,对__block_func_0()a没有影响。故修改block块外部修改auto变量的值不会对^{}内部同名变量值造成影响。


  • block块内部修改外部auto变量值,Xcode编译器报错分析

至于上图在block内部修改 a = 2时报错,原因是这block内部个a,是接收__cself->a的内部临时变量与外面的a分别属于不同的作用域。即便对block内部变量a作了修改,外部也无法访问(这样做没有意义,下面代码模拟了该过程)。

    typedef struct {
        int a;
    }Impl;
    
    void impl_func(Impl *__cself) {
        int a = __cself->a;
        a++;
        printf("%d\n",a);// 输出2
    }
    
    void test() {
        Impl impl = {1}; // 结构体初始化,a=1
        Impl *implPtr = &impl; // 声明一个结构体指针指向该结构体
        impl_func(implPtr); // 函数调用
        printf("%d\n",impl.a);// 输出 1
    }

再者,在ARC环境下,上面的栈block会被copy到堆上。此时上述错误提示代码a = 2中的a就是堆上的a。然而block外部的a又处于栈上。很明显,这两者是相互隔离的。即便修改堆上的变量a,通过外部栈的也是访问不到的。除非是将栈上的block与堆block进行关联(即后面讲到的通过__block修饰下__forwarding指针,可以由栈指向堆)。


另外,某些情况下,我们是可以通过位于栈的block的__cself->a也是可以修外部变量值的。

通过C模拟实现

typedef struct {
    int a;
}Impl;

void impl_func(Impl *__cself) {
    int a = ++ __cself->a;// 前提是__cself所指向的Impl作用域还在
    printf("%d\n",a);// 输出2
}

void test() {
    Impl impl = {1}; // 结构体初始化,a=1
    Impl *implPtr = &impl; // 声明一个结构体指针指向该结构体
    impl_func(implPtr); // 函数调用
    printf("%d\n",impl.a);// 输出 2
}

通过C++模拟实现

// block.hpp
/**************************************************************/
#include <stdio.h>
namespace block {
    struct Impl{
        int a;
        void *FuncPtr;
        // C++结构体构造函数,: a(_a)相当于把形参_a传给了结构体变量a
        Impl(void *fp, int _a) : a(_a) {
            FuncPtr = fp;
        }
    };
    class BlockClass {
        public:
            void blockTest();
            BlockClass();// 构造函数,相当于Objective-C的 init函数
            ~BlockClass();// 析构函数,相当于Objective-C的 dealloc函数
    };
}


// block.cpp
/**************************************************************/
#include "block.hpp"
#include <iostream>
using namespace std;

namespace block {
    void BlockClass::blockTest(void)
    {
        // C++闭包(匿名函数) 学习下C++闭包,就不在外部写函数了 ~~
        auto f = [](struct Impl *__cself){
            int a = ++ __cself->a;// 前提是__cself所指向的Impl作用域还在
            cout << "__cself->a ++结果:" << a << endl;
        };
        
        // 结构体构函数初始化
        Impl impl = Impl(&f, 1);
        
        // 类型强转
        ((void (*)(struct Impl *__cself))f)(&impl);
        cout << "最终结果:" << impl.a << endl;// 2
    }
    BlockClass::BlockClass(void) { }
    BlockClass::~BlockClass(void){ }
}

// 调用
/**************************************************************/
block::BlockClass block;
block.blockTest();

但值得注意的是,这种操作不安全,前提是栈block没有出作用域。例如异步情况下可能栈block已出作用域,被系统回收了,再次执行++ __cself->a就会发生错误。


分析至此,Xcode编译器给出的报错提示,我猜测是主要有以下几个原因:

① 为了避免二义性(不知道这个a是上面提到的哪个a);

② ARC环境下,栈block会自动拷贝到堆上,但无法通过栈block操作堆block(二者是相互隔离的)。

③ MRC环境下,某些情况下,在栈block块内部(即代表block块所执行的代码的函数)操作该block是不安全的(异步情况下可能栈block已出作用域,被系统回收了,再次执行++ __cself->a就会发生错误)。

④ 再者,也可能是ObjC语言就是这么设计的,既然作为闭包,那就在闭包作用域内做自己内部的数据处理,就不要修改外部的auto变量了。若真要修改,则通过__block修饰符显式说明就好了。(严格意义上讲,通过__block修改的auto变量(位于堆),是位于栈auto变量的副本,并不是栈auto变量本身。对于这一点,后面内容会有更详细的讲解)


5、小结

变量类型 是否捕获到block内部 访问方式 是否有相应内存管理
局部变量 auto 值类型 ✔️ 值传递
对象类型 ✔️ 对象自身遵守内存管理
static ✔️ 指针传递
全局变量 直接访问


四、__block修饰的auto变量的copy及dispose分析

1、__block修饰值类型变量


Objc代码:

void test() 
{
    __block int a = 1;
    void (^block)(void) = ^{
        a = 99;
   };
}

对应Clang代码

struct __block_impl {
  void *isa; 
  int Flags; 
  int Reserved; 
  void *FuncPtr; 
};

// __block修饰的auto变量被包装成对象
struct __Block_byref_a_0 {
    void *__isa;
    struct __Block_byref_a_0 *__forwarding;// 指向自身
    int __flags;
    int __size;
    int a;
};

struct __test_block_impl_0 {
   struct __block_impl impl;
   struct __test_block_desc_0* Desc; 
   __Block_byref_a_0 *a; // 指针传递,指向包装了block所捕获变量a的对象
   
    __test_block_impl_0(void *fp, struct __test_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;
   }
};

static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
    // 指针传递,指向包装了block所捕获变量a的对象(即__Block_byref_a_0)
    __Block_byref_a_0 *a = __cself->a; // bound by ref
    
    // 通过__forwarding指针,指向该结构体自身内部的变量a并对其修改。但这里修改的不是外部变量a的值,而是由栈copy到堆的__Block_byref_a_0中的变量a。
    // 具体可查看block源码的 `static struct Block_byref *_Block_byref_copy(const void *arg)`函数

    (a->__forwarding->a) = 99;
}

// __block修饰的变量copy(retain)操作
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src)
{
    _Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
}

// __block修饰的变量dispose(release)操作
static void __test_block_dispose_0(struct __test_block_impl_0*src)
{
    _Block_object_dispose((void*)src->a, 8);
}

static struct __test_block_desc_0 {
   size_t reserved;
   size_t Block_size;
   // copy函数指针
   void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
   // dispose函数指针
   void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
    0,
    sizeof(struct __test_block_impl_0),
    __test_block_copy_0,
    __test_block_dispose_0
};

void test() 
{
    __attribute__((__blocks__(byref))) __Block_byref_a_0 a = {
        (void*)0,
        (__Block_byref_a_0 *)&a,
        0,
        sizeof(__Block_byref_a_0),
        1
    };
    
    // 使用blcok指针指向了block实现的地址
    void (*block)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_a_0 *)&a, 570425344));

    // 执行block
    ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
    

上述代码对比未使用__block捕获变量a的情况,可以看出:__block修饰auto值类型变量,在被block捕获后,将其包装成一个对象(结构体)__Block_byref_a_0(Block源码称之为__block container)。


  • copy操作

上面clang代码中_Block_object_assign函数flag参数为BLOCK_FIELD_IS_BYREF,因此内部执行的是_Block_byref_copy函数。

_Block_byref_copy源码 中提取关键内部执行的代码块如下:

static struct Block_byref *_Block_byref_copy(const void *arg) {
    // 栈__block container
    struct Block_byref *src = (struct Block_byref *)arg;
    
    // 1、在堆区开辟一块内存,保存栈__block container信息
    struct Block_byref *copy = (struct Block_byref *)malloc(src->size);

    // 2、copy的forwarding指向自身(堆)
copy->forwarding = copy; // patch heap copy to point to itself

    // 3、让位于栈区的src的forwarding指向新分配位于堆区的__block container
    src->forwarding = copy; 
    
    ......
}

据此可知,此种情况在堆区开辟一块内存,来copy栈__block container内容到堆上(包含__block修饰的auto变量也由栈copy堆),即堆有一个栈__block container副本。

堆__block container__forwarding指向自身堆的变量a

栈__block container__forwarding由原来指向自身转变为指向堆__block container

这样就可以在外面通过栈__block container操作堆__block container 内部的变量a并修改其值。如下面的测试代码,输出结果是100

void test()
{
    __block int a = 1;
    void (^block)(void) = ^{
        NSLog(@"%d",a);// 100
    };
    a = 100;
    block();
}

对于更多__forwarding 解读请查看__forwarding指针的用意?


  • dispose操作

根据Block源码的_Block_object_dispose函数可知,ARC环境下,对于Block捕获的__block变量会调用内部的_Block_byref_release函数。由于__block变量是值类型,因此只需将封装了该值__block container对象释放即可,此过程是通过free函数将__block container对象释放的(详见_Block_object_dispose源码解读)

MRC环境下,由于__block container是对象类型,因此需要程序员手动的去release。


2、__block修饰对象类型变量

ObjC代码:(MRC环境)

void test()
{
    __block NSObject *objc = [[NSObject alloc] init];
    
    void (^block)(void) = [^{
        objc = [[NSObject alloc] init];// objc指针指向了一个新对象
    } copy];// 当ARC下,会自动copy,此处就不用加copy了
    
    block();
}

clang代码:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __Block_byref_objc_0 {
    void *__isa;// 8
    __Block_byref_objc_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
    NSObject *objc;// 通过__Block_byref_objc_0结构体地址 +40,找到该对象
};

struct __test_block_impl_0 {
    struct __block_impl impl;
    struct __test_block_desc_0* Desc;
    __Block_byref_objc_0 *objc; // by ref

    __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
    __Block_byref_objc_0 *objc = __cself->objc; // bound by ref

    (objc->__forwarding->objc) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}

// 【第二层copy】将栈__block container拷贝到堆(__block container表示:__block所修饰包装了被block所捕获的外部变量的对象__Block_byref_xx)
static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) 
{
    _Block_object_assign(
        (void*)&dst->objc,
        (void*)src->objc,
        8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __test_block_dispose_0(struct __test_block_impl_0*src) 
{
    _Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __test_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
    void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
    0,
    sizeof(struct __test_block_impl_0),
    __test_block_copy_0,
    __test_block_dispose_0
};// 结构体默认初始化

// 【第三层copy】浅拷贝:对block所捕获的对象类型的auto变量自身的copy。【注意此过程在MRC环境下,根据131这个flag,执行object的retainCount加1】
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
    // + 40:*det拿到__Block_byref_objc_0结构体地址首地址,由于该结构体object变量前面成员内存总共为40,这里通过+40找到该object.
    // 第三个参数是 131代表:BLOCK_BYREF_CALLER | BLOCK_FIELD_IS_OBJECT,表示为__block container,并捕获了对象类型的auto变量
    _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
    _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}

void test()
{
    __attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {
      (void*)0,
      (__Block_byref_objc_0 *)&objc, 
      33554432, 
      sizeof(__Block_byref_objc_0), 
      __Block_byref_id_object_copy_131, 
      __Block_byref_id_object_dispose_131, 
      ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
    };

    // 【第一层copy】深拷贝:block自身由栈copy到堆。
    void (*block)(void) = (void (*)())((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344)), sel_registerName("copy"));

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

小结

  • __block所修饰的block捕获对象类型的auto变量的copy操作有三层,即:


    【第一层copy】深拷贝:block自身由栈copy到堆。


    【第二层copy】深拷贝:将栈__block container拷贝到堆(__block container表示:__block所修饰包装了被block所捕获的外部变量的对象__Block_byref_xx)。该过程详细描述见_Block_byref_copy

    NOTE:ARC环境下在_Block_byref_copy内部已经执行了第三层拷贝。


    【第三层copy】浅拷贝:block所捕获的对象类型的auto变量自身的copy(指针拷贝)。即该对像会被Block持有。

NOTE:此过程在MRC环境下,根据131这个flag,执行object的retainCount加1。ARC环境下在该copy过程发生在第二层copy内部。


  • dispose操作

根据Block源码可知,ARC环境下,对于Block捕获的__block变量会调用内部的_Block_byref_release函数。由于__block变量是对象类型,因此会调用该对象的dispose函数对其进行释放。最后再调用free函数将__block container对象释放。(详见_Block_object_dispose源码解读)

MRC环境下,由于__block container是对象类型,因此需要程序员手动的去release。


3、__block修饰__weak对象类型变量

ObjC代码:

void test()
{
    __weak __block NSObject *objc = [[NSObject alloc] init];
    void (^block)(void) = ^{
        objc = [[NSObject alloc] init];// objc指针指向了一个新对象
    };// __weak适用于ARC环境,所以此处位于栈的block,编译器会自动将其copy到堆
    block();
}

clang代码:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};

struct __Block_byref_objc_0 {
    void *__isa;
    __Block_byref_objc_0 *__forwarding;
    int __flags;
    int __size;
    void (*__Block_byref_id_object_copy)(void*, void*);
    void (*__Block_byref_id_object_dispose)(void*);
    NSObject *__weak objc;
};

struct __test_block_impl_0 {
    struct __block_impl impl;
    struct __test_block_desc_0* Desc;
    __Block_byref_objc_0 *objc; // by ref
    __test_block_impl_0(void *fp, struct __test_block_desc_0 *desc, __Block_byref_objc_0 *_objc, int flags=0) : objc(_objc->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __test_block_func_0(struct __test_block_impl_0 *__cself) {
    
    __Block_byref_objc_0 *objc = __cself->objc; // bound by ref

    (objc->__forwarding->objc) = ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"));
}

static void __test_block_copy_0(struct __test_block_impl_0*dst, struct __test_block_impl_0*src) {
    _Block_object_assign((void*)&dst->objc, (void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __test_block_dispose_0(struct __test_block_impl_0*src) {
    _Block_object_dispose((void*)src->objc, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __test_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __test_block_impl_0*, struct __test_block_impl_0*);
    void (*dispose)(struct __test_block_impl_0*);
} __test_block_desc_0_DATA = {
    0,
    sizeof(struct __test_block_impl_0),
    __test_block_copy_0,
    __test_block_dispose_0
};

void test()
{
    __attribute__((objc_ownership(weak))) __attribute__((__blocks__(byref))) __Block_byref_objc_0 objc = {
        (void*)0,
        (__Block_byref_objc_0 *)&objc,
        33554432,
        sizeof(__Block_byref_objc_0),
        __Block_byref_id_object_copy_131,
        __Block_byref_id_object_dispose_131,
        ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))};

    void (*block)(void) = ((void (*)())&__test_block_impl_0((void *)__test_block_func_0, &__test_block_desc_0_DATA, (__Block_byref_objc_0 *)&objc, 570425344));

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

将clang代码与__block修饰对象类型变量中的clang代码对比,发现区别在于__Block_byref_objc_0对象中的objc对象类型为__weak,即block与捕获的对象为弱引用关系。这也符合ARC下,__weak对象的内存管理原则(即__weak类型的对象引用计数为0时,__weak对象的指针被置为nil,这一点有别于MRC下使用的unsafe unretained)。

同理将上面Objc代码中的__weak改为__strong,再转成clang代码后,会发现__Block_byref_objc_0对象中的objc对象类型为__strong,即block与捕获的对象为强引用关系。由于Block已持有了该__strong类型的__block变量,在某种情况下,若该变量反过来又直接或间接引用了该Block,这就造成了循环引用。(循环引用问题详见:iOS进阶笔记--Block基础篇

至于__weak__strong类型的__block变量第dispose基本是一样的,都还是走__block修饰对象类型变量中的dispose流程。区别还是在于强引用和弱引用的不同。



4、__forwarding指针的用意?


  • 拷贝前栈Block的__forwarding指针指向自身

根据前面clang源码可知,__forwarding指针是 __block在修饰auto变量后,将其包装成一个类似struct __Block_byref_xx的对象。在该结构(源码称作:__block container)中,__forwarding指向了自身。


  • 拷贝到堆上后,栈上__block变量用结构体(源码称作:__block container,也就是被__block所修饰并将所捕获了auto变量包装成类似__Block_byref_objc_0的结构体)会将成员变量的__forwarding指针替换为目标堆上的__block container地址。

为了保证栈与堆上的__block所修饰的变量修改同步,在将__block所修饰的变量从对栈copy到堆的同时,栈的__block container结构体的__forwarding指针将指向了堆__block container结构体。



5、小结

  • __block修饰的auto变量(包括值类型、__weak对象和__strong类型对象)会被包装成__block container对象。

    ① 值类型变量的三层copy:即block自身由栈到堆的copy、__block container对象由栈到堆的copy及__block变量值的copy(在对堆上复制一个栈__block变量的副本)。

    ② 对象类型的三层copy:即block自身由栈到堆的copy、__block container对象由栈到堆的copy和对象指针的浅拷贝(即被Block持有,引用计数增加)。


  • Block从栈copy到堆时,__block变量产生的影响如下:

    __block变量的位置 Block从栈copy到堆的结果
    从栈copy到堆并被Block持有
    被Block持有(引用计数增加)

  • 栈Block被copy到堆的时机:

    ① 主动调用Block的copy实例方法时(内部执行了_Block_copy函数)。

    ② Block作为函数返回值。

    ③ 将Block赋值个给__strong修饰符id类型的类或Block类型成员变量。

    ④ 在方法名中含有usingBlock的Cocoa框架或GCD的API中的Block。


  • Block捕获__block变量的dispose其实是copy的逆操作。

    ① 若为__block值类型的变量:则只释放封装该类型的位于堆的__block container对象。

    ② 若为__block对象类型的变量:先释放该对象(调用dispose函数),然后再释放封装该类型的位于堆的__block container对象。


  • __forwarding指针是Block捕获 __block变量的产物。

    目的是通过栈的__forwarding访问堆的内存,并达到修改位于堆的__block变量(即栈__block变量的副本)的目的。



五、Block的方法签名探索

据前面所述,Block本质是Objc对象,并且Block的调用过程也等同于方法(函数)调用。因此Block的方法应该也少不了方法签名(Signature)。接下来我们先看个普通Objc对象方法的签名的例子复习下签名表示含义。

1、普通Objc对象的Signature

示例如下:

@interface Person : NSObject{
    @public
    int _hours;
}
- (void)sleep:(int)hours;
- (NSString *)printHours;
@end

@implementation Person

- (void)sleep:(int)hours {
    _hours = hours;
}
- (NSString *)printHours {
    return [NSString stringWithFormat:@"%d",_hours];
}
+ (NSArray *)getPersonInfoWithName:(NSString *)name age:(int)age {
    return @[name,@(age)];
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // import<objc/runtime.h>
    
        // 获取无返回值且含参的instance方法的Type Encoding
       Method m_sleep = class_getInstanceMethod([Person class], @selector(sleep:));
        const char *m_sleep_chr = method_getTypeEncoding(m_sleep);
        printf("%s\n\n",m_sleep_chr);// v20@0:8i16
        
        // 获取有返回值无参的instance方法的Type Encoding
        Method m_hours = class_getInstanceMethod([Person class], @selector(printHours));
        const char *m_hours_chr = method_getTypeEncoding(m_hours);
        printf("%s\n\n",m_hours_chr);// @16@0:8
        
        // 获取含返回值且含参的Class方法的Type Encoding
        Method m_info = class_getClassMethod([Person class], @selector(getPersonInfoWithName:age:));
        const char *m_info_chr = method_getTypeEncoding(m_info);
        printf("%s\n\n",m_info_chr);// @28@0:8@16i24
    return 0;
}

通过runtime提供的API,很容易获得一个对象方法的Signature,简单说明下@28@0:8@16i24这个签名的打印结果:

  • 第一个@表示返回值为对象;

  • 28表示参数总共内存大小(字节数)。

    第一个参数为self,占8个字节;

    第二个参数为SEL struct objc_selector *类型,占8个字节;

    第三个参数为对象,占8个字节;

    第四个参数为int类型,占4个字节。

    (每个Objc方法都默认含有self和SEL两个参数,所以才能在方法体中访问self,self是局部变量)
  • 第二个@表示第一个参数类型为对象类型(即self),0表示第一个参数内存起始值,因为是第一个参数,内存从0开始;

  • :表示第二个参数类型为SEL,8表示第二个参数内存起始值,由于第一次参数为self,占用8个字节,故第二个参数从8开始;

  • 第三个@表示第三个参数类型为对象,由于self和SEL各占8个字节,故该参数内存起始值为16;

  • i表示第四个参数为int类型,起始地址为24。(0+8+8+8)


综上,方法签名描述了方法的返回值类型、所有参数占用内存大小的总和、参数个数、参数类型、各个参数的内存起始地址的描述信息。


另外还可以通过NSMethodSignature这个类获取Objc对象的签名(这种方式比method_getTypeEncoding相比,不含有各参数起始地址信息)。代码块如下:

NSString * getMethodTypeEncoding(Class cls, SEL sel, BOOL isInstanceMethod)
{
    NSMutableString *result = [NSMutableString string];
    NSMethodSignature *signature;
    
    if (isInstanceMethod) {
        signature = [cls instanceMethodSignatureForSelector:sel];
    }else {
        signature = [cls methodSignatureForSelector:sel];
    }
    
    // 返回值类型
    const char * returnType = signature.methodReturnType;
    [result appendString:[NSString stringWithUTF8String:returnType]];
    for (NSUInteger i = 0; i < signature.numberOfArguments; i++) {
        // 参数类型
        const char *argType = [signature getArgumentTypeAtIndex:i];
        [result appendString:[NSString stringWithUTF8String:argType]];
    }
    return [result copy];
}


2、Block的方法签名分析

1)从Block源码角度分析

从Block源码Block_descriptor_3结构体中可以找到signature信息。

struct Block_descriptor_3 {
    // requires BLOCK_HAS_SIGNATURE
    const char *signature; //签名
    const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT 内存布局
};

下面是对Block内存经过平移得到signature的过程

// Block的signature的实现
const char * _Block_signature(void *_Block_descriptor_3)
{
    struct Block_layout *layout = (struct Block_layout *)aBlock;
    
    // 【关键1】确保有BLOCK_HAS_SIGNATURE这个标识位
    if (!(layout->flags & BLOCK_HAS_SIGNATURE))
        return nullptr;

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (layout->flags & BLOCK_SMALL_DESCRIPTOR) {
        auto *bds = (Block_descriptor_small *)_Block_get_descriptor(layout);
        return unwrap_relative_pointer<const char>(bds->signature);
    }
#endif
    struct Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
    return desc3->signature;
}

// _Block_descriptor_3通过block内存经过两次平移得到
static struct Block_descriptor_3 * _Block_descriptor_3(struct Block_layout *aBlock)
{
    // 先由aBlock得到Block_descriptor_1
    uint8_t *desc = (uint8_t *)_Block_get_descriptor(aBlock);
    //Block_descriptor_1内存平移得到Block_descriptor_2
    desc += sizeof(struct Block_descriptor_1);
    if (aBlock->flags & BLOCK_HAS_COPY_DISPOSE) {// 【关键2】确保有这个标识位
        // 对Block_descriptor_2内存平移得到Block_descriptor_3
        desc += sizeof(struct Block_descriptor_2);
    }
    return (struct Block_descriptor_3 *)desc;
}

// 通过block内存,找到Block_descriptor_1
static inline void *_Block_get_descriptor(struct Block_layout *aBlock)
{
    void *descriptor;

#if __has_feature(ptrauth_signed_block_descriptors)
    if (!(aBlock->flags & BLOCK_SMALL_DESCRIPTOR)) {
        descriptor =
                (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
    } else {
        uintptr_t disc = ptrauth_blend_discriminator(
                &aBlock->descriptor, _Block_descriptor_ptrauth_discriminator);
        descriptor = (void *)ptrauth_auth_data(
                aBlock->descriptor, ptrauth_key_asda, disc);
    }
#elif __has_feature(ptrauth_calls)
    descriptor = (void *)ptrauth_strip(aBlock->descriptor, ptrauth_key_asda);
#else
    descriptor = (void *)aBlock->descriptor;
#endif

    return descriptor;
}

概括就是:先通过Block内存地址+24字节(根据Block_layout结构:isa[8字节] + flags[4字节] + reserved[4字节] + invoke[8字节] = 24)得到Block_descriptor_1地址,然后将Block_descriptor_1内存+16字节(Block_descriptor_1和Block_descriptor_2为指针类型分别占8个字节)得到Block_descriptor_3内存地址。而Block_descriptor_3内部第一个员为signature,因此就拿到了signature


根据这个思路,我们可以自己实现获取Block的signature过程。(借鉴了Aspects

① 先定义自定义block结构:struct GGBlock_layout

 typedef NS_ENUM(int, GGBlockFlags) {
    // 是否拥有拷贝辅助函数,它决定了description_2
    GG_BLOCK_HAS_COPY_DISPOSE = (1 << 25),
    // 是否有签名,它决定了description_3
    GG_BLOCK_HAS_SIGNATURE    = (1 << 30)
};

// descriptor_1
struct GGBlock_descriptor_1 {
    unsigned long int reserved;
    unsigned long int size;
};

// descriptor_2
struct GGBlock_descriptor_2 {
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

// descriptor_3
struct GGBlock_descriptor_3 {
    const char *signature;
    const char *layout;
};

typedef struct GGBlock_layout {
    __unused Class isa;
    GGBlockFlags flags;
    __unused int reserved;
    // block块内真正执行的函数指针
    void (__unused *invoke)(struct Block_layout *block, ...);
    
    struct GGBlock_descriptor_1 *descriptor_1;
    struct GGBlock_descriptor_2 *descriptor_2;
    struct GGBlock_descriptor_3 *descriptor_3;
    
} *GGBlock_layoutRef;// 声明一个Block_layout类型的结构体指针

② 根据前面的思路进行实现

// 获取方法签名的 Type Encoding
NSString * gg_signatureTypeEncoding(NSMethodSignature *signature)
{
    NSMutableString *result = [NSMutableString string];
    // 返回值类型
    const char * returnType = signature.methodReturnType;
        [result appendString:[NSString stringWithCString:returnType encoding:NSUTF8StringEncoding]];
    for (NSUInteger i = 0; i < signature.numberOfArguments; i++) {
        const char *argType = [signature getArgumentTypeAtIndex:i];
        [result appendString:[NSString stringWithCString:argType encoding:NSUTF8StringEncoding]];
    }
    return [result copy];
}

// 获取block的signature
NSMethodSignature * gg_blockMethodSignature(id block)
{
    // 拿到block结构体
    GGBlock_layoutRef layout = (__bridge void *)block;

    if (!(layout->flags & GG_BLOCK_HAS_SIGNATURE)) return nil;
    
    // 首先拿到descriptor_1
    void *desc = layout->descriptor_1;
    
    // 按descriptor_1结构内存平移16字节拿到descriptor_2
    desc += sizeof(struct GGBlock_descriptor_2);
    
    if (layout->flags & GG_BLOCK_HAS_COPY_DISPOSE) {
        // 按descriptor_2结构内存平移16字节拿到descriptor_3
        desc += sizeof(struct GGBlock_descriptor_3);
    }
    if (!desc) return nil;
    // 至此,此时desc内存地址里的内容就是signature的信息了
    const char *signature = (*(const char **)desc);
    return [NSMethodSignature signatureWithObjCTypes:signature];
}

// 获取block签名的类型编码(Type Encoding)
NSString * gg_blockSignatureTypeEncoding(id block)
{
    if (!block) return nil;
    NSMethodSignature *signature = gg_blockMethodSignature(block);
    if (!signature) return nil;
    return gg_signatureTypeEncoding(signature);
}

const char *signature = (*(const char **)desc)解释如下:
const char ** :由于desc类型为 struct *,而signature类型为 const char *,需要进行类型强转;
最外层的 * :desc为地址,所以要用 * 取里面的值,即signature内容。


③ 下面开始验证一下

  • 全局block:无参数无返回值的

    void (^block1)(void) = ^{
        printf("hello\n");
    };
    block1();
        
    NSString *s1 = gg_blockSignatureTypeEncoding(block1);
    NSLog(@"%@",s1);
    

    输出结果:v@?

    返回值:为void类型;
    第一个参数:为@表示block对象(这也侧面证明了block本质是对象);
    第二个参数:(可理解为SEL)为?,表示block方法的Type Encoding类型。

    对于?这个Type EncodingApple官方解释是这样:An unknown type (among other things, this code is used for function pointers),即该类型被用作函数指针的特殊类型。这也与block源码的Block_descriptor_2的描述一致。即

    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
    

  • 有返回值且含参的栈block,在_Block_copy函数返回时被copy到堆,成为堆block

    int b = 1;
    int (^block2)(int) = ^(int a){
        return a+b;
    };
    block2(2);
    NSString *s2 = gg_blockSignatureTypeEncoding(block2);
    NSLog(@"%@",s2);
    

    输出结果:i@?i

    返回值为int类型;
    第一个参数:为@block对象类型;
    第二个参数:?为block的Type Encoding
    第三个参数:为int类型。


  • 返回值为所捕获的对象类型

    NSObject *obj = [[NSObject alloc] init];
    NSObject* (^block3)(void) = ^{
        return obj;
    };
    block3();
    NSString *s3 = gg_blockSignatureTypeEncoding(block3);
    NSLog(@"%@",s3);
    

    输出结果:@"NSObject"@?

    返回值:@NSObject对象类型;
    第一个参数:@为block对象类型;
    第二个参数:?为block的Type Encoding


  • __block的情况

    __block int c = 1;
    void (^block4)(void) = ^{
        c =2;
    };
    block4();
    NSString *s4 = gg_blockSignatureTypeEncoding(block4);
    NSLog(@"%@",s4);
    

    输出结果:v@?

    返回值:vvoid类型;
    第一个参数:@为block对象类型;
    第二个参数:?为block的Type Encoding


  • 返回值为block的情况

    typedef void(^GGBlock)(float);
    GGBlock (^block5)(int) = ^(int a){
        return ^(float b){
            
        };
    };
    block5(1);
    NSString *s5 = gg_blockSignatureTypeEncoding(block5);
    NSLog(@"%@",s5);
    

    输出结果:@?<v@?f>@?i

    返回值:@?表示返回值为block类型,只有默认参数,不含其它参数;
    <v@?f>描述了这个返回block的签名:返回值为void类型,第一个参数为@block对象类型;第二个参数为block的Type Encoding;第三个参数为float类型。)

    第一个参数:@为block对象类型;
    第二个参数:?为block的Type Encoding



2) 从汇编角度分析

以下面栈block代码为例:

int a=1;
void (^block)(int) = ^(int b){
    printf("a+b:%d",a+b);
}
block(2);

根据前面所掌握的知识,我们知道ARC环境下会将栈block拷贝到堆上,会执行_Block_copy函数。我们就断点定位到此。

从通用寄存器x0中可以通过register read x0打印栈block的内存地址;
_Block_copy返回前(req命令前),再次读取x0就是copy栈block后的堆block内存地址。

前面我们讲到过,Block_descriptor_1地址 = block内存地址 + 24个字节;

Block_descriptor_3地址 = Block_descriptor_1地址 + 16个字节;

因此,最终再打印Block_descriptor_3的内容即为block的sigature

最终得到该block的签名为:v8@?0,这里我们可以看出参数内存起始位置0是在?后面,这是因为@?这两个参数为block的默认参数,不计入在内。(偷工减料,这优化简直了~~~)


3、小结

根据以上分析得知block方法签名的Type Encoding为 ?@?这两个参数为block的默认参数。



六、附录:Block相关源码解读

  • struct Block_layout
struct Block_layout {
    // isa指针,通过isa可以找到block的类型
    void * __ptrauth_objc_isa_pointer isa;
    
    // 用来作标识符的(包括引用计数信息),类似于isa中的位域,按bit位表示一些block的附加信息
    volatile int32_t flags; // contains ref count
    
    // 保留信息,用于储存block内部变量信息
    int32_t reserved;
    
    // 函数指针,指向包装了block所执行代码块的函数地址
    // BlockInvokeFunction定义:typedef void(*BlockInvokeFunction)(void *, ...);
     BlockInvokeFunction invoke;
    
    // block描述信息( blockSize等信息)
     struct Block_descriptor_1 *descriptor;
    
    // 捕获的外部变量
};

  • flags->flags
// Values for Block_layout->flags to describe block objects
enum {
    //释放标记,一般常用于BLOCK_BYREF_NEEDS_FREE做位与运算,一同传入flags,告知该block可释放
    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 SMALL 类型的Block
#endif

    BLOCK_IS_NOESCAPE =       (1 << 23), // compiler
    
   //低16位是否有效的标志,程序根据它来决定是否增加或者减少引用计数位的值
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    
    //是否拥有拷贝辅助函数,(a copy helper function)决定block_description_2
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    
   //是否拥有block C++析构函数
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    
    //标志是否有垃圾回收,OSX
    BLOCK_IS_GC =             (1 << 27), // runtime
    
   //标志是否是全局block
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
   
     //与BLOCK_HAS_SIGNATURE相对,判断是否当前block拥有一个签名,用于runtime时动态调用
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    
    //是否有签名
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    
    //使用有拓展,决定block_description_3
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};


  • descriptor
typedef void(*BlockCopyFunction)(void *, const void *);
typedef void(*BlockDisposeFunction)(const void *);

#define BLOCK_DESCRIPTOR_1 1
struct Block_descriptor_1 {
    uintptr_t reserved;//保留信息
    uintptr_t size;//block大小
};

// 对block捕获的auto变量进行内存管理的descriptor
#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    BlockCopyFunction copy;//拷贝函数指针
    BlockDisposeFunction dispose;// 销毁函数指针
};

// block签名的descriptor
#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 内存布局
};

// SMALL 类型的Block,对以上三种descriptor简单封装,编译器的优化
struct Block_descriptor_small {
    uint32_t size;
    int32_t signature;
    int32_t layout;

    /* copy & dispose are optional, only access them if
       Block_layout->flags & BLOCK_HAS_COPY_DIPOSE */
    int32_t copy;
    int32_t dispose;
};


Block_descriptor_2是由Block_descriptor_1内存平移得到

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);// 对Block_descriptor_1内存地址向左平移
    return (struct Block_descriptor_2 *)desc;
}


Block_descriptor_3是由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);// 对Block_descriptor_2内存地址向左平移
    }
    return (struct Block_descriptor_3 *)desc;
}

  • signature
const char * _Block_signature(void *aBlock)
{
    struct Block_layout *layout = (struct Block_layout *)aBlock;
    if (!(layout->flags & BLOCK_HAS_SIGNATURE))
        return nullptr;

#if BLOCK_SMALL_DESCRIPTOR_SUPPORTED
    if (layout->flags & BLOCK_SMALL_DESCRIPTOR) {
        auto *bds = (Block_descriptor_small *)_Block_get_descriptor(layout);
        return unwrap_relative_pointer<const char>(bds->signature);
    }
#endif
    // _Block_descriptor_3()函数内部首先拿到Block_descriptor_1,然后再进行内存平移得到Block_descriptor_3
    struct Block_descriptor_3 *desc3 = _Block_descriptor_3(layout);
    return desc3->signature;// 通过Block_descriptor_3拿到方法签名
}

  • Block的copy和dispose的参数
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类型作为变量
    BLOCK_FIELD_IS_BLOCK    =  7,  // a block variable
    // 经过__block修饰的变量
    BLOCK_FIELD_IS_BYREF    =  8,  // the on stack structure holding the __block variable
    // weak 弱引用变量
    BLOCK_FIELD_IS_WEAK     = 16,  // declared __weak, only used in byref copy helpers
    // 处理__block container(__block所修饰包装了被block所捕获的外部变量的对象__Block_byref_xx) 修饰并捕获变量为对象类型auto变量)
    // 不单独使用,需结合 id objc 或 __weak id objc使用。
    BLOCK_BYREF_CALLER      = 128, // called from __block (byref) copy/dispose support routines.
};
  • _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:
        /******* Block所捕获对象类型的auto变量
        id object = ...;
        [^{ object; } copy];
        ********/

        _Block_retain_object(object);// 对object对象进行相应内存管理
        *dest = object;// 指针赋值
        break;

      case BLOCK_FIELD_IS_BLOCK:
        /******* 【第一层copy】 block自身copy,即将栈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】将__block container 从栈copy到堆
         __block container解释为:__block修饰,包装block所捕获auto变量的对象(即:__Block_byref_xx的结构体)
         
         // 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);// ARC下,在该函数内部执行了第三层copy
        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;// MRC下,由于object本身就是对象类型,这里直接执行引用,retainCount加1,在使用完该对象时需要手动执行[objc release].
        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;// MRC下, 由于object本身就是对象类型,这里直接执行引用,retainCount加1,与上面区别,此处为unretained引用。在使用完毕该对象时需要手动执行[objc release].
        break;

      default:
        break;
    }
}

  • _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:// __block修饰的变量
        // get rid of the __block data structure held in a Block
        _Block_byref_release(object);//
        break;
      case BLOCK_FIELD_IS_BLOCK:// 若为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_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
    // 强转为Block_layout类型
    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) {// 若是全局block直接返回
        return aBlock;
    }
    else {// 若是栈block,则malloc申请一块内存,将blockcopy到堆
        // 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;// 直接调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 重置flags
        result->flags &= ~(BLOCK_REFCOUNT_MASK|BLOCK_DEALLOCATING);    // XXX not needed
        result->flags |= BLOCK_NEEDS_FREE | 2;  // logical refcount 1
        
        // 对block的descriptor进行处理
        // 具体是:将discriptor_1经过内存平移得到discriptor_2,并执行discriptor_2中的copy函数指针调用
        // 函数执行顺序:_Block_call_copy_helper -> _Block_get_copy_function -> _Block_get_copy_fn -> _Block_get_function_pointer ->  _Block_set_function_pointer
        
        _Block_call_copy_helper(result, aBlock);//
        // Set isa last so memory analysis tools see a fully-initialized object.
    
        result->isa = _NSConcreteMallocBlock;// 完成copy后,将堆block的isa设置为_NSConcreteMallocBlock堆类型
        return result;
    }
}

  • _Block_byref_copy
static struct Block_byref *_Block_byref_copy(const void *arg) {
    struct Block_byref *src = (struct Block_byref *)arg;

    // 若blcok捕获的对象没有分配内存,则进行malloc分配内存
    if ((src->forwarding->flags & BLOCK_REFCOUNT_MASK) == 0) {
        // 【第二层拷贝】👇这些操作意思是将位于栈的__block所修饰的对象(即__Block_byref_objc_0)copy到堆上
        // 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
        // byref引用计数加减以2为单位,所以这里+4
        // 为什么引用计数是 +2 而不是 +1 ?,因为flags的第一号位置已经存储着释放标记
        copy->flags = src->flags | BLOCK_BYREF_NEEDS_FREE | 4;
        
        // copy的forwarding指针指向了自身(堆)
        copy->forwarding = copy; // patch heap copy to point to itself

        // 让位于栈区的src的forwarding指向新分配位于堆区的__block container
        src->forwarding = copy;  // patch stack to point to heap copy
        
        copy->size = src->size;
        
        
        if (src->flags & BLOCK_BYREF_HAS_COPY_DISPOSE) {// 若有copy能力
            // Trust copy helper to copy everything of interest
            // If more than one field shows up in a byref block this is wrong XXX
            
            // 对src内存平移得到src2
            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;
            
            // 对src2内存平移得到src3
            if (src->flags & BLOCK_BYREF_LAYOUT_EXTENDED) {// BLOCK_BYREF_LAYOUT_EXTENDED类似于TiggedPointer,是对Block_byref_3的指针进行的优化
                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;
            }
            
            // 【第三层拷贝】__block所修饰的变量的copy
            // 当__block修饰的是对象,对象通过byref_keep保存,在合适的时机进行调用
            (*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
    // 已经拷贝到堆,这里只进行引用计数加1
    else if ((src->forwarding->flags & BLOCK_BYREF_NEEDS_FREE) == BLOCK_BYREF_NEEDS_FREE) {
        latching_incr_int(&src->forwarding->flags);
    }
    
    // 这里__forwarding实际指向的是src拷贝后的堆
    // 因为上边执行了代码:src->forwarding = copy;
    // 这样做,有点里氏替换原则的意思
    return src->forwarding;
}


  • _Block_release
void _Block_release(const void *arg) {
    struct Block_layout *aBlock = (struct Block_layout *)arg;
    if (!aBlock) return;
    // 若是全局block,直接返回
    if (aBlock->flags & BLOCK_IS_GLOBAL) return;
    // 若引用计数不为0
    if (!(aBlock->flags & BLOCK_NEEDS_FREE)) return;
    // 以上都不满足,则释放block对象
    if (latching_decr_int_should_deallocate(&aBlock->flags)) {
        _Block_call_dispose_helper(aBlock);
        _Block_destructInstance(aBlock);
        free(aBlock);
    }
}

  • _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;// 取消指针引用,又将原来指向堆的又重新指回到栈;但这里的栈byref指针任然指向堆空间
    
    if (byref->flags & BLOCK_BYREF_NEEDS_FREE) {// 若判断__block变量可被释放
        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) {// 是否有辅助的copy函数,即表示__block变量为对象类型
                // 通过Block_byref_1内存平移找到Block_byref_2
                struct Block_byref_2 *byref2 = (struct Block_byref_2 *)(byref+1);
                // 将对象类型__block类型变量进行dispose操作
                (*byref2->byref_destroy)(byref);
            }
            free(byref);// 释放__block container 对象自身。
        }
    }
}



以上


----------End------------

参考文章:

https://clang.llvm.org/docs/Block-ABI-Apple.html

http://blog.devtang.com/2013/07/28/a-look-inside-blocks/

https://www.galloway.me.uk/2013/05/a-look-inside-blocks-episode-3-block-copy/

https://github.com/steipete/Aspects

https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/ObjCRuntimeGuide/Articles/ocrtTypeEncodings.html


参考书籍:《Objective-C 高级编程 iOS与OS X多线程和内存管理

这里有电子书,由于Github访问时不时的不稳定,于是换到了Gitee(Gitee有文件下载大于1M需要登录授权限制 ~~~~)



posted @ 2021-08-04 20:57  ITRyan  阅读(216)  评论(0编辑  收藏  举报