深入浅出Block


ps:看网上的东西最好自己试一下,别人讲的东西不一定是正确的。

Block概要

什么是Block

Blocks是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。命名就是工作的本质,函数名、变量名、方法名、属性名、类名和框架名都必须具备。而能够编写不带名称的函数对程序员来说相当有吸引力。例如:我们要进行一个URL的请求。那么请求结果以何种方式通知调用者呢?通常是经过代理(delegate)但是,写delegate本身就是成本,我们需要写类、方法等等。这时候,我们就用到了block。block提供了类似由C++和OC类生成实例或对象来保持变量值的方法。像这样使用block可以不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量,仅用编写C语言函数的源码量即可使用带有自动变量值的匿名函数。其他语言中也有block概念。

Block的实现

下面我们来定义一个简单的block,block的语法看上去好像很特别,但实际上是作为极为普通的C语言代码来处理的。这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。控制台命令是: clang -rewrite-objc 源代码文件名。


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

看上去有点晕对不对,没关系我们一个个来分析。

__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的结构体总结一下,得到如下形式:

因此我可以看出来所谓的block就是一个object-c 对象。为什么这么说呢,在runtime机制的时候会讲到相关内容,到时候你就明白了。

截获自动变量值

我们看下面一段代码,你猜猜是什么结果呢?

上面这段代码的值是10,你可能会感到很疑惑。block截获自动变量的瞬时值。因为block保存了自动变量的值,所以在执行block语法后,即使改写block中使用的自动变量的值也不会影响block执行时自动变量的值。

如果你强行在block里面修改val的值,那么编译器将会报错,我们可以这么理解,block捕获的自动变量会默认转化为const类型,不可修改了,如果我们要改的话只需要在定义变量的时候给它加__block修饰符就可以了。但是如果我们捕获的是oc对象呢?

就比如一个NSMutableArray *array。^{[array addObject:obj];};这么写是没有问题的,因为array只是一个指针而已,我们并没有改变指针的值。

还有一种情况我们也可以很好的解释:const char text[] = "hello";  ^{ printf("%c\n",text[2]);}; 这样会编译错误。为何?这是因为捕获自动变量的方法并没有实现C语言数组类型。

可以通过指针代替:const char *text= "hello";那么这个block的对象结构是什么样呢,请看下面:

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

就像C里面初始化结构体一样的,注意函数调用最后一个参数,即val参数。那么函数调用的代码页转化为下面这样了.这里的cself跟C++的this和OC的self一样。

所以,block捕获变量更像是:函数按值传递。是不是很简单呢?

__block说明符

前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是编译错误。但是可以改变全局变量、静态变量、全局静态变量。其实这两个特点不难理解:

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

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

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

该源码转化后如下:

比__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就是这个值。

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

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

一种情况在非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了。

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

我们经过调试发现如下结果:

它的第一个对象是在堆上面,第二个个在栈上面,我表示很困惑。

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

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

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

__block变量存储区域

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

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 217,084评论 6 503
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,623评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 163,450评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,322评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,370评论 6 390
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,274评论 1 300
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,126评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,980评论 0 275
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,414评论 1 313
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,599评论 3 334
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,773评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,470评论 5 344
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,080评论 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,713评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,852评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,865评论 2 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,689评论 2 354

推荐阅读更多精彩内容

  • 一句话总结block : 带有局部变量的匿名函数 闭包在其它编程语言的名称 iOS闭包的声明与定义 博主iOS开发...
    王韩峰阅读 1,034评论 4 5
  • 前言 Blocks是C语言的扩充功能,而Apple 在OS X Snow Leopard 和 iOS 4中引入了这...
    小人不才阅读 3,768评论 0 23
  • 《Objective-C高级编程》这本书就讲了三个东西:自动引用计数、block、GCD,偏向于从原理上对这些内容...
    WeiHing阅读 9,810评论 10 69
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,762评论 5 61
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 925评论 1 3