iOS中block技术小结

block是C语言级别的语法和运行时特性,应用到Objective-C中可以增强函数功能。在合适场景中灵活应用block技术,对实际开发大有裨益。

block是对C语言中函数的扩展,除了函数中的代码,还包含变量的绑定。block有时也被称为闭包(closure),闭包就是一个函数,或者一个指向函数的指针,加上这个函数执行的非局部变量。通俗一点,就是闭包允许一个函数访问声明该函数运行上下文中的变量,甚至可以访问不同运行上文中的变量。

脚本语言:

function funA(callback){

alert(callback());

}

function funB(){

var str = "Hello World"; //函数funB的局部变量,函数funA的非局部变量

funA(

function(){

return str;

}

);

}

通过上面的代码我们可以看出,按常规思维来说,变量str是函数funB的局部变量,作用域只在函数funB中,函数funA是无法访问到str的。但是上述代码示例中函数funA中的callback可以访问到str,就是因为闭包性。

block实际上就是Objective-C语言对于闭包的实现。block配合dispatch_queue,可以方便地实现简单的多线程编程和异步编程。

block原型及定义

block本质上是和其他变量类似。不同的是block存储的数据是一个函数体。使用block时,你可以像调用其他标准函数一样,传入参数,并得到返回值。

脱字符(^)是block的语法标记,按照我们熟悉的参数语法规约所定义的返回值以及block的主体(也就是可以执行的代码)。下图讲解了如何把block变量赋值给一个变量的语法:

按照调用函数的方式调用block对象变量就可以了:

int result = myBlock(4); //result是28

使用typedef关键字

由于block数据类型的语法会降低整个代码的阅读性,所以常使用typedef来定义block类型。

typedef double (^Multiply2BlockRef)(double c, double d);

这行语句定义了一个名为Multiply2BlockRef的block变量,它包含两个double类型参数并返回一个double类型数值。有了typedef定义,就可以像下面这样使用这个变量:

Multiply2BlockRef multiply2 = ^(double c, double d) {

return c * d;

}

printf(“%f”, multiply2(4, 5));

Block和变量

block被声明后会捕捉创建点时的状态。block可以访问函数用到的标准类型的变量:

全局变量;

全局函数(这个是可以调用);

封闭范围内的变量;

与block声明时同级别的__block变量(这是可以修改的);

封闭范围内的非静态变量会被获取为常量;

Objective-C对象;

block内部变量;

3.1 本地变量

本地变量就是与block在同一范围内声明的变量。

typedef double (^Multiply2BlockRef)(double c, double d);

double a = 10, b = 10;

Multiply2BlockRef multiply = ^(void) { return a * b;}

a = 20;

b = 20;

NSLog(@“%f”, multiply());

这个NSLog会输出什么值?400?不是,为什么?

因为对于本地变量,block定义时copy变量的值,在block中作为常量使用,所以即使变量的值在block外改变,也不影响他在block中的值。NSLog只会输出100。

3.2 全局变量

全局变量或静态变量在内存中的地址是固定的,block在读取该变量值的时候是直接从其所在内存读出,获取到的是最新值,而不是在定义时copy的常量。

3.3 Block变量

本地变量会被block作为常量获取到,如果想要修改它们的值,必须将它们声明为可修改的,否则会编译出错。

double c = 3;

Multiply2BlockRef multiply2 = ^(double a, double b){ c = a * b; } // 编译会报错

如果想要在block代码里修改本地变量,需要将变量标记为__block。基于之前的代码,给变量c添加__block关键字,如下:

__block double c = 3;

对于用__block修饰的外部变量引用,block是复制其引用地址来实现访问的。

3.4Objective-C对象

一般来说我们总会在设置block之后,在合适的时间回调block,而不希望回调block的时候block已经被释放了,所以我们需要对block进行copy,copy倒堆中,以便后用。

当一个block被Copy的时候,如果你在block里进行了一些调用,那么将会有一个强引用指向这些调用方法的调用者,有两个规则:

如果是通过引用来访问一个实例变量,那么将强引用至self;

如果是通过值来访问一个实例变量,那么将直接强引用至这个“值”变量;

苹果官方文档里有两个例子来说明这两种情况:

dispatch_async(queue, ^{

// instanceVariable is used by reference, a strong reference is made to self

doSomethingWithObject(instanceVariable);

});

id localVariable = instanceVariable;

dispatch_async(queue, ^{

/*

localVariable is used by value, a strong reference is made to localVariable

(not to self)

*/

doSomethingWithObject(localVariable);

});

上面第一种情况相当于通过self.xxx来访问实例变量,所以强引用指向了self;第二种情况把实例变量变成了本地临时变量,强引用将直接指向这个本地的临时变量。有时第一种情况可能会造成循环引用(在后面的注意事项中会解释),要避免强引用到self的话,用__weak把self重新引用一下,在block中使用weakSelf就行了,比如:

__weak UIViewController *weakSelf = self;

Block类型

block有几种不同的类型,这里列出常见的三种类型:

_NSConcreteGlobalBlock:全局的静态block,不会访问任何外部变量,不会涉及到任何拷贝,比如一个空的block。例如:

int main()

{

^{ printf("Hello, World!\n"); } ();

return 0;

}

_NSConcreteStackBlock:保存在栈中的block,当函数返回时被销毁。例如:

int main()

{

char a = 'A';

^{ printf("%c\n",a); } ();

return 0;

}

_NSConcreteMallocBlock:保存在堆中的block,当引用计数为0时被销毁。该类型的block都是由_NSConcreteStackBlock类型的block从栈中复制到堆中形成的。例如下面代码中,在exampleB_addBlockToArray方法中的block还是_NSConcreteStackBlock类型的,在exampleB方法中就被复制到了堆中,成为_NSConcreteMallocBlock类型的block:

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

}

小结

_NSConcreteGlobalBlock类型的block要么是空block,要么是不访问任何外部变量的block。它既不在栈中,也不在堆中,可以理解为它在内存的text区。

_NSConcreteStackBlock类型的block有闭包行为,也就是有访问外部变量,并且该block有且只有一次执行,因为栈中的空间是可重复使用的,所以当栈中的block执行一次之后就被清除出栈了,所以无法多次使用。

_NSConcreteMallocBlock类型的block有闭包行为,并且该block需要被多次执行。当需要多次执行时,就会把该block从栈中复制到堆中,供以多次执行。

使用block的注意事项

避免在block里用self造成循环引用

block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增加1(MRC)。在ARC与MRC环境下对block使用不当都会引起循环引用问题。一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,简单说就是block的这种循环引用会被编译器捕捉到并及时提醒。

self.someBlock = ^(Type var) {

[self doSomething];

//或self.otherVar = XXX;

//或_otherVar = ...

};

即使在block代码中没有显式地出现"self",也会出现循环引用!只要在block里用到了self所拥有的东西!

对于这种情况,可以通过添加__weak声明(ARC)或者__block声明(MRC)去禁止block对self进行强引用或者强制增加引用计数。

开发过程中该选择block还是delegate

如果对象有超过一个以上不同的事件源,使用delegation;一般的delegate方法会有返回值;delegate的回调更多的面向过程,而block则是面向结果的。如果需要得到一条多步进程的通知,应该使用delegation。而只是希望得到请求的信息(或者获取信息时的错误提示),应该使用block。

总结

可见灵活安全地使用block,必定会使编码工作事半功倍。block经常被用作回调函数,取代传统的回调方式,可以使得编码时更顺畅,不用中途换到另一个地方写一个回调函数。采用block,可以在调用函数时直接写后续处理代码,将其作为参数传递过去,供其任务执行结束时回调。block通常适用于以下场景:任务完成时回调;处理消息监听回调处理;错误回调处理;枚举回调;视图动画、变换;排序等。

本文作者:张生辉(点融黑帮),来自点融北京技术团队,曾在索贝做过3年C++开发,目前主要做iOS开发,对前端技术及新兴技术比较感兴趣。

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

推荐阅读更多精彩内容