block详解

毫不夸张地说,block让objc这门语言变得更有魅力,它就是在其它语言中常见的闭包的概念。
在block之前,objc重度依赖delegate来完成一些用户行为,是block让我们开发者多了一个
更简单的选择,本文就《objc高级编程》来总结一下block的实现原理。

什么是block

用书中的话来概括block,就是:带自动变量的匿名函数。block可以有很多理解,它可以被
看作一个代码块,这个代码块即是匿名函数的函数体,它和普通的函数一样可以有参数,可
以有返回值,由此可见,block在使用上除了没有函数名之外和普通的函数没有区别,但是在
这里要注意区分函数和方法的区别。其带有自动变量,即是它可以保持block声明作用域内变
量的值,无论把这个block拿到哪里去执行,这些变量的值都为你保存着。这些都是显然的闭
包的概念,通过block来深入理解objc(甚至是其它语言)的闭包未尝不是一个好的方法。

虽然使用和理解block给接触objc不久的开发者带来了困扰,但是block的语法却很简单,通
过下面三个实例概括一下block的使用:

  1. 定义:
typedef void (^RFAudioBasicBlock) (void);
typedef void (^RFAudioSuccessBlock) (BOOL flag);
typedef void (^RFAudioSuccessDetailBlock) (BOOL flag, NSURL *url, NSTimeInterval duration);
typedef void (^RFAudioSuccessURLBlock) (BOOL flag, NSURL *url);
  1. 作为参数:
- (void)playWithURL:(NSURL *)url finishedBlock:(RFAudioSuccessDetailBlock)block;
  1. 使用:
[[RFAudioManager defaultManager] playWithURL:url finishedBlock:^(BOOL flag, NSURL *url) {
NSLog(@"播放结束:%@", url);
}];

block的本质与实现

直接通过代码来分析block的实现比较有说服力:

  • objc源码
int main {
void (^blk)(void) = ^{printf("Block/n");};
blk();
return 0;
}
  • 编译之后的代码
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};

struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0 *Desc;

__main_block_impl_0 (void *fp, struct __main_block_desc_0 *desc, int flags=0){
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};

static void __main_block_func_0(struct __main_block_impl_0 *__cself){
printf("Block/n");
}

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)
};

int main() {
void (^blk)(void) = (void (*)(void))&__main_block_impl_0(
(void *)__main_block_func_0, &__main_block_desc_0_DATA
);

( (void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr )(
(struct __block_impl *)blk
);
}

这段代码是我照着PDF文档手打出来的,等我打出来之后也就明白了这段代码的意思,不信你
可以试试。由源代码我们可以清晰地看到:

  1. Block就是__main_block_impl_0结构体指针类型的变量blk,即栈上生成的__main_block_impl_0
    结构体实例(对象)。
  2. __block_impl即为Block的类信息,理解这里的类信息需要借助与objc对象与类之间的关系。
    里面的isa变量我们很熟悉,标识了__block_impl的元类型NSConcreteStackBlock(其实标识了
    该对象所处的位置在stack中)。
  3. Block如何截获自动变量,再简单不过了,将自动变量保存在__main_block_impl_0对象中即可。

由此,我们可以下结论:Block就是一个Objective-c对象。

__block说明符

Block能够轻松地截获自动变量的值,并在其调用内使用它,然而却不能更改它,如果需要在
Block内部更改外部的变量值,需要加上__block修饰符。咋一看,这个要求很简单,在Block
对象中保存变量的指针不就完事了吗?然而实际情况却不是,在Block调用的时候,自动变量
可能早已超出其作用域被销毁了,也就不能通过指针来访问自动变量了。看代码:

  • objc代码
__block int val = 10;

*编译之后的代码

struct __Block_byref_val_0 {
void *__isa;
__Block_byref_val_0 *__forwarding;
int flags;
int __size;
int val;
};

int main() {
__Block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
}
}

由上,我们不难看出所有被__block修饰的变量均会变成一个结构体(即对象),所以在block
中改变其值并不难,然而即便是对象也并没有解决超出作用域被收回的问题,下面我们来探讨
Block及__block变量超出作用域而存在的道理。

Block存储域

分解上述的isa指针,Block分为三种类型:

  • _NSConcreteGlobalBlock
    通过名称就可以判断出此Block为全局的,处于程序的数据区,生成此种类型的Block有
    两种情况:
  1. 记述全局变量的地方有Block语法
  2. Block语法的表达式中不使用应该截获的自动变量时
    此种类型的Block就如同全局变量一样,在全局环境下通过指针安全地使用。
  • _NSConcreteStackBlock
    当Block截获自动变量的时候,其所处的区域在于Stack中。设置在栈上的Block,如果其
    所属的作用域结束,该Block即会被废弃;同理__block类型的变量也配置在栈上,如果
    其所属的变量作用域结束,该__block变量也会被废弃。

  • _NSConcreteMallocBlock
    大部分Block的结构体实例均是在栈上,如果在实例作用域结束的时候,要保留这个Block
    就需要将其复制到堆上,如果堆上的Block引用也过期,就会被收回。下面探讨将Block
    从栈复制到堆的情形。

typedef int (^blk_t)(int);

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

该Block即是属于Stack上的实例变量,当函数返回的时候,Block也相继被废弃,但是此Block
作为函数的返回值,编译器会自动生成将Block复制到堆上的代码。一般情况下,编译器会自行
判断需要将Block复制到堆上的情形,但是也存在编译器不能判断的场景:

  • 向方法或者函数的参数中传递Block时需要手动复制Block。这就是很好地解释了@property
    后面的类型为Block时需要申明copy,当然申明strong/retain也不会出现问题,因为其对于
    Block的默认行为也是copy。

还有两个我们不需要copy的场景:

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

至此我们可以稍微总结一下Block被复制的情形:

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

__block变量作用域

由上文可知,__block变量被转化为结构体实例,其实就是对象,其随着持有它的Block一起被
复制到堆中或者被废弃。当其被复制之后,不管是栈中还是堆中,均可以访问到该对象,并对其
值进行的更改均有效,这是如何做到的呢?

在上文__Block_byref_val_0结构体中有个_forwarding字段,其即为__block变量对象的
指针,栈中对象的_forwarding实际上指向了堆中对象的地址,所以不管是在栈中还是
在堆中访问这个变量均指向了同一个地方即堆中的对象。所以在Block内外均能够对其
进行赋值和访问。

截获对象

有一种情况,如果非__block变量指向的是对象,那么我们依然得克服变量作用域失效而导致
对象被释放的困境,以达到Block截获自动变量的目的。这个问题也似乎很好解决,只需要在
Block中带入该自动变量的修饰符(strong/weak),于是Block对象中保持了对自动变量指向
的对象的强引用,那么目标对象即不会被释放,这样就达到了Block持有这个对象的目的。
实际上苹果也是这么做的,只是这个修饰符在Block被copy的时候才会生效,也即要求Block
对象处于堆中,如果该Block并没有被copy,那么自动变量的修饰符会丢失,自动变量所指
向的对象也会因为超出作用域而被释放。

如果__block变量指向的是对象,情况与非block变量类似。

最后

提醒:使用block是循环引用的高发区,所以要小心杜绝循环引用引起的内存泄露。

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

推荐阅读更多精彩内容

  • 转自李峰峰博客 一、概述 闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」...
    Joshua520阅读 951评论 0 0
  • 1、概述 闭包 = 一个函数「或指向函数的指针」+ 该函数执行的外部的上下文变量「也就是自由变量」;Block 是...
    DeerRun阅读 653评论 0 0
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 901评论 1 3
  • Block基础回顾 1.什么是Block? 带有局部变量的匿名函数(名字不重要,知道怎么用就行),差不多就与C语言...
    Bugfix阅读 6,736评论 5 61
  • 2.1 Blcoks概要 2.1.1 什么是Blocks Blocks是C语言的扩充功能——“带有自动变量(即局部...
    SkyMing一C阅读 2,310评论 6 18