OC高级编程——深入block,如何捕获变量,如何存储在堆上
OC高级编程——深入block,如何捕获变量,如何存储在堆上
首先先看几道block相关的题目
这是一篇比较长的 博文 ,前部分是block的测试题目,中间是block的语法、特性,block讲解block内部实现和block存储位置,请读者耐心阅读。 具备block基础的同学,直接调转到block的实现
下面列出了五道题,看看能否答对两三个。主要涉及block栈上、还是堆上、怎么捕获变量。 答案在博文最后一行
//-----------第一道题:--------------
void exampleA() {char a = 'A';^{ printf("%c\n", a);}; } A.始终能够正常运行 B.只有在使用ARC的情况下才能正常运行 C.不使用ARC才能正常运行 D.永远无法正常运行 //-----------第二道题:答案同第一题-------------- void exampleB_addBlockToArray(NSMutableArray *array) { char b = 'B'; [array addObject:^{printf("%c\n", b);}]; } void exampleB() { NSMutableArray *array = [NSMutableArray array]; exampleB_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); } //-----------第三道题:答案同第一题-------------- void exampleC_addBlockToArray(NSMutableArray *array) { [array addObject:^{printf("C\n");}]; } void exampleC() { NSMutableArray *array = [NSMutableArray array]; exampleC_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); } //-----------第四道题:答案同第一题-------------- typedef void (^dBlock)(); dBlock exampleD_getBlock() { char d = 'D'; return ^{printf("%c\n", d);}; } void exampleD() { exampleD_getBlock()(); } //-----------第五道题:答案同第一题-------------- typedef void (^eBlock)(); eBlock exampleE_getBlock() { char e = 'E'; void (^block)() = ^{printf("%c\n", e);}; return block; } void exampleE() { eBlock block = exampleE_getBlock(); block(); }
注:以上题目摘自:CocoaChina论坛 点击打开链接
block概要
什么是block
Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。 命名就是工作的本质,函数名、变量名、方法名、属性名、类名和框架名都必须具备。而能够编写不带名称的函数对程序员来说相当有吸引力。
例如:我们要进行一个URL的请求。那么请求结果以何种方式通知调用者呢?通常是经过代理(delegate)但是,写delegate本身就是成本,我们需要写类、方法等等。
这时候,我们就用到了block。block提供了类似由C++和OC类生成实例或对象来保持变量值的方法。像这样使用block可以不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量, 仅用编写C语言函数的源码量即可使用带有自动变量值的匿名函数。
其他语言中也有block概念。
block的实现
block的语法看上去好像很特别,但实际上是作为极为普通的C语言代码来处理的。这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。
控制台命令是: clang -rewrite-objc 源代码文件名。
int main(){void (^blk)(void) = ^{printf("block\n");};blk();return 0; }
经过 clang -rewrite-objc 之后,代码编程这样了(简化后代码,读者可以搜索关键字在生成文件中查找):
struct __block_impl{
void *isa;
int Flags; int Reserved; void *FuncPtr; }; static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size }__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; struct __main_block_impl_0{ struct __block_impl impl; struct __main_block_desc_0 *Desc; } static struct __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("block\n"); } int main(){ struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA); (*blk->impl.FuncPtr)(blk); }
很多结构体,很多下划线的变量和函数名。我们一个个来:
__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的结构体总结一下,得到如下形式:
__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; }
总结:所谓block就是Objective-C的对象
截获自动变量值
int val = 10; void (^blk)(void) = ^{printf("val=%d\n",val);}; val = 2; blk();
上面这段代码,输出值是:val = 10.而不是2. block截获自动变量的瞬时值 。因为block保存了自动变量的值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。
尝试改写block中捕获的自动变量,将会是编译错误。我更喜欢把这个理解为:block捕获的自动变量都将转化为const类型。不可修改了
解决办法是将自动变量添加修饰符 __block;那么如果截获的自动变量是OC对象呢
^{[array addObject:obj];};
这么写是没有问题的,因为array是一个指针,我们并没有改变指针的值。这个也可以解释下面的问题
const char text[] = "hello";
这样会编译错误。为何?
这是因为捕获自动变量的方法并没有实现C语言数组类型
。可以通过指针代替:const char *text= "hello";
那么这个block的对象结构是什么样呢,请看下面:
__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; int val; }
这个val是如何传递到block结构体中的呢?
int main(){struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val); }
注意函数调用最后一个参数,即val参数。
那么函数调用的代码页转化为下面这样了.这里的cself跟C++的this和OC的self一样。
static struct __main_block_func_0(struct __main_block_impl_0 *__cself) {printf("val=%d\n",__cself-val); }
所以,block捕获变量更像是:函数按值传递。
__block说明符
前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是编译错误。但是可以改变全局变量、静态变量、全局静态变量。
其实这两个特点不难理解: 第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。
第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。
解决block不能保存值这一问题的另外一个办法是使用__block修饰符。
__block int val = 10; void (^blk)(void) = ^{val = 1;};
该源码转化后如下:
struct __block_byref_val_0{ void *__isa; __block_byref_val_0 *__forwarding; int _flags; int __size; int val; }
__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但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:
typedef int (^blk_t)(int);
for(...){blk_t blk = ^(int count) {return count;}; }
虽然,这个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拷贝到堆上。看下面的代码:
-(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();
这段代码在最后一行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调用前后是否变化。输出一致说明是栈上,不一致说明是堆上。
typedef int (^blkt1)(void) ; -(void) stackOrHeap{ __block int val =10; int *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上 blkt1 s= ^{ NSLog(@"val_block = %d",++val); return val;}; s(); NSLog(@"valPointer = %d",*valPtr); }
在ARC下——block捕获了自动变量,那么block就被会直接生成到堆上了。 val_block = 11 valPointer = 10
在非ARC下——block捕获了自动变量,该block还是在栈上的。 val_block = 11 valPointer = 11
调用copy之后的结果呢:
-(void) stackOrHeap{ __block int val =10; int *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上 blkt1 s= ^{ NSLog(@"val_block = %d",++val); return val;}; blkt1 h = [s copy]; h(); NSLog(@"valPointer = %d",*valPtr); }
----------------在ARC下>>>>>>>>>>>无效果。 val_block = 11 valPointer = 10
----------------在非ARC下>>>>>>>>>确实复制到堆上了。 val_block = 11 valPointer = 10
用这个表格来表示
/*当block捕获了自动变量时候
------------------------------------------------------------------
| where block stay | ARC | 非ARC |
-------------------------------------------------------------------
| copy | heap | heap |
------------------------------------------------------------------
| no copy | heap | stack |
------------------------------------------------------------------
*/
__block变量存储区域
当block被复制到堆上时,他所捕获的对象、变量也全部复制到堆上。
回忆一下block捕获自动变量的时候,自动变量将编程一个结构体,结构体中有一个字段叫__forwarding,用于指向自动这个结构体。那么有了这个__forwarding指针,无论是栈上的block还是被拷贝到堆上,那么都会正确的访问自动变量的值。
截获对象
block会持有捕获的对象。编译器为了区分自动变量和对象,有一个类型来区分。
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){_Block_objct_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF); } static void __main_block_dispose_0(struct __main_block_impl_0 *src){ _block_object_dispose(src->val,BLOCK_FIELD_IS_BYREF); }
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测试题答案:ABABB
int val = 10; void (^blk)(void) = ^{printf("val=%d\n",val);}; val = 2; blk();
上面这段代码,输出值是:val = 10.而不是2. block截获自动变量的瞬时值 。因为block保存了自动变量的值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。
尝试改写block中捕获的自动变量,将会是编译错误。我更喜欢把这个理解为:block捕获的自动变量都将转化为const类型。不可修改了
解决办法是将自动变量添加修饰符 __block;那么如果截获的自动变量是OC对象呢
^{[array addObject:obj];};
这么写是没有问题的,因为array是一个指针,我们并没有改变指针的值。这个也可以解释下面的问题
const char text[] = "hello";
这样会编译错误。为何?
这是因为捕获自动变量的方法并没有实现C语言数组类型
。可以通过指针代替:const char *text= "hello";
- 1. 016 Add Two Numbers
- 2. 指针学习系列一 基础知识
- 3. 你真的深入理解计算机系统了吗之篇章三:程序的结构
- 4. C/C++函数参数读取顺序
- 5. 翻译「C++ Rvalue References Explained」C++右值引用详解 Part8:P..
- 6. 排列组合技术
我来评几句
OC高级编程——深入block,如何捕获变量,如何存储在堆上相关推荐
- 变量可以存储在堆中,栈中,方法区中。哪里都可以啊。对象只能存储在堆中...
变量可以存储在堆中,栈中,方法区中.哪里都可以啊.对象只能存储在堆中
- C语言高级编程:如何确定一个变量是有符号还是无符号数
ANSI C中的整型升级: 1)char,short int或者int型位段(bit-field),包括它们的有符号或无符号变型, 2)以及枚举类型,可以使用在需要int或unsigned int的表 ...
- UNIX环境高级编程——线程同步之条件变量以及属性
条件变量变量也是出自POSIX线程标准,另一种线程同步机制.主要用来等待某个条件的发生.可以用来同步同一进程中的各个线程.当然如果一个条件变量存放在多个进程共享的某个内存区中,那么还可以通过条件变量来 ...
- OC高级foundation框架类以及数据的简单存储
Objective-c高级复习 第一章 Foundation框架介绍 Foundation.framework是iOS开发的核心框架之 第二章 一.NSNumber OC的数字类型(存储C语言中简单的 ...
- 关于UNIX环境高级编程(第2版)——图灵计算机科学丛书的读后感
UNIX环境高级编程(第2版)--图灵计算机科学丛书 当当上购买 当当网全场免运费!! 关于UNIX环境高级编程(第2版)--图灵计算机科学丛书 评论 读后感:先讲语法,再讲继承机制.后讲Dom,越往 ...
- C# 高级编程个人笔记搬运 一(基础知识)
今天天气不错,连夜下了场大雨后一切湿漉漉的.是的,我又回来接着写了,我又要看新的书了,怕自己忘记了之前看过的,大概搬运下旧书笔记.一边给新书记笔记,一遍搬运旧的,说不定可以打通任督二脉. 扎个马步: ...
- bash-shell高级编程-变量的赋值
变量的赋值 = 赋值操作(前后都不能有空白) 注意:因为=和-eq都可以用作条件测试操作,所以不要与这里的赋值操作相混淆. =既可以用作条件测试操作,也可以用于赋值操作,这需要视具体上下文而定 简单的 ...
- 《Objective-C高级编程 iOS与OS X多线程和内存管理》读书笔记
<Objective-C高级编程 iOS与OS X多线程和内存管理>读书笔记 第一章:自动引用计数 自己生成的对象,自己所持有. 非自己生成的对象,自己也能持有 不再需要自己持有的对象时释 ...
- C++高级编程(第3版)_学习记录
<C++高级编程(第3版)> Professional C++, Third Edition [美]Narc Gregoire 著,张永强 译,清华大学出版社,2015.5第1版 文章目录 ...
最新文章
- python运行非常慢的解决-提升Python程序运行效率的6个方法
- channels java_Java NIO channels
- php foreach 循环 判断index 小于多少_PHP设计模式之迭代器模式 - 硬核项目经理
- 语言 班费管理系统源代码_固定资产管理系统开源的各种语言翻译
- 聊聊我常用的5款动态数据可视化工具
- Chrome中输入框默认样式移除
- vue项目中对axios的全局封装
- java类继承语法_java类的继承(基础)
- 鲲鹏性能优化十板斧(四)——磁盘IO子系统性能调优
- 服务器的维护记录在哪查看,教你巧用事件查看器维护服务器安全 -电脑资料
- coolfire黑客入门教程系列之(八)最后部分!
- 做一个自己的PC微信无感免打扰检测僵尸粉死粉的小工具。使用c++ call
- 软件测试工程师自我介绍范文_软件测试自我介绍范文
- 使用opencv在视频中插入文字、图片;生成特定文字视频
- Android仿人人客户端(v5.7.1)——项目框架新做的调整描述(项目中基类java源码)
- Python3爬取淘宝网商品数据!
- 用正则表达式匹配“空值”
- 【微服务架构】一文读懂单片到微服务架构的模式和最佳实践
- 移动、联通和电信,哪家的宽带好,看完你就知道该怎么选了!
- 《从总账到总监》读书笔记