Block 捕获变量的实现原理之基本数据类型变量

之前的文章我们谈过关于C语言结构体的一些知识,借助于这些知识我们来看一下OC中关于Block的内部实现原理.

1. Block基本知识

1.1 Block的内部实现

在apple开源的objc源代码中,我们找到了关于Block实现的一些细节:

// Values for Block_layout->flags to describe block objects
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    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
};

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

#define BLOCK_DESCRIPTOR_2 1
struct Block_descriptor_2 {
    // requires BLOCK_HAS_COPY_DISPOSE
    void (*copy)(void *dst, const void *src);
    void (*dispose)(const void *);
};

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

struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved; 
    void (*invoke)(void *, ...);
    struct Block_descriptor_1 *descriptor;
    // imported variables
};

在struct Block_layout 中,我们发现了Block的结构体实现:

  • Block的结构体中存在(void *)类型的指针isa,与类的结构保持一致,所以在很多场景下,Block是可以作为对象来使用的;
  • Block结构体中的descriptor成员变量是一个动态值,我们可以根据Block_layout->flags与枚举值进行&运算来判断对应的内容是否存在,例如Block_layout->flags & BLOCK_HAS_SIGNATURE是否为真来判断该Block是否有方法签名.

1.2 Block的分类

Block类型常用的大概有三类:

  • _NSConcreteGlobalBlock:存储在数据区
  • _NSConcreteStackBlock:存储在栈区
  • _NSConcreteMallocBlock:存储在堆区

一般我们认为,如果Block 本身不捕获任何变量,或者需要布局(layout)的变量的数量为0时,初始化为Block类型为_NSConcreteGlobalBlock;而如果在Block内部引用了外部变量,初始化的Block类型为_NSConcreteStackBlock;而如果对于_NSConcreteStackBlock进行copy操作将其从栈区复制到堆区就生成了_NSConcreteMallocBlock堆区的Block.之所以需要_NSConcreteMallocBlock这类Block,是因为在栈区Block生命周期由系统控制,在其生命周期结束之后,就会被系统释放掉,从而使Block的生命周期不能根据实际的需求由开发者来控制,大大降低了Block的应用范围.在MRC模式下,需要手动copy来完成Block由栈区到堆区的转化,在ARC模式下,在赋值的过程中会自动进行copy操作,所以在ARC模式下_NSConcreteStackBlock的存在只是一个瞬间的存在.

在MRC模式下:

        void(^block_one)(NSString *) = ^(NSString *str){
            NSLog(@"str === %@", str);
        };
        NSLog(@"block_one === %@", block_one);
        NSLog(@"block_one_copy === %@", [block_one copy]);
        
        Person *person = [[Person alloc] init];
        person.name = @"Jack";
        void(^block_two)(void) = ^(){
            NSLog(@"person.name == %@", person.name);
        };
        NSLog(@"block_two ==== %@", block_two);
        
        void(^block_three)(void) = [block_two copy];
        NSLog(@"block_three === %@", block_three);

输出结果:

block_one === <__NSGlobalBlock__: 0x100002080>
block_one_copy === <__NSGlobalBlock__: 0x100001070>
block_two ==== <__NSStackBlock__: 0x7ffeefbff4e8>
block_three === <__NSMallocBlock__: 0x1006244a0>

在MRC模式中,不捕获任何变量初始化Block的是_NSConcreteGlobalBlock类型,该类型的Block经过copy操作之后依然是_NSConcreteGlobalBlock类型;如果在Block内部引用了外部变量,初始化的Block类型是_NSConcreteStackBlock;_NSConcreteStackBlock类型的Block经过copy操作之后生成的变量是_NSConcreteMallocBlock类型的Block.

同样的代码实现,在ARC模式下,输出结果:

block_one === <__NSGlobalBlock__: 0x1000020b8>
block_one_copy === <__NSGlobalBlock__: 0x1000020b8>
block_two ==== <__NSMallocBlock__: 0x100674b90>
block_three === <__NSMallocBlock__: 0x100674b90>

在ARC模式中,不捕获任何变量初始化的Block的是_NSConcreteGlobalBlock类型,该类型的Block经过copy操作之后依然是_NSConcreteGlobalBlock类型;不同的是,如果在Block内部引用了外部变量,初始化的Block类型是_NSConcreteMallocBlock,_NSConcreteMallocBlock类型的Block经过copy操作之后生成的变量依然是_NSConcreteMallocBlock类型的Block.那么在ARC模式下,是不是没有_NSConcreteStackBlock类型的Block呢?其实是有的:

        Person *person = [[Person alloc] init];
        person.name = @"Jack";
        NSLog(@"block == %@", ^(NSString *str){
            NSLog(@"person.name == %@", person.name);
        });

输出结果:

block == <__NSStackBlock__: 0x7ffeefbff4e0>

所以在ARC模式下,是存在_NSConcreteStackBlock类型的Block,只不过是一瞬间的事,然后通过赋值操作"="之后在系统默默进行了copy操作,所以你看到的就是初始化之后就变成了_NSConcreteMallocBlock.

1.3 Block的类究竟是个什么?

Block是个啥,Block的类_NSConcreteGlobalBlock,_NSConcreteStackBlock,_NSConcreteMallocBlock又到底是什么?我们来一个简单的代码验证一下(MRC模式):

        void(^block_global)(NSString *) = ^(NSString *str){
            NSLog(@"str === %@", str);
        };
        
        NSLog(@"_NSConcreteGlobalBlock的继承链:");
        id superClass = object_getClass(block_global);
        while (superClass) {
            NSLog(@"superClass == %@", superClass);
            superClass = class_getSuperclass(superClass);
        }
        
        Person *person = [[Person alloc] init];
        person.name = @"Jack";
        void(^block_stack)(void) = ^(){
            NSLog(@"person.name == %@", person.name);
        };
        NSLog(@"_NSConcreteStackBlock的继承链:");
        superClass = object_getClass(block_stack);
        while (superClass) {
            NSLog(@"superClass == %@", superClass);
            superClass = class_getSuperclass(superClass);
        }

        
        void(^block_malloc)(void) = [block_stack copy];
        NSLog(@"block_malloc === %@", block_malloc);
        NSLog(@"_NSConcreteMallocBlock的继承链:");
        superClass = object_getClass(block_stack);
        while (superClass) {
            NSLog(@"superClass == %@", superClass);
            superClass = class_getSuperclass(superClass);
        }

输出结果:

_NSConcreteGlobalBlock的继承链:
superClass == __NSGlobalBlock__
superClass == __NSGlobalBlock
superClass == NSBlock
superClass == NSObject

_NSConcreteStackBlock的继承链:
superClass == __NSStackBlock__
superClass == __NSStackBlock
superClass == NSBlock
superClass == NSObject

_NSConcreteMallocBlock的继承链:
superClass == __NSStackBlock__
superClass == __NSStackBlock
superClass == NSBlock
superClass == NSObject

所以,其实Block不单单是一个结构体,更是NSBlock的子类,而NSBlock是NSObject的基类.如果你在某些地方看到了NSBlock这个类,请不要奇怪,例如在著名的JSPatch里.

2.捕获基本数据类型变量

Block是一个很神奇的存在,它不仅可以在自己的作用域内定义属于自己的变量,还可以捕获外部作用域内的其他变量.对于基本数据变量和对象类型变量我们分开做讨论。这篇文章我们只讨论基本的数据类型的捕获,以下讨论中未特殊说明的捕获变量,均为基本数据类型(以整形为例),这种捕获大致可以分为两种情况:

2.1  Block捕获静态变量或者全局变量

当Block捕获了局部静态变量时,由于局部静态变量存储在全局区,虽然并不是对所有的函数都可见,但是在Block所在的区域内相当于一个作用域内的全局变量.

[示例代码2.1.1]

int main(int argc, const char * argv[]) {
        @autoreleasepool {
            static int i = 10;
            void (^block)(void) = ^{
                NSLog(@"Inside block, i = %d", i);
            };
            i = 15;
            block();
        }
    return 0;
}

当Block捕获了全局变量时,由于全局变量的作用域是全局可见的,存储在全局区,所以对于全局来讲都只能访问唯一的一份内存空间,Block也不例外.所以对于这类变量来讲,在block内部既可以使用也可以修改,而且修改是同步的.

[实例代码2.1.2]

int i = 10;
int main(int argc, const char * argv[]) {
        @autoreleasepool {
            void (^block)(void) = ^{
                NSLog(@"Inside block, i = %d", i);
            };
            i = 15;
            block();
        }
    return 0;
}

以上两种情况执行结果:

Inside block, i = 15

 

2.2 Block捕获局部变量

2.2.1 只需要使用,而不需要修改该变量的值

在这种情况下,Block内部只能在其作用域使用该变量,而不能修改该变量的值.外部的局部变量改变时不会影响block内部的值,Block只是捕获了定义Block时变量的值.

[示例代码2.2.1]

int i = 10;
void (^block)(void) = ^{
    NSLog(@"Inside block, i = %d", i);
     };
i = 15;
block();

执行结果依然是:

Inside block, i = 10

2.2.2 需要在Block作用域内修改外部局部变量的值

如果需要在Block内可以修改外部局部变量的值,并同步改变外部变量的值,可以只用__block来修饰变量.

[示例代码2.2.2]

__block int i = 10;
void (^block)(void) = ^{
    NSLog(@"Inside block, i = %d", i);
     };
i = 15;
block();

执行结果:

Inside block, i = 15

 

3. 捕获变量时发生了什么?

Block在捕获变量时,都做了哪些操作,为什么有些变量可以修改,而有些变量不可以修改呢?为了探究这一过程我们可以使用clang命令将OC代码转化为C++代码,来看一下针对不同的变量底层到底做了哪些操作.这里我们只讨论捕获变量时Block内外变量值变化之间的影响,不讨论捕获对于对象类变量生命周期产生的影响.

clang -rewrite-objc sourceFile -o destionationFile

3.1 捕获局部静态变量

使用上边的命令我们将[示例代码2.1.1]进行重写只展示主要的实现代码(代码组织方式和顺序位置有改动):

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

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};



struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *i = __cself->i; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_60c225_mii_0, (*i));
            }

转化之后的实现中,有三个结构体和一个实现函数:

1.__block_impl:该结构体是Block的基本实现结构,包含了:

  • void * isa:这是一个void *类型的指针,也正是因为有了这个结构才使得Block与类的结构相似,所以在OC中,在很多情况下Block可以作为类对象来对待.Block的isa指针有三种取值,_NSConcreteStackBlock,_NSConcreteGlobalBlock, _NSConcreteMallocBlock.
  • int Flags:这是一个Block信息标识位.Block被引用的次数,是否包含签名信息,是否有延展结构等都需要该标志位;
  • int Reserved:这是一个预留信息位,默认为0;
  • void *FunPtr:这是一个void *类型的通用指针变量,该变量主要用来存储真正的实现函数的地址.

2.__main_block_desc_0:该结构体主要保存Block的描述信息,主要包含以下部分:

  • size_t reserved:预留信息位,默认为0;
  • size_t Block_size:Block占据的物理空间大小,以字节为单位.

3. __main_block_impl_0:该结构体是完整的Block实现,将上述两个结构体的信息结合在一起,同时添加了需要捕获的外部变量:

  • struct __block_impl impl:Block的主要实现结构;
  • struct __main_block_desc_0* Desc:Block的主要描述结构;
  • 捕获的变量.

4. static void __main_block_func_0(struct __main_block_impl_0 *__cself):该函数是Block操作的实现函数,也就是你要使用Block做什么样的操作都会包含在该实现里(例如实例中我们想要打印一些信息的操作).

int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

            static int i = 10;
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i));
            i = 15;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
    return 0;
}

在main()函数的实现中出现了很多的强制转化,看起来有点凌乱,我们做一下拆分将各个步骤转化为单步骤执行:

int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

            static int i = 10;

            //1.定义一个struct __main_block_impl_0结构体的实例mainBlockImp0
            struct __main_block_impl_0 mainBlockImp0 = __main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &i);

            //2. 将mainBlockImp0转化为struct __main_block_impl_0 *类型的结构体指针
            struct __main_block_impl_0 *mainBlockImp0_pointer = &mainBlockImp0;

            //3.将mainBlockImp0_pointer转化为(void (*)(void))类型的指针block
            void (*block)(void) = ((void (*)()) mainBlockImp0_pointer);
      
            i = 15;
            //4. 将block强制转化为struct __block_impl *类型的结构体指针
            __block_impl *block_impl = ((__block_impl *)block);

            //5.获取block_impl中的实现函数FunPtr
              void (*FunPtr)(__block_impl *) = (void (*)(__block_impl *))block_impl->FuncPtr;

            //6.执行FunPtr
            FunPtr(block_impl);
        }
    return 0;
}

拆分之后是不是更加清晰了.对于上边的步骤,或许会有人对于[5.获取block_impl中的实现函数FunPtr]中的操作不太理解,为啥原始的函数的参数类型是(struct __main_block_impl_ *),而获取函数时参数的类型确是(struct __block_impl *)?

首先确定一点,就是这种转化是肯定没有问题的,因为实际传递的参数block_impl本身就是由mainBlockImp0转化而来的,而mainBlockImp0是由结构体struct __main_block_impl_0初始化出来的.而对于为什么实现函数的参数要转化成(struct __block_impl *)类型,个人觉得虽然block_impl是由mainBlockImp0转化而来的,block_impl和&mainBlockImp0指向的是相同的内存地址,但毕竟block_impl是最新的变量,能用最新的肯定不用旧的,更何况这里其实传什么类型的指针都是不影响的,只要使用的时候使用正确的类型去进行操作就可以了.

回到我们最初的讨论,为什么Block捕获的局部静态变量是可以在内部被修改的呢?

在上述实现代码中,我们发现了在Block结构体初始化时,将全局的静态变量使用地址传递的方式传递给了结构体,

struct __main_block_impl_0 mainBlockImp0 = __main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, &i);

而在实现函数的执行中,取值使用的是指针寻址,赋值也是直接将值填充到变量对应的内存空间,Block内外操作的都是同一块内存空间,所以Block对于静态变量的使用权限与普通方法一样,可以直接获取变量的值,也可以进行修改变量的值.

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int *i = __cself->i; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_60c225_mii_0, (*i));
            }

结论是:当Block捕获了局部静态变量时,传入Block内部的是该变量的指针引用,所以在Block内部可以通过变量指针修改局部静态变量的值.

3.2 捕获局全部变量[普通全局变量和静态全局变量]

[严格意义上讲,Block并没有捕获这个变量,这样的变量本身所有的方法就具有相同的使用权限]

我们使用clang命令将[示例代码2.1.2]进行重写,

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


int i = 10;

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;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_a5d611_mii_0, i);
            }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};

在重写后的实现中我们看到,如果Block捕获了全局变量,被捕获全局变量并没有在Block的构建过程中进行参数传递,由于全局变量存储在全局区,对所有的方法都是可见的,全局使用的都是同一份这样的变量,所有在所有的方法都可以访问得到,也都具有相同的权限做修改,所以在一定程度上全局变量不是很安全(所以如果不想被修改可以使用常量).

结论是:如果Block捕获了全局变量,Block并没有对全局变量做什么操作,它只是恰好跟其他方法具有相同的数据访问权限.

3.3 捕获局部变量

3.3.1 捕获不使用任何修饰符的局部变量

我们使用clang命令将[示例代码2.2.1]进行重写,

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

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  int i = __cself->i; // bound by copy

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_28d478_mii_0, i);
            }

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};



int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            int i = 10;
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, i));
            i = 15;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
    return 0;
}

在这部分实现中,我们可以清楚地看到:在初始化Block时struct __main_block_impl_0时,没有任何修饰符号的外部局部变量是一个值引用被做参数传递给Block的,

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

在Block中创建了一个新的临时变量的来接收传入的变量值,这样临时的变量与外部的临时变量是两个完全独立的变量,在存储在不同的空间,彼此之间不产生影响.

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int i;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _i, int flags=0) : i(_i) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

所以,在这种情况下Block内部的变量是独立于外部局部变量存在的,所以即使Block内部的变量发生了变化,也不会对外部的局部变量产生任何影响.

结论是:如果Block捕获了没有任何修饰符的局部变量,是通过值传递的方式来传递参数的,在Block内部重新创建了一个新的变量来接收这个值,这两个变量存储在不同的空间,相互之间的改变不会对另一个变量产生影响.

3.3.2 捕获使用__block修饰的的局部变量

我们继续使用clang命令将[示例代码2.2.2]进行重写,

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

struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_i_0 *i; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  __Block_byref_i_0 *i = __cself->i; // bound by ref

                NSLog((NSString *)&__NSConstantStringImpl__var_folders_hz_vhd445sx35q8gbfkzf7ljrg40000gp_T_main_97ee31_mii_0, (i->__forwarding->i));
            }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};



int main(int argc, const char * argv[]) {
        /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
            __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 10};
            void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344));
            (i.__forwarding->i) = 15;
            ((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
        }
    return 0;
}

这个实现如之前的实现,我们发现了明显的区别:

  • 实现中出现了新的结构体struct __Block_byref_i_0:这个新增加的结构体是用来存储我们使用__block修饰的局部变量的,在这个结构体被初始化之后,所有对于局部变量的操作都会借助于结构体实现.同时可以看到原始的局部变量会被编译器优化掉,直接将被__block修饰的局部变量的值赋给该结构体的变量;[这个结构体的实现跟struct __main_block_desc_0很相似, 结构体的成员变量会根据实际的需求有所变化,比如如果捕获的局部变量是对象类型这个结构体会对出两个函数类型的成员变量,这里不做深入讨论]
struct __Block_byref_i_0 {
  void *__isa;
__Block_byref_i_0 *__forwarding;
 int __flags;
 int __size;
 int i;
};
  • 在Block实现中描述结构体struct __main_block_desc_0中多出了两个新的成员变量,用来存储新增加的两个实现函数copy和dispose:1. copy函数的作用,这是因为当Block捕获了变量时,需要根据局部变量的修饰符号来确定是对该变量引用方式,即对外边变量产生强引用或者弱引用,可以理解为Block内部会对捕获的对象变量进行引用计算器的操作,来影响对象的生命周期.2. dispose函数的作用:主要作用是在Block之行结束之后用来释放掉copy函数对于引用对象的操作,消除对于引用对象声明周期的影响,使得被引用的对象能够按照原来的生命周期正常释放,而不被Block影响.
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};

正是由于被__block修饰的局部变量使用了新的结构体(struct __Block_byref_i_0)进行存储,然后以地址传值的方式传递给Block的实例,在后续的操作中所有对于__block修饰变量的操作都是基于__Block_byref_i_0实例来进行操作的,所以在Block的作用域内可以对这个局部变量的值进行修改.[实际上是生成了新的变量来代替原来的局部变量并用局部变量的值来进行初始化,在以后的操作中都使用了新的变量来进行操作而已]

结论是:如果Block捕获了使用__block修饰的局部变量,则在实现中会生成一个新的结构体,使用新生成的结构体的成员变量来存储局部变量的值,并对该结构中成员变量的生命周期进行管理.而在以后的操作中,涉及到该变量的操作都会使用新生成的结构体来完成,所以看起来的效果是在Block内部可以修改使用__block修饰的局部变量的值.

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值