阅读Objective-C高级编程+iOS与OS+X多线程和内存管理 之对Block的理解之Block截获变量与__block

Block截获变量

这次来讲解截获自动变量值,跟上一节一样我们先通过clang来进行代码转换,转换后的代码为

//源代码
int main(){
    int val = 10;
    const char *fm = "val = %d\n";
    void (^blk) (void) = ^{
        printf(fm,val);
    };
    blk();
    return 0;
}
//由于转换后有许多跟Block无关的代码所以这里只粘贴处我们需要的代码
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;
  const char *fm;
  int val;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fm, int _val, int flags=0) : fm(_fm), val(_val) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  const char *fm = __cself->fm; // bound by copy
  int val = __cself->val; // bound by copy

        printf(fm,val);
    }

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 val = 10;
    const char *fm = "val = %d\n";
    void (*blk) (void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, fm, val));
    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    return 0;
}

转换后我们可以看到在__main_block_impl_0结构体中新增了两个成员,它们就是在block表达式中使用到的两个自动变量。
接下来我们看__main_block_impl_0实例的构造函数,根据初始化列表来初始化字段

__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fm, int _val, int flags=0) : fm(_fm), val(_val)
/*
上面这段代码就是C++ 类的构造函数 使用初始化列表来初始化字段
*/
//例如
class Line
{
   public:
      void setLength( double len );
      double getLength( void );
      Line(double len);  // 这是构造函数
 
   private:
      double length;
};
/*-----------下面是构造函数--------------*/
Line::Line( double len): length(len)
{
    cout << "Object is being created, length = " << len << endl;
}
//上面这段代码就相当于
Line::Line( double len)
{
    length = len;
    cout << "Object is being created, length = " << len << endl;
}

通过上面的讲解我们就可以了解到使用的外界自动变量的block跟上一节所说的简单block的初始化只多了对新增成员变量的赋值。它的初始化方法也可以写成

struct __main_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr; //函数指针
    const *fm;
    int val;
    struct __main_block_desc_0 *desc
    };
    //初始化以后
    isa = &_NSConcreteStackBlock;
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    fm = "val = %d\n"; 
    val = 10;
    /*
     blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, fm, val);
   它初始化也就是去外界自动变量的值来给结构体成员变量赋值。
    */

看完了block定义的初始化,然后我们再来看一下block的实现

 ^{ printf(fm,val);};
 //转换后
 static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
        const char *fm = __cself->fm; // bound by copy
        int val = __cself->val; // bound by copy
        printf(fm,val);
    }

从转换后的代码我们可以看到在block的实现里面使用的值是我们在初始化block结构体是所使用的值。这也就可以看出截获变量就是在代码自上而下运行时将block所使用的外部的自动变量赋值给block结构体的内部成员变量。

__block

我们都知道block在实现的时候不能修改被截获的自动变量的值,但有的时候我们有需要对截获的自动变量的值进行修改,这样就会出现问题。为了解决的这个问题现在有两种方法。第一种方法:被截获的自动变量我们可以使用

  • 静态变量
  • 静态全局变量
  • 全局变量
    第二种方法:使用__block来修饰被截获的自动变量。
下面我们先说第一种方法
int  global_val = 1;
static int static_global_val = 2;
int main(){
   static int static_val = 3;
    void (^blk) (void) = ^{
        global_val = 4;
        static_global_val = 5;
        static_val = 6;
    };
    return 0;
}

上面的方法我们在block的实现中改写了全局变量global_val,静态全局变量static_global_val和静态变量static_val的值。经过clang转换后代码为

int global_val = 1;
static int static_global_val = 2;

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

        global_val = 4;
        static_global_val = 5;
        (*static_val) = 6;
    }

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(){
   static int static_val = 3;
    blk = &__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA, &static_val));    return 0;
}

从上面可以看出对全局变量和静态全局变量的使用跟转换前是一样的,对静态变量的使用我们单提出来看一下

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

它使用的是静态变量static_val的指针对其进行操作,它是将静态变量static_val的指针(地址传递给)__main_block_impl_0结构体进行保存。这样也存在另外一个问题,那就是在超出其作用域以后自动变量将被废弃,也就是不能再使用指针对其进行操作。

第二种方法 __block

我们先来看一下什么是 __block修饰符,其更准确地表达方式应该为“__block存储域类说明符”。
我们来看一下用__block说明符修饰的变量的 OC表达式

__block int val = 10;
void (^blk) (void) = ^{ val = 1;};

转换后我们可以看到被__block修饰的变量被转换成一个结构体类型

struct __Block_byref_val_0 {
  void *__isa;
__Block_byref_val_0 *__forwarding;
 int __flags;
 int __size;
 int val;
};
//它的赋值为
__Block_byref_val_0 val = {
  0,
  &val,
  0,
  sizeof(__Block_byref_val_0),
  10
}

从上可以看出该结构体内有一成员变量相当于原来的自动变量,

下面我们看一下block实现中对__block变量的赋值

^{val = 1;};
//改代码转换成
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_val_0 *val = __cself->val; // bound by ref
    (val->__forwarding->val) = 1;
    }

从上面代码我们一样也可以看出__main_block_impl_0结构体实例中有一个指针类型的成员变量指向想了__block变量的实例指针val。
我们从__Block_byref_val_0的初始化赋值可以看到__Block_byref_val_0实例有一个指向自身的指针类型的成员变量__forwarding。
另外__Block_byref_val_0结构体并不在Block __main_block_impl_0结构体当中,它只是有一个指针指向了__Block_byref_val_0结构体。

Block的存储域

Block的存储域分为

  • _NSConcreteGlobalBlock 配置在程序的数据区域
  • _NSConreteStackBlock 配置在栈上
  • _NSConreteMallocBlock 配置在堆上

基数全局变量的地方有Block语法时、Block语法的表达式中不使用应截获的自动变量时,在上面的情况下Block呗配置在程序的数据区域。除此之外的语法生成的Block配置在栈上。

Blocks提供了将Block和__block变量从栈上复制到堆上的方法来解决超出作用域消失的问题。
因为Block结构体实例的一个指针变量指向__block变量,当Block被复制到堆上以后该指针也被复制到堆上,当超出作用域以后不会被消除。所以__block变量所在的内存有指针指向所以不会被释放,这样也就做到超出作用域还能使用的功能。

总结Block截获特性和截获变量的特性

  1. 对于基本数据类型的局部变量截获其值
  2. 对于对象类型的局部变量连同所有权修饰符一起截获
  3. 对于静态局部变量一直镇形式截获
  4. 对全局变量不接货

__block修饰符的使用

  • 一般情况对被截获的变量进行赋值操作的时候需要添加__block修饰符
  • 静态局部变量、全局变量、静态全部变量不需要使用__block修饰符

你可能感兴趣的:(阅读Objective-C高级编程+iOS与OS+X多线程和内存管理 之对Block的理解之Block截获变量与__block)