主要内容:

一、block相关的题目

二、block的定义

三、block的实现

四、捕获自动变量值

五、block存储区域

六、截获对象

一、block相关的题目

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

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

  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 源代码文件名。 
  1. int main(){
  2. void (^blk)(void) = ^{printf("block\n");};
  3. blk();
  4. return 0;
  5. }
经过 clang -rewrite-objc 之后,代码编程这样了(简化后代码,读者可以搜索关键字在生成文件中查找):
  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的结构体总结一下,得到如下形式:
  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的对象

四、捕获自动变量值

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

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

那么这个block的对象结构是什么样呢,请看下面:
  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结构体中的呢?
  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一样。
  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修饰符。
  1. __block int val = 10;
  2. void (^blk)(void) = ^{val = 1;};
该源码转化后如下:
  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但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:
  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拷贝到堆上。看下面的代码:
  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调用前后是否变化。输出一致说明是栈上,不一致说明是堆上。
  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之后的结果呢:

  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会持有捕获的对象。编译器为了区分自动变量和对象,有一个类型来区分。
  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。不推荐!

block的内部实现的更多相关文章

  1. block的内部实现原理

    一.简单定义 block是一个指向结构体的指针,编译器将block内部代码生成对应的函数,上述结构体中的函数指针(funcPtr)指向该函数的实现: 二.相关概念 形参和实参 形参:形式参数,用于定义 ...

  2. block反向界面传值

    1.在第二个界面的.h文件中申明block @property(nonatomic,copy)void(^myBlock)(NSString * str); 2.在返回第一个界面的点击事件中赋值要传递 ...

  3. Block循环引用问题研究

    自从苹果在objc中添加Block功能支持以后已经过了很久.目前网上对于Block的使用有很多介绍.不过对于Block的内存管理问题,则是众说纷纭.再加上objc开始使用ARC以后,对于Block的内 ...

  4. 浅谈 block(1) – clang 改写后的 block 结构

    这几天为了巩固知识,从 iOS 的各个知识点开始学习,希望自己对每一个知识理解的更加深入的了解.这次来分享一下 block 的学习笔记. block 简介 block 被当做扩展特性而被加入 GCC ...

  5. iOS block 的底层实现

    其实swift 的闭包跟 OC的block 是一样一样的,学会了block,你swift里边的闭包就会无师自通. 参考:http://www.jianshu.com/p/e23078c11518 ht ...

  6. 初始block,关于定义的几个小题目

    block的定义和C语言指针函数非常相似,就可以照着指针函数的方法去依葫芦画瓢就可以了 block中的^只是用来表示这是一个block对象,和函数指针中的*作用一样,只是一个标识符 下面有三个小例子来 ...

  7. 深入理解block

    2010年WWDC发布iOS4时Apple对Objective-C进行了一次重要的升级:支持Block.说到底这东西就是闭包,其他高级语音例如Java和C++已有支持,第一次使用Block感觉满简单好 ...

  8. block 实现原理详解(一)

    对于大多数人来讲,block内部到底是怎样实现的呢?我们可以借助clang将其编译成为c++的代码,就可以看出,block到底是什么东西, 先来看这样一个问题, <!-- lang: cpp - ...

  9. iOS中Block使用探索

    Block介绍 Block在ios 4.0之后加入,并大量使用在新的ios api中.block是一个匿名的代码块,可以传递给其他对象的参数,并得到返回值.从本质上讲,block同其他普通的变量类似, ...

随机推荐

  1. wcf 配置

    wcf 开发 [ServiceContract]-----接口定义1 public interface ILog { [OperationContract]------接口定义1 List<Lo ...

  2. 我的android学习经历34

    用类对象作为ArrayAdapter绑定的基本数据类型(和SimpleAdater效果类似) 一般ArrayAdapter绑定的基本数据类型是String,接下来介绍一下类对象作为基本数据类型: 首先 ...

  3. centos6服务器YUM安装LNMP(LINUX+NGINX+MYSQL+PHP)

    之前都用的lamp,这次配置一个lnmp来看看,试试Nginx是不是好用 关闭SELINUXvi /etc/selinux/config#SELINUX=enforcing #注释掉#SELINUXT ...

  4. IE7下总提示" 缺少标识符、字符串或数字"

    用Jquery easyUI ,IE7下列表显示不了,总提示缺少标识符.字符串或数字.而google,maxthon,firefox,IE10等却没有问题. 原因是Json末尾多了个逗号.IE7下js ...

  5. Flex 文本控件实现自定义复制粘贴

    由于添加了自定义右键菜单,导致Textinput控件默认的右键复制粘贴功能被屏蔽了.最后通过JS脚本实现这个功能,参考代码如下 <?xml version="1.0" enc ...

  6. Linux shell中单引号,双引号及不加引号的简单区别

    简要总结: 单引号: 可以说是所见即所得:即将单引号内的内容原样输出,或者描述为单引号里面看见的是什么就会输出什么. 双引号: 把双引号内的内容输出出来:如果内容中有命令,变量等,会先把变量,命令解析 ...

  7. ThreadLocal的分享

    最开始的时候打算自己写点什么,但是看了这些博客以后感觉真的不知道应该写点什么了,全部都是好文章,只做分享了,链接如下: 1.http://www.cnblogs.com/dolphin0520/p/3 ...

  8. 监控 Linux Unix Solaris AIX, swap page in / swap page out

    vmstat 的 pi/po si/so --监控一天 vmstat 5 17280> vmstat.txt sar -W 1.得到数据 (linux 的 /var/log/sar/saX 自带 ...

  9. 转!!mybatis在xml文件中处理大于号小于号的方法

    第一种方法: 用了转义字符把>和<替换掉,然后就没有问题了. SELECT * FROM test WHERE 1 = 1 AND start_date  <= CURRENT_DA ...

  10. checkbox 设置不可更改

    readonly="readonly" 设置不起作用   用 onclick="return false;"