《Objective-C高级编程 iOS与OS X多线程与内存管

2019-01-12  本文已影响12人  我才是臭吉吉

Blocks篇:3.Blocks使用捕获到的变量

所谓Blocks捕获变量,即在Block函数体内使用外部声明的变量。

1. 捕获局部变量(自动变量)和静态局部变量

// main.m
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        int uselessVal = 1;
        // 局部变量
        int val = 3;
        // 静态局部变量
        static int staticVal = 4;
        
        void (^myBlock)(void) = ^{
            // 捕获val和staticVal使用
            printf("val = %d\n", val);
            printf("staticVal = %d\n", staticVal);
        };
        myBlock();
    }
    return 0;
}

转换后的C++代码部分如下:

// main.cpp

/** Block的完整结构体声明 */
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int val; // 内部捕获的局部变量
    int *staticVal; // 内部捕获的静态局部变量
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int *_staticVal, int flags=0) : val(_val), staticVal(_staticVal) {
        impl.isa = &_NSConcreteStackBlock; // 这里只按照MRC编译确定,ARC则不同
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

/** Block中函数体转换生成的C函数 */
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    // 获取Block结构体实例中捕获的变量
    int val = __cself->val; // bound by copy
    int *staticVal = __cself->staticVal; // bound by copy
    printf("val = %d\n", val);
    printf("staticVal = %d\n", (*staticVal));
}


// main函数
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        int uselessVal = 1;
        int val = 3;
        static int staticVal = 4;

        void (*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val, &staticVal)); // 初始化时赋值,将要捕获的变量传递进去
        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}

以上可以看出:

  1. 只有Block内部使用的变量才会被捕获;
  2. 普通的局部变量是通过值传递方式进行捕获,静态局部变量是通过指针传递方式进行的;
    • 原因:
      1. 由于C函数的声明和实现是在原局部变量的生命周期之外(这里是main函数外部),使用时原变量早已被释放,故需要直接将值保存到Block结构体实例中;
      2. 对于静态局部变量,由于静态变量是存储在专门的数据区(也就可以理解为全局),在C函数中也可以直接访问,故只需将其指针保存在Block结构体实例中即可。
  3. C函数内部使用时,通过传递进去的Block指针取出被捕获的变量值。
  4. 补充:在ARC环境下,对于Block的类型(isa)
    • 捕获普通局部变量后,在运行时,系统会将Block拷贝到堆上,变为_NSConcreteMallocBlock
    • 捕获静态局部变量,虽然使用,实际也只是记录该变量的指针,Block为_NSConcreteGlobalBlock
    • 没有捕获任何变量的Block为_NSConcreteGlobalBlock类型

2. 捕获全局变量和全局静态变量

// main.m

int globalVal = 3;
static int globalStaticVal = 4;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^myBlock)(void) = ^{
            printf("globalVal = %d\n", globalVal);
            printf("globalStaticVal = %d\n", globalStaticVal);
        };
        myBlock();
    }
    return 0;
}

转换后的C++部分代码为:

// main.cpp

int globalVal = 3;
static int globalStaticVal = 4;

/** Block完整结构体声明 */
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) {
    // 直接使用
    printf("globalVal = %d\n", globalVal);
    printf("globalStaticVal = %d\n", globalStaticVal);
}

可以看到,

  1. 对于全局变量(包含静态全局变量),由于在Block的C函数中可以直接访问,故无需在Block结构中对其进行额外的保存工作
  2. 在ARC环境下,Block的类型(isa)为_NSConcreteGlobalBlock
上一篇下一篇

猜你喜欢

热点阅读