美文网首页
Block的深入学习

Block的深入学习

作者: 褪而未变 | 来源:发表于2017-08-02 19:24 被阅读0次

(一)Block基础回顾

1.Block定义

带有局部变量的匿名函数,差不多就与C语言中的函数指针类似,可以当做参数传来传去,而且可以没有名字。

2.Block语法完整的形式的Block语法如下

参数类型(^函数名)(形参) = ^(实参){ /代码块/};

并且与一般的C语言函数定义相比,仅有两点不同:
(1)没有函数名
(2)带有"^"符号所以根据前面的语法格式可以写出如下例子:
^int(int count) {return count+1};

当然,也可以有很多的省略格式,省略返回值如下
^(int count) {return count+1};

省略返回值类型时,如果表达式中有return语句时,block语句的的返回值类型就使用return返回的类型;如果return中没有返回类型,就使用void类型。再省略参数列表,参数列表和返回值都省略是最简洁的方式,同时将参数和返回值省略如下:
^{printf("good!");}

3.Block类型变量在Block语法下,一旦使用了Block语法就相当于生成了可赋值给Block类型变量的值,"Block"既指源代码中的Block语法,也指由Block语法所生成的值即:

int (^blk)(int) = ^(int count){return count +1};
int (^blk1)(int) = blk;
int (^blk2)(int);
blk2 = blk1;

从上面看出,Block确实代表了一种语法,但在这里,对于blk,他也是一个Block类型变量的值。但是,当Block作为函数的参数或者返回 值的时候若传来传去,写法上难免有点复杂,毕竟都是那么长一串儿,此时,就可以像C语言那样使用typedef了:
typdef int(^blk_t)(int);

这时,blk_t就变成了一种Block类型了,例如:
typef int(^blk_t)(int);
blk_t bk = ^(int count){return count+1};//很明显省略了返回值

4.截获的自动变量(自动变量==局部变量)

通过前面的知识,我们已经大部分理解了Block了,这里引入截获的自动变量,什么是截获的局部变量?先看一段代码:

int main() 
{
     int dmy = 256;
     int val = 10; 
     const char *fmt = "val = %d\n";
    void (^blk)(void) = ^{printf(fmt, val);}; 
    val = 2; 
    fmt = "These values were changed. val = %d\n"; 
    blk(); 
    return 0;
}

执行结果:val = 10

解释:在该源代码中,Block语法的表达式使用的是它之前声明的自动变量fmt 和val.Block语法中,Block表达式截获的自动变量,即保存该自动变量瞬间的值。因为Block表达式保存了自动变量的值,所以在执行Block语法之后,即使改变Block中的自动变量的值也不会影响Block执行时自动变量的值。这就是所谓的截获

5._ _block修饰符咱们来尝试着,在Block中修改自动变量的值:

int val = 0;
      void (^blk)(void) = ^{val = 1;};
      blk();
      printf("val = %d\n", val);
      ```
执行结果:`error: variable is not assignable (missing __block type specifier) void (^blk)(void) = ^{val = 1;};~~~ ^`

很显然,光这样的话是不允许在Block内部修改外面的自动变量的值的。如果强势要改呢,所以这会儿就该 __block出场了:若想在Block语法的表达式中将赋值给在Block语法外声明的自动变量,需要在该自动变量上加上 _block修饰符,如下:
```objectivec
__block int val = 0;
  void (^blk)(void) = ^{val = 1;};
  blk();
 printf("val is %d",val);

执行结果:val is 1

所以,使用 _block修饰的变量,就可以在Block语法内部进行修改了,该变量称为 _block变量。但这里还有另一种情况,见如下代码:

id array = [[NSMutableArray alloc] init]; 
void (^blk)(void) = ^{id obj = [[NSObject alloc] init];
[array addObject:obj]; };

这会出错吗?其实是不会的,咱们在这里是没有向arry赋值,向他赋值才会产生编译错误,在这里,咱们截获到了NSMutableArray类对象的一个结构体指针(后面会讲),咱们没有对它赋值,只是使用而已,所以不会出错。

(二)Block存储域

( _NSConcreteStackBlock,_NSConcreteGlobalBlock,_NSConcreteMallocBlock)

通过前面的学习,了解到Block转换为Block的结构体类型的自动变量,__block修饰符修饰的变量转换为block变量的结构体类型的自动变量,所谓结构体类型的自动变量,即栈上生成的该结构体的实例变量。:

根据咱们之前提到的,其实Block也是一种对象,并且Block的类是_NSConcreteStackBlock,和他类似的还有两个如:

  • _NSConcreteMallocBlock
  • _NSConcreteGlobalBlock三个不同的类名称决定了三个Block类生成的Block对象存在内存中的位置:

到目前为止,出现的Block例子都是_NSConcreteStackBlock类,所以都是设置在栈上,但实际上并非是这样,在记述全局变量的地方使用Block语法时生成的Block为_NSConcreteGlobalBlock对于Block对象分配在数据区的情况,略过分析过程,直接总结:

  • 当把Block声明为全局变量的时候,Block分配在数据区:
void (^blk)(void) = ^{printf("Global Block\n");};
int main() {}
  • Block语法表达式中不使用截获的自动变量的时候:
typedef int (^blk_t)(int);
for (int rate = 0; rate < 10; ++rate) { 
blk_t blk = ^(int count){return count;};
}

以上两种情况,Block分配在数据区。最后一个问题,什么时候Block会分配在堆上呢?此时可以引出之前说的一个问题,“Block可以超出变量作用域而存在”,换句话说就是,Block倘若作为一个局部变量存在,结果他居然可以在超出作用域之后不被废弃,同样的,由于block修饰的变量也是放在栈上的,如果其变量作用域结束,那么block修饰符修饰的变量也应该结束。解决方案如下:
将Block和__block修饰的变量从栈上复制到堆上来解决,将配置在栈上的Block复制到堆上,这样即使Block语法记述的变量作用域结束时,堆上的Block还可以继续存在

复制到堆上之后,将Block内部的isa成员变量进行改变:
impl.isa = &_NSConcreteMallocBlock;

当ARC有效时,大多数情况下编译器会进行恰当地进行判断,自动生成将栈上复制到堆上的代码,并且最后复制到堆上的Block会自动的加入到autoRealeasePool中,编译器不能进行判断的情况便是:

向方法或函数的参数中传递Block时但是在向方法或函数的参数中传递Block时也有不需要手动复制的情况如下:

  • Cocoa框架的方法且方法名中含有usingBlock等时
  • GCD中的API

举个栗子:在使用NSArray类的enumeratObjectsUsingBlock实例方法以及dispatch_async函数时,不用手动复制,相反的,在NSArray类的initWithObjects实例方法上传递时需要手动复制,看代码:

- (id) getBlockArray {
    int val = 10;
    return [[NSArray alloc] initWithObjects: ^{NSLog(@"blk0:%d", val);},  ^{NSLog(@"blk1:%d", val);}, nil];
 }接下来,调用:
id obj = getBlockArray();
      typedef void (^blk_t)(void);
      blk_t blk = (blk_t)[obj objectAtIndex:0];
      ```

      blk(); 结果就是Block在执行时发生异常,应用程序强制结束,这是由于在getBlockArray函数执行结束时,栈上的Block被废弃的缘故。而此时编译器恰好又不能判断是否需要复制。 注:但将Block从栈上复制到堆上是相当消耗CPU的,当Block设置在栈上也能够使用时,将Block从栈上复制到堆上只是在浪费CPU资源,能少复制,尽量少复制。
将以上代码修改一下即可运行:

```objectivec
- (id) getBlockArray {
   int val = 10;
   return [[NSArray alloc] initWithObjects: [^{NSLog(@"blk0:%d", val);} copy], [^{NSLog(@"blk1:%d", val);} copy], nil];
}

小结:



(三)block变量存储域(Block移动对block变量的影响)

使用block变量的Block从栈复制到堆上时,block修饰的变量也会受到影响。

  • 1.多个Block使用一个block变量时,因为会将所有的Block配置在栈上,所以block变量也会配置在栈上,其中任何一个Block从栈复制到堆时,block变量也会一并从栈复制到堆并被该Block持有,当剩下的Block从栈复制到堆时,被复制的Block会依次持有block变量,并增加__block变量的引用计数。
  • 2.在这里,读者可以采用Objective-C的引用计数的方式来考虑。使用block变量的Block持有block变量,如果Block被废弃,它所持有的block变量也就被释放在这里,回到之前讲到的“block变量使用结构体成员变量forwarding的原因”,不管block变量配置在栈上还是在堆上,都能够正确的访问该变量(通过指针),通过Block的复制,block变量也从栈复制到堆,此时可同时访问栈上的block变量和堆上的block变量,下面解释一下:看代码:
__block int val = 0;
   void (^blk)(void) = [^{++val;} copy];
   ++val;
   blk();
   NSLog(@"%d", val);
结果是:2
^{++val;}和++val;都可以转化为++(val.__forwarding->val);

在变换Block语法的函数中,该变量val为复制到堆上的block变量结构体实例,而另外一个(++val)与Block无关的变量val,为复制前栈上的block变量结构体实例。但是栈上的block变量结构体实例在block变量从栈复制到堆上时,会将成员变量forwarding指针替换为复制目标堆上的block变量用结构体实例的地址

下面总结栈上的Block复制到堆的情况:

  • 调用Block的copy实例方法时
  • 将Block作为函数返回值时
  • 将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时
  • 在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中传递Block时

在调用Block的copy方法时,如果Block配置在栈上,那么该Block会从栈上赋值到堆;将Block作为函数返回值时、将Block赋值给附有__strong修饰符id类型的类或者Block类型成员变量时,编译器将自动地将对象的Block作为参数并调用_Block_copy函数,这与调用Block的copy实例方法的效果相同;在方法名中含有usingBlock的Cocoa框架方法或者GCD的API中传递Block时,在该方法或函数内部对传递过来的Block调用Block的copy实例方法或者_Block_copy函数。

相关文章

  • hbase资料收集

    一、HBase 0.94.1 block-cache 理解 二、HBase深入学习(1) 三、HBase深入学习(...

  • Block深入学习

    Block 带有自动变量和相关匿名函数的对象。 为什么出现Block,和block相比的就是函数,自带了执行上下文...

  • 深入学习Block

    深入学习block 首先,什么是block?block其实就是一个代码块,把你想要执行的代码封装在这个代码块里,等...

  • Block 深入学习

    Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。--...

  • Block的深入学习

    (一)Block基础回顾 1.Block定义 带有局部变量的匿名函数,差不多就与C语言中的函数指针类似,可以当做参...

  • iOS Block - 深入学习篇

    前面写了一篇Block开发中的简单使用,这篇文章将深入的学习一下Block和开发中的一些使用。 目录 Block的...

  • Block深入学习笔记

    前言 block是日常iOS开发高频率使用的闭包,之前也看过不少文章,但是一直疏于总结,今日再次深入研究一下,并记...

  • 深入 Block

    (转自https://github.com/oa414/objc-zen-book-cn 禅与 Objective...

  • 深入 Block

    Block 前言 Block是OC中对C语言的扩展功能,是一种带有自动变量的匿名函数,Block在OC中的实现,点...

  • Block深入学习 —— Block与各种变量

    https://juejin.im/post/5aa0e70bf265da239866d527

网友评论

      本文标题:Block的深入学习

      本文链接:https://www.haomeiwen.com/subject/jemylxtx.html