block的内部实现

主要内容:

一、block相关的题目

二、block的定义

三、block的实现

四、捕获自动变量值

五、block存储区域

六、截获对象

一、block相关的题目

这是一篇比较长的博文,前部分是block的测试题目,中间是block的语法、特性,block讲解block内部实现和block存储位置,请读者耐心阅读。具备block基础的同学,直接调转到block的实现

下面列出了五道题,看看能否答对两三个。主要涉及block栈上、还是堆上、怎么捕获变量。答案在博文最后一行

[objc] view plaincopy

  1. //-----------第一道题:--------------
  2. void exampleA() {
  3. char a = ‘A‘;
  4. ^{ printf("%c\n", a);};
  5. }
  6. A.始终能够正常运行          B.只有在使用ARC的情况下才能正常运行
  7. C.不使用ARC才能正常运行     D.永远无法正常运行
  8. //-----------第二道题:答案同第一题--------------
  9. void exampleB_addBlockToArray(NSMutableArray *array) {
  10. char b = ‘B‘;
  11. [array addObject:^{printf("%c\n", b);}];
  12. }
  13. void exampleB() {
  14. NSMutableArray *array = [NSMutableArray array];
  15. exampleB_addBlockToArray(array);
  16. void (^block)() = [array objectAtIndex:0];
  17. block();
  18. }
  19. //-----------第三道题:答案同第一题--------------
  20. void exampleC_addBlockToArray(NSMutableArray *array) {
  21. [array addObject:^{printf("C\n");}];
  22. }
  23. void exampleC() {
  24. NSMutableArray *array = [NSMutableArray array];
  25. exampleC_addBlockToArray(array);
  26. void (^block)() = [array objectAtIndex:0];
  27. block();
  28. }
  29. //-----------第四道题:答案同第一题--------------
  30. typedef void (^dBlock)();
  31. dBlock exampleD_getBlock() {
  32. char d = ‘D‘;
  33. return ^{printf("%c\n", d);};
  34. }
  35. void exampleD() {
  36. exampleD_getBlock()();
  37. }
  38. //-----------第五道题:答案同第一题--------------
  39. typedef void (^eBlock)();
  40. eBlock exampleE_getBlock() {
  41. char e = ‘E‘;
  42. void (^block)() = ^{printf("%c\n", e);};
  43. return block;
  44. }
  45. void exampleE() {
  46. eBlock block = exampleE_getBlock();
  47. block();
  48. }

二、block的定义

Block是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。命名就是工作的本质,函数名、变量名、方法名、属性名、类名和框架名都必须具备。而能够编写不带名称的函数对程序员来说相当有吸引力。

例如:我们要进行一个URL的请求。那么请求结果以何种方式通知调用者呢?通常是经过代理(delegate)但是,写delegate本身就是成本,我们需要写类、方法等等。

这时候,我们就用到了block。block提供了类似由C++和OC类生成实例或对象来保持变量值的方法。像这样使用block可以不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量,仅用编写C语言函数的源码量即可使用带有自动变量值的匿名函数。

其他语言中也有block概念。点击查看官方block语法文档

三、block的实现

block的语法看上去好像很特别,但实际上是作为极为普通的C语言代码来处理的。这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。

控制台命令是: clang -rewrite-objc 源代码文件名。

[objc] view plaincopy

  1. int main(){
  2. void (^blk)(void) = ^{printf("block\n");};
  3. blk();
  4. return 0;
  5. }

经过 clang -rewrite-objc 之后,代码编程这样了(简化后代码,读者可以搜索关键字在生成文件中查找):

[objc] view plaincopy

  1. struct __block_impl{
  2. voidvoid *isa;
  3. int Flags;
  4. int Reserved;
  5. voidvoid *FuncPtr;
  6. };
  7. static struct __main_block_desc_0{
  8. unsigned long reserved;
  9. unsigned long Block_size
  10. }__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  11. struct __main_block_impl_0{
  12. struct __block_impl impl;
  13. struct __main_block_desc_0 *Desc;
  14. }
  15. static struct __main_block_func_0(struct __main_block_impl_0 *__cself)
  16. {
  17. printf("block\n");
  18. }
  19. int main(){
  20. struct __main_block_impl_0 *blk =  &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);
  21. (*blk->impl.FuncPtr)(blk);
  22. }

很多结构体,很多下划线的变量和函数名。我们一个个来:

__block_impl:更像一个block的基类,所有block都具备这些字段。
__main_block_impl_0:block变量。
__main_block_func_0:虽然,block叫,匿名函数。但是,这个函数还是被编译器起了个名字。
__main_block_desc_0:block的描述,注意,他有一个实例__main_block_desc_0_DATA

上述命名是有规则的:main是block所在函数的名字,后缀0则是这个函数中的第0个block。由于上面是C++的代码,可以将__main_block_impl_0的结构体总结一下,得到如下形式:

[objc] view plaincopy

  1. __main_block_impl_0{
  2. voidvoid *isa;
  3. int Flags;
  4. int Reserved;
  5. voidvoid *FuncPtr;
  6. struct __main_block_desc_0 *Desc;
  7. }

总结:所谓block就是Objective-C的对象

四、捕获自动变量值

[objc] view plaincopy

  1. int val = 10;
  2. void (^blk)(void) = ^{printf("val=%d\n",val);};
  3. val = 2;
  4. blk();

上面这段代码,输出值是:val = 10.而不是2,点击这里查看【block第二篇】block捕获变量和对象。

那么这个block的对象结构是什么样呢,请看下面:

[objc] view plaincopy

  1. __main_block_impl_0{
  2. voidvoid *isa;
  3. int Flags;
  4. int Reserved;
  5. voidvoid *FuncPtr;
  6. struct __main_block_desc_0 *Desc;
  7. int val;
  8. }

这个val是如何传递到block结构体中的呢?

[objc] view plaincopy

  1. int main(){
  2. struct __main_block_impl_0 *blk =  &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val);
  3. }

注意函数调用最后一个参数,即val参数。

那么函数调用的代码页转化为下面这样了.这里的cself跟C++的this和OC的self一样。

[objc] view plaincopy

  1. static struct __main_block_func_0(struct __main_block_impl_0 *__cself)
  2. {
  3. printf("val=%d\n",__cself-val);
  4. }

__block说明符

前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是编译错误。但是可以改变全局变量、静态变量、全局静态变量。

其实这两个特点不难理解:第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。

第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。

解决block不能保存值这一问题的另外一个办法是使用__block修饰符。

[objc] view plaincopy

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

该源码转化后如下:

[objc] view plaincopy

  1. struct __block_byref_val_0{
  2. voidvoid *__isa;
  3. __block_byref_val_0 *__forwarding;
  4. int _flags;
  5. int __size;
  6. int val;
  7. }

__main_block_impl_0中自然多了__block_byreg_val_0的一个字段。注意:__block_byref_val_0结构体中有自身的指针对象,难道要

_block int val = 10;这一行代码,转化成了下面的结构体

__block)byref_val_0 val = {0,&val,0,sizeof(__block_byref_val_0),10};//自己持有自己的指针。

它竟然变成了结构体了。之所以为啥要生成一个结构体,后面在详细讲讲。反正不能直接保存val的指针,因为val是栈上的,保存栈变量的指针很危险。

五、block存储区域

这就需要引入三个名词:

● _NSConcretStackBlock

● _NSConcretGlobalBlock

● _NSConcretMallocBlock

正如它们名字说的那样,说明了block的三种存储方式:栈、全局、堆。__main_block_impl_0结构体中的isa就是这个值。

【要点1】如果是定义在函数外面的block是global的,另外如果函数内部的block但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:

[objc] view plaincopy

  1. typedef int (^blk_t)(int);
  2. for(...){
  3. blk_t blk = ^(int count) {return count;};
  4. }

虽然,这个block在循环内,但是blk的地址总是不变的。说明这个block在全局段。

【要点2】一种情况在非ARC下是无法编译的:

typedef int(^blk_t)(int);

blk_t func(int rate){

return ^(int count){return rate*count;}

}

这是因为:block捕获了栈上的rate自动变量,此时rate已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回
block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下没有这个问题,是因为ARC使用了autorelease了。

【要点3】有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:

[objc] view plaincopy

  1. -(id) getBlockArray{
  2. int val =10;
  3. return [[NSArray alloc]initWithObjects:
  4. ^{NSLog(@"blk0:%d",val);},
  5. ^{NSLog(@"blk1:%d",val);},nil];
  6. }
  7. id obj = getBlockArray();
  8. typedef void (^blk_t)(void);
  9. blk_t blk = (blk_t){obj objectAtIndex:0};
  10. blk();

这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。

【要点4】不管block配置在何处,用copy方法复制都不会引起任何问题。在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。

注意:在栈上调用copy那么复制到堆上,在全局block调用copy什么也不做,在堆上调用block 引用计数增加

【注意】本人用Xcode 5.1.1 iOS sdk 7.1 编译发现:并非《Objective-C》高级编程这本书中描述的那样

int val肯定是在栈上的,我保存了val的地址,看看block调用前后是否变化。输出一致说明是栈上,不一致说明是堆上。

[objc] view plaincopy

  1. typedef int (^blkt1)(void) ;
  2. -(void) stackOrHeap{
  3. __block int val =10;
  4. intint *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上
  5. blkt1 s= ^{
  6. NSLog(@"val_block = %d",++val);
  7. return val;};
  8. s();
  9. NSLog(@"valPointer = %d",*valPtr);
  10. }

在ARC下——block捕获了自动变量,那么block就被会直接生成到堆上了。 val_block = 11 valPointer = 10

在非ARC下——block捕获了自动变量,该block还是在栈上的。 val_block = 11 valPointer = 11

调用copy之后的结果呢:

[objc] view plaincopy

  1. -(void) stackOrHeap{
  2. __block int val =10;
  3. intint *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上
  4. blkt1 s= ^{
  5. NSLog(@"val_block = %d",++val);
  6. return val;};
  7. blkt1 h = [s copy];
  8. h();
  9. NSLog(@"valPointer = %d",*valPtr);
  10. }

在ARC下>>>>>>>>>>>无效果。 val_block = 11 valPointer = 10

在非ARC下>>>>>>>>>确实复制到堆上了。 val_block = 11 valPointer = 10

用这个表格来表示

在ARC下:似乎已经没有栈上的block了,要么是全局的,要么是堆上的

在非ARC下:存在这栈、全局、堆这三种形式。

更详细的描述专题点击打开链接

__block变量存储区域

当block被复制到堆上时,他所捕获的对象、变量也全部复制到堆上。

回忆一下block捕获自动变量的时候,自动变量将编程一个结构体,结构体中有一个字段叫__forwarding,用于指向自动这个结构体。那么有了这个__forwarding指针,无论是栈上的block还是被拷贝到堆上,那么都会正确的访问自动变量的值。

六、截获对象

block会持有捕获的对象。编译器为了区分自动变量和对象,有一个类型来区分。

[objc] view plaincopy

  1. static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){
  2. _Block_objct_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF);
  3. }
  4. static void __main_block_dispose_0(struct __main_block_impl_0 *src){
  5. _block_object_dispose(src->val,BLOCK_FIELD_IS_BYREF);
  6. }

BLOCK_FIELD_IS_BYREF代表是变量。BLOCK_FIELD_IS_OBJECT代表是对象

【__block变量和对象】
    __block修饰符可用于任何类型的自动变量
【__block循环引用】
根据上面讲的内容,block在持有对象的时候,对象如果持有block,会造成循环引用。解决办法有两种:
1. 使用__weak修饰符。id __weak obj = obj_
2. 使用__block修饰符。__block id tmp = self;然后在block中tmp = nil;这样就打破循环了。这个办法需要记得将tmp=nil。不推荐!

时间: 2024-10-27 05:01:26

block的内部实现的相关文章

Block的引用循环问题 (ARC & non-ARC)

Block实现原理 首先探究下Block的实现原理,由于Objective-C是C语言的超集,既然OC中的NSObject对象其实是由C语言的struct+isa指针实现的,那么Block的内部实现估计也一样,以下三篇Blog对Block的实现机制做了详细研究: A look inside blocks: Episode 1 A look inside blocks: Episode 2 A look inside blocks: Episode 3 虽然实现细节看着头痛,不过发现Block果然

Block语法(1)

1. 为什么要使用block. 我们知道,对象与对象之间的通信方式有以下三种:1.代理-协议:2.通知:3.block.三种方式都实现了对象之间的解耦合.其中不同就是:通知的通信方式是1对多:代理.block的通信方式是1对1. 2.Block的简介. Block是iOS4.0之后新增的一种语法结构,也成为“闭包”. SDK4.0新增的API大量使用了block Block是一个匿名的函数代码块,此代码块可以作为参数传递给其他对象. 3. Block的使用 //int 是返回值:sumBlock

深入理解block

2010年WWDC发布iOS4时Apple对Objective-C进行了一次重要的升级:支持Block.说到底这东西就是闭包,其他高级语音例如Java和C++已有支持,第一次使用Block感觉满简单好用的,但是慢慢也遇到很多坑.本文聊聊ARC和non-ARC下Block使用中的引用循环问题,最近遇到了好几次这种问题,还是深入记录下.先来套题目热热身,貌似能够全部答对的人蛮少的 Block实现原理 首先探究下Block的实现原理,由于Objective-C是C语言的超集,既然OC中的NSObjec

ios学习笔记block回调的应用(一个简单的例子)

一.什么是Blocks      Block是一个C级别的语法以及运行时的一个特性,和标准C中的函数(函数指针)类似,但是其运行需要编译器和运行时支持,从ios4.0开始就很好的支持Block. 二.在ios开发中,什么情况下使用Block      Block除了能够定义参数列表.返回类型外,还能够获取被定义时的词法范围内的状态(比如局部变量),并且在一定条件下(比如使用__block变量)能够修改这些状态.此外,这些可修改的状态在相同词法范围内的多个block之间是共享的,即便出了该词法范围

block 解析 - 局部变量

局部变量 block内使用局部变量,一般都是截获变量(只读),截获离block初始化最近的一次的值. 引用官方文档: Stack (non-static) variables local to the enclosing lexical scope are captured as const variables.Their values are taken at the point of the block expression within the program. In nested blo

block的学习(block和timer的循环引用问题)

一.什么是回调函数? 回调函数,本质上也是个函数(搁置函数和方法的争议,就当这二者是一回事).由"声明"."实现"."调用"三部分组成. 在上面的例子中,我可以看出,函数amount(其实是Block),的声明和调用在A类中,而实现部分在B类中.也就是说,B类实现了amount函数,但并没有权限调用,最终还是 由A类触发调用.我们称这样的机制为"回调".意思是"虽然函数的实现写在B类中,但是真正的调用还是得由A类来完

OC基础:block.字面量

block 块语法,能够用block去保存一段代码,或者封装一段代码. block 实际是由c语言实现的,运行效率非常高. block 实际借鉴了函数指针的语法. block (^)(參数类型1 參数名1,參数类型2 參数名2...); 返回值类型  (^)(); 1.没有參数,括号也不能省略 2.參数名能够省略 void(^myBlock1)();   无參数无返回值 void(^myBlock2)(int a,int b);   有參数无返回值 int(^myBlock3)();    无參

Block(简单介绍)

1 #import "ViewController.h" 2 3 int age = 18; 4 typedef int(^MaxBlock)(int,int); 5 @interface ViewController () 6 7 @end 8 9 @implementation ViewController 10 11 - (void)viewDidLoad { 12 [super viewDidLoad]; 13 // Do any additional setup after

【少年,放松~】出现block循环引用的三种情况和处理办法

刚入职在看已经上线的项目,其中用到block进行快捷回调的做法很常用,但是Xcode都给给以了如下[循环引用]的警告(如下)的情况,结合网络上的查找和自己的理解,进行总结如下. // Capturing 'self' strongly in this block is likely to lead to a retain cycle 出现这种情况的原因主要是:因为block的特性,声明block的对象都会以copy的形式持有block,而block对于它内部的对象也会进行强引用,从而导致了循环引