1. 什么是Blocks

Blocks是C语言的扩充功能。就是:带有自动变量的匿名函数。

类似C语言的函数指针。但Blocks不是一个指针,而是一个不带名字的函数,它允许开发者在两个对象之间将任意的语句当作数据进行传递,同时它又能获得上下文的信息(闭包Closure),所以在一些场合使用Blocks很方便简洁。

2. Block语法

我们先来看一个例子吧。

^int(int count){return count++;}

这个Block非常简单,就是把计数器加一,但麻雀虽小,五脏俱全,语法上一个元素都没漏掉。首先是^符号(插入符号caret),然后依次是是返回值类型,参数列表,表达式。

^            int                  (int count)              {return count++;}

caret    返回参数          参数列表               表达式

齐全的Block就是这些内容了,不同的Block的表达式的复杂程度各异,但元素就是这么多了。

不过很多时候我们会遇到没有返回参数,或者没有传入参数,甚至既没有传入参数也没有返回参数的情况。这个时候Block可以省略相关的内容,就是说相对应的那一块不用写了。比如:

没有返回参数,可能就会写成:^(int count){printf("count=%d", count);}

没有参数列表,可能会写成:^int{return 1;}

既没有返回参数也没有参数列表,可能就会写成:^{printf("Blocks Demo!\n")};

Block还可以申明变量,Block变量一样可以用typedef来定义成新的类型,这种做法下和函数指针真的非常非常类似,仅仅是将*换成了^。举个例子:

int (^blk)(int) = ^int (int count) {return count+;};

熟悉C语言的人对这个都会比较熟悉。这里有一个要说明,上面的赋值语句右侧可以省略掉返回类型(猜测是这部分信息编译器已经可以确定,所以不再是必须提供的了)。这样,上面的语句也可以写成;

int (^blk)(int) = ^(int count) {return count+;};

如果使用typedef,就可以更清晰一点:

typedef int (^blk_t)(int);

blk_t blk =  ^(int count) {return count+;};

3. 截获自动变量值

Block是带自动变量的匿名函数,下面就要看看“自动变量”了。Block可以访问在它之前申明的变量,但也有它特殊的地方,先看一个例子:

  1. int val = 1;
  2. void (^blk)(void) = ^{printf("val=%d\n", val);};
  3. val = 2;
  4. blk();  Block在定义时相当于对val这个变量照了张相,
  5. return 0;

运行结果val=1。请注意,虽然这个时候val的值已经变成了2,但Block里面仍然是1,也就是说,Block在定义时相当于对val这个变量照了张相,然后一直自己使用这张相片,不管val本身何去何从。

这个特性可以带来很大的便利(记下了定义时的上下文),有时是我们所需要的;但一不小心也很容易错,所以使用时需要注意。Block里面,val就是只读的,而且值是定义时的那个。

虽然记住上下文,Block的内容也跟随改变或者要修改自动变量的值,这个就需要用到__block关键词了。继续上代码:

  1. __block int val = 1;
  2. void (^blk)(void) = ^{printf("val=%d\n", val);};
  3. val = 2;
  4. blk();
  5. return 0;
  6. }

这段代码和上一段的区别仅仅是多了__block的声明,但运行结果就是val=2了,也就是说,Block能跟踪val的变化了。

这时,Block里面也可以改变val的值了,就是说,用了__block之后,val对于Block不再是只读的了,而是和自己定义的变量一样了。

在一个Block里面,往往两种变量都需要有,具体怎么使用,就看具体的情况了。

4. Block的使用

我总是觉得任何一种技术的出现总是用来解决某个问题的,也决定了在何种情况下使用该技术。Block应该如何使用呢?这是仁者见仁,智者见智的问题了,我接触最多的是在GCD里面。

个人的感觉这个东东主要用在回调里面,比如:网络连接成功后我应该把某个按钮激活之类的,Block使用起来简洁明快,如鱼得水。GCD实际上也是给出了系统的回调,所以就特别适合Block大显身手。

5. 其他

Block当然还有其他的一些内容,比如可以作为函数参数来传递,比如当自动变量是ObjC的对象时,虽然不能修改,但可以调用对象的方法,再比如,C语言的数组不能作为自动变量等等。但这些都不是Block主要的内容,最重要的还是灵活的使用。

1.简介

从iOS4开始,苹果引入了这个C语言的扩充功能“Blocks”,在一些特定的场景下也是一把利刃。我前面一篇博客中初步介绍了Blocks这个东西,主要是语法的介绍(《iOS中Blocks的介绍》)。

我曾经看见了老外的一个系列的Blocks介绍,很有深度(A look inside blocks:Episode 1,A look inside blocks:Episode 2, A look inside blocks:Episode 3),里面深入到汇编的层次对Blocks的实现进行了分析。不过如果象我这样对于汇编不熟悉的人肯定也是很多的,理解起来十分痛苦,于是就想到从ObjC本身对Blocks进行的处理里面来入手分析,看看对于Blocks都悄悄做了什么。

2.环境

很简单,就是Xcode啦。使用的编译器是CLang,主要是利用了-rewrite-objc这个参数,把源文件转换成中间文件。这样就揭开了面纱的一角。我使用的clang编译器版本是:

Apple clang version 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn)
      Target: x86_64-apple-darwin12.5.0
      Thread model: posix

转成中间文件的命令是:clang -rewrite-objc 源文件

3. 例子1

  1. #include <stdio.h>
  2. int main(int argc, const charchar * argv[])
  3. {
  4. int val = 2;
  5. int val1 = 5;
  6. void (^blk)(void) = ^{printf("in Block():val=%d\n", val);};
  7. val = 4;
  8. blk();
  9. printf("in main(): val=%d, val1=%d\n", val, val1);
  10. return 0;
  11. }

代码的运行结果是:

in Block():val=2

in main():val=4, val1=5

这里我们可以看到Block对于自动变量的“快照”功能。由于转成中间文件之后发现长了很多,变成了100多行(不同的clang版本转换出来的文件还不同,不过实现部分代码还是一样的),下面的代码是相关部分的节选,主要说明苹果是如何实现的。

  1. struct __block_impl {
  2. voidvoid *isa;
  3. int Flags;
  4. int Reserved;
  5. voidvoid *FuncPtr;
  6. };
  1. #include <stdio.h>
  2. int main(int, const charchar **);
  3. struct __main_block_impl_0 {
  4. struct __block_impl impl;
  5. struct __main_block_desc_0* Desc;
  6. int val;
  7. __main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
  8. impl.isa = &_NSConcreteStackBlock;
  9. impl.Flags = flags;
  10. impl.FuncPtr = fp;
  11. Desc = desc;
  12. }
  13. };
  14. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  15. int val = __cself->val; // bound by copy
  16. printf("in Block():val=%d\n", val);}
  17. static struct __main_block_desc_0 {
  18. unsigned long reserved;
  19. unsigned long Block_size;
  20. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
  21. int main(int argc, const charchar * argv[])
  22. {
  23. int val = 2;
  24. int val1 = 5;
  25. void (*blk)(void) = (void (*)(void))&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA, val);
  26. val = 4;
  27. ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
  28. printf("in main(): val=%d, val1=%d\n", val, val1);
  29. return 0;
  30. }

中间文件确实看起来复杂了不少,不过还是有脉络可循。

看main函数的内容,里面有个函数指针blk,这个就是指向Block的指针,所以难怪Block变量的声明和函数指针如此相像(就是把*换成^),编译器转换后就是同一个东西啊。

我们看blk这个函数指针,就是__main_block_impl_0这个结构体变量的指针,这个结构体变量此时已经存在,然后用__main_block_func_0等几个变量赋初值。我们可以看到__main_block_impl_0这个struct中有个val这个项,并且在这里也赋值了,这就是给变量照的“快照”,由于这个变量在这里被记录了,所以无论外面的val变量如何变化,Block运行时使用的值就始终是“快照”的值了。同时我们也注意到__main_block_impl_0这个struct中没有val1这个项,所以说明如果Block中不用到的自动变量是不会自动加入到结构体中的。

Block的运行就是运行__main_block_impl_0这个struct中的FuncPtr这个指针,这个在前面初始化的时候已经被赋值成__main_block_func_0了,所以这里也就是运行这个函数,并把自己的指针传入。这里我们的实现非常简单,就是一句printf语句。

4.例子2

  1. #include <stdio.h>
  2. int main(int argc, const charchar * argv[])
  3. {
  4. int __block val = 2;
  5. int val1 = 5;
  6. void (^blk)(void) = ^{printf("in Block():val=%d\n", ++val);};
  7. blk();
  8. printf("in main(): val=%d, val1=%d\n", val, val1);
  9. return 0;
  10. }

这个例子把自动变量val声明成了__block变量,这样语法上Block不是对val进行“快照”,而是会直接使用val变量,同时在Block内部val可读可写,不再是只读的了。

运行结果如下:

in Block():val=3
      in main(): val=3, val1=5

同样展开成中间文件来看苹果的实现。

  1. struct __block_impl {
  2. voidvoid *isa;
  3. int Flags;
  4. int Reserved;
  5. voidvoid *FuncPtr;
  6. };
  1. #include <stdio.h>
  2. int main(int, const charchar **);
  3. struct __Block_byref_val_0 {
  4. voidvoid *__isa;
  5. __Block_byref_val_0 *__forwarding;
  6. int __flags;
  7. int __size;
  8. int val;
  9. };
  10. struct __main_block_impl_0 {
  11. struct __block_impl impl;
  12. struct __main_block_desc_0* Desc;
  13. __Block_byref_val_0 *val; // by ref
  14. __main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {
  15. impl.isa = &_NSConcreteStackBlock;
  16. impl.Flags = flags;
  17. impl.FuncPtr = fp;
  18. Desc = desc;
  19. }
  20. };
  21. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
  22. __Block_byref_val_0 *val = __cself->val; // bound by ref
  23. printf("in Block():val=%d\n", ++(val->__forwarding->val));}
  24. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
  25. static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}
  26. static struct __main_block_desc_0 {
  27. unsigned long reserved;
  28. unsigned long Block_size;
  29. void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
  30. void (*dispose)(struct __main_block_impl_0*);
  31. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
  32. int main(int argc, const charchar * argv[])
  33. {
  34. __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 2};
  35. int val1 = 5;
  36. void (*blk)(void) = (void (*)(void))&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA, (struct __Block_byref_val_0 *)&val, 570425344);
  37. ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);
  38. printf("in main(): val=%d, val1=%d\n", (val.__forwarding->val), val1);
  39. return 0;
  40. }

中间文件又变长了一些,除去我们已经了解的部分,我们可以看到自动变量val不再是直接加入到__main_block_impl_0里面,而是又变成了一个__Block_byref_val_0的struct结构体的指针。

main函数里面对于val的赋值已经变成了对这样一个数据结构的赋值,第一句上就把2赋给了__Block_byref_val_0里面的val项,然后在blk这个指针初始化的时候,把__Block_byref_val_0的结构体变量指针传入__main_block_impl_0。此后所有对于自动变量val的操作都变成对val.__forwarding->val的操作。这样就解决了Block内外变量同时变化的问题(在操作同一块内存)。

这里还看见__Block_byref_val_0里面有个__forwarding项,这个项是指向自身的一根指针。在blk指针初始化的时候我们把这个指针的值传入了__main_block_impl_0。

在__main_block_desc_0里面,多出了两个函数指针,分别用于copy和dispose,这两个函数这里也是自动生成的。

5.总结

综合前面的例子来看,Block的实现还是借助了C语言的函数指针来实现了,对于普通的自动变量,在Block声明时会快照内容存储;对于__block变量,则是生成一个数据结构来存储,然后取代所有访问这个变量的地方。

事实上,因为Block运行时完全可能自动变量的生命周期已经结束,所以Block对于内存的管理是很复杂的,会把内容从栈上copy到堆上(这也是copy和dispose函数的作用)。所以Block虽然威力巨大,但使用时也需要遵循一定的规则。

iOS中Block介绍(二)内存管理与其他特性

 

我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题。这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因。

AD:2014WOT全球软件技术峰会北京站 课程视频发布

一、block放在哪里

我们针对不同情况来讨论block的存放位置:

1.栈和堆

以下情况中的block位于堆中:

  1. void foo()
  2. {
  3. __block int i = 1024;
  4. int j = 1;
  5. void (^blk)(void);
  6. void (^blkInHeap)(void);
  7. blk = ^{ printf("%d, %d\n", i, j);};//blk在栈里
  8. blkInHeap = Block_copy(blk);//blkInHeap在堆里
  9. }
  10. - (void)fooBar
  11. {
  12. _oi = 1;
  13. OBJ1* oj = self;
  14. void (^oblk)(void) = ^{ printf("%d\n", oj.oi);};
  15. void (^oblkInHeap)(void) = [oblk copy];//oblkInHeap在堆中
  16. }

2.全局区

以下情况中的block位于全局区:

  1. static int(^maxIntBlock)(int, int) = ^(int a, int b){return a>b?a:b;};
  2. - (void)fooBar
  3. {
  4. int(^maxIntBlockCopied)(int, int) =[maxIntBlock copy];
  5. }
  6. void foo()
  7. {
  8. int(^maxIntBlockCopied)(int, int) = Block_copy(maxIntBlock);
  9. }

需要注意的是,这里复制过后的block依旧位于全局区,实际上,复制操作是直接返回了原block对象。

二、block引用的变量在哪里

 1.全局区

全局区的变量存储位置与block无关:

  1. static int gVar = 0;
  2. //__block static int gMVar = 1;
  3. void foo()
  4. {
  5. static int stackVar = 0;
  6. //    __block static int stackMVar = 0;
  7. }

注意:static变量是不允许添加__block标记的

2.堆栈 

此时,你可能会问,当函数foo返回后,栈上的j已经回收,那么blkInHeap怎么能继续使用它?这是因为没有__block标记的变量,会被当做实参传入block的底层实现函数中,当block中的代码被执行时,j已经不是原来的j了,所谓物是人非就是这样吧~

另外,如果使用到变量j的所有block都没有被复制至heap,那么这个变量j也不会被复制至heap。

因此,即使将j++这一句放到blk()这句之前,这段代码执行后,控制台打印结果也是:1024, 1。而不是1024, 2

三、其他特性

1.复制的行为

对block调用复制,有以下几种情况:

1.对全局区的block调用copy,会返回原指针,并且这期间不处理任何东西(至少目前的内部实现是这样);

2.对栈上的block调用copy,每次会返回新复制到堆上的block的指针,同时,所有__block变量都会被复制至堆一份(多次拷贝,只会生成一份)。

3.对已经位于heap上的block,再次调用copy,只会增加block的引用计数。

为什么我们不讨论retian的行为?原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。

2.objc类中的block复制

objc类实例方法中的block如果被复制至heap,那么当前实例会被增加引用计数,当这个block被释放时,此实例会被减少引用计数。

但如果这个block没有使用当前实例的任何成员,那么当前实例不会被增加引用计数。这也是很自然的道理,我既然没有用到这个instance的任何东西,那么我干嘛要retian它?

我们要注意的一点是,我看到网上有很多人说block引起了实例与block之间的循环引用(retain-cycle),并且给出解决方案:不直接使用self而先将self赋值给一个临时变量,然后再使用这个临时变量。

但是,大家注意,我们一定要为这个临时变量增加__block标记(多谢第三篇文章回帖网友的提醒)。

这一章我们以结果导向的方式来说明了各种情况下,block的内存问题,下一章,我将剖析运行时库的源码,从根源阐述block的行为。也就是过程导向的方式了。

//Block

// '^'脱字符

//函数指针

int (*p)(int, int) = sum;

//调用函数

sum(3, 4);

p(2 ,4);

//声明一个Block变量

/*1*///int:返回值   ,后面两个int:参数类型

int (^block1) (int, int) = nil;

//block 变量存储的值,是block的实现部分

//实现部分返回值类型常省略

//参数变量名不能省略

//以';'结尾

block1 = ^(int a, int b){

return a + b;

};

//使用block1

int sum = block1(2, 4);

NSLog(@"%d",sum);

/*2*//////////////////////////////////

void (^block2)() = ^(){

NSLog(@"无参数 无返回值的  block2(); ");

};

block2();//调用

//3,求两个数最大值 block

int (^maxValue) (int x,int y) = ^(int a,int b){

return a > b ? a : b;

};

int max = maxValue(2,3);//调用

NSLog(@"最大值:%d",max);

//4, 将字符串转成数字的block

int (^numberFromString)(NSString *) = ^(NSString *str){

return [str intValue];

};

NSLog(@"%d",numberFromString(@"123"));//回调了 ^numberFromString 这个block

//5, 给可变数组排序的block

//5,1

NSComparator sortBlock = ^(id string1, id string2)

{

return [string1 compare:string2];

};

NSMutableArray *stringArray = [NSMutableArray arrayWithObjects:@"a", @"c", @"b", @"d",nil];

[stringArray sortedArrayUsingComparator:sortBlock];

NSLog(@"sortArray:%@", stringArray);

//5,2

[stringArray sortUsingComparator:^NSComparisonResult(id obj1,id obj2){

if ([obj1 intValue] > [obj2 intValue]) {

return NSOrderedDescending;

}

return NSOrderedSame;

}];

NSLog(@"--%@",stringArray);

//6, 取别名

typedef int (^SumBlock) (int,int);

SumBlock sumBlock1 = ^(int a,int b){

return a + b;

};

NSLog(@"%d",sumBlock1(7,8));

//7, 求阶乘 的block

int (^jiecheng)(int) = ^(int n){

int m = 1;

for (int i = 1; i <= n; i++) {

m *= i;

}

return m;

};

NSLog(@"阶乘:%d",jiecheng(3));

//8, block内部可以访问外部局部变量,但是不能修改,若要修改需加__block 修饰

__block int count = 2;

void (^sayHi)() = ^(){

count = 3;

NSLog(@"count 被修改成=%d",count);

};

sayHi();

//9,

iOS - Blocks的更多相关文章

  1. SoftwareEngineering.APIDesign.iOS

    API Design for iOS/Mac (Objective-c Edition) 1. UI Control Library API的设计 和已有组件保持一致(例如: 使用标准的API, 模型 ...

  2. iOS开发 - OC - block的详解 - 基础篇

    深入理解oc中的block 苹果在Mac OS X10.6 和iOS 4之后引入了block语法.这一举动对于许多OC使用者的编码风格改变很大.就我本人而言,感觉block用起来还是很爽的,但一直以来 ...

  3. iOS 中Block以及Blocks的使用,闭包方法调用

    OC: -(void)dataWithUrl:(NSString*)string AndId:(NSInteger)id returnName:(void(^)(NSString*name))back ...

  4. Using Blocks in iOS 4: Designing with Blocks

    In the first part of this series, we learned how to declare and call basic Objective-C blocks. The m ...

  5. Using Blocks in iOS 4: The Basics

    iOS 4 introduces one new feature that will fundamentally change the way you program in general: bloc ...

  6. iOS 开发该选择Blocks还是Delegates

    http://www.cocoachina.com/ios/20150925/13525.html 前文:网络上找了很多关于delegation和block的使用场景,发现没有很满意的解释,后来无意中 ...

  7. ios理解 -- Pro Mutlithreading and Memory Management for iOS and OS X with ARC, Grand Central Dispatch, and Blocks

    Capturing automatic variables Next, you need to learn what the “together with automatic (local) vari ...

  8. iOS中Blocks的介绍

    1. 什么是Blocks Blocks是C语言的扩充功能.如果用一句话来概括就是:带有自动变量的匿名函数. 第一次看见Blocks的时候,感觉很类似C语言的函数指针,尤其是Block类型变量,更是有极 ...

  9. ios高效开发--blocks相关

    1.替换delegate       如果我们有2个viewController,a和b,当我们从a界面push到b后,在b上面触发了一些事件,这些时间又会影响到a界面上的内容.            ...

随机推荐

  1. EnterpriseArchitectect 软件的勾选的几个选项对应的中文意思

    Business Process 业务流程 Requirements 需求分析 Use Case 用例 Domain Model 领域模型 Class 类 Database 数据库设计 Compone ...

  2. CSU1312:榜单(模拟)

    Description ZZY很喜欢流行音乐,每周都要跟踪世界各地各种榜单,例如Oricon和Billboard,现在给出每周各个单曲的销量请给出每周的TOP5以及TOP5中各个单曲的浮动情况. 量的 ...

  3. [RxJS] Combination operators: concat, startWith

    Some Observables may complete, and we may want to append another Observable to the one which just co ...

  4. 《Java并发编程实战》第六章 任务运行 读书笔记

    一. 在线程中运行任务 无限制创建线程的不足 .线程生命周期的开销很高 .资源消耗 .稳定性 二.Executor框架 Executor基于生产者-消费者模式.提交任务的操作相当于生产者.运行任务的线 ...

  5. XZ压缩

    XZ压缩最新压缩率之王 xz这个压缩可能很多都很陌生,不过您可知道xz是绝大数linux默认就带的一个压缩工具. 之前xz使用一直很少,所以几乎没有什么提起. 我是在下载phpmyadmin的时候看到 ...

  6. Qt 学习之路:输入元素

    前面的章节中,我们看到了作为输入元素的MouseArea,用于接收鼠标的输入.下面,我们再来介绍关于键盘输入的两个元素:TextInput和TextEdit. TextInput是单行的文本输入框,支 ...

  7. spring mvc DispatcherServlet详解之拾忆工具类utils

    DispatcherServlet的静态初始化 /** * Name of the class path resource (relative to the DispatcherServlet cla ...

  8. iOS流布局UICollectionView使用FlowLayout进行更灵活布局

    一.引言 前面的博客介绍了UICollectionView的相关方法和其协议中的方法,但对布局的管理类UICollectionViewFlowLayout没有着重探讨,这篇博客介绍关于布局的相关设置和 ...

  9. JAVA 生成PDF报表()

    许多应用程序都要求动态生成 PDF 文档.这些应用程序涵盖从生成客户对帐单并通过电子邮件交付的银行到购买特定的图书章节并以 PDF 格式接收这些图书章节的读者.这个列表不胜枚举.在本文中,我们将使用 ...

  10. ORACLE 中ROWNUM用法总结!(转)

    对于 Oracle 的 rownum 问题,很多资料都说不支持>,>=,=,between...and,只能用以上符号(<.<=.!=),并非说用>,>=,=,be ...