1、block定义
是准备好的一段代码片段,在需要的时候执行
注意:block 是C语言的格式
2、block格式
格式:返回类型 (^blockName) (参数类型) = ^(形参列表) { 代码实现 };
提示:如果没有参数,格式可以简化
格式:返回类型 (^blockName) () = ^ { 代码实现 };
在Xcode代码编辑中输入 inlineBlock,有提示,这是block格式的快捷方式
3、示例代码
(1)先看看block怎么用
void (^myBlock)() = ^ { NSLog(@"hello block"); }; // 调用block myBlock();
输出结果是:hello block
(2)带参数
void (^sumBlock)(int, int) = ^(int x, int y) { NSLog(@"%d", x + y); }; sumBlock(10, 20);
输出结果是:30
(3)block到底干了什么
int x = 10; NSLog(@"%p", &x); // 在定义block时,如果block中使用到外部的“局部”变量,block会建立该变量的副本(会记录当前x的数值) void(^myBlock)() = ^ { NSLog(@"%d", x); NSLog(@"%p", &x); }; myBlock();
输出结果:
0xbfffc8e4
10
0x8e632a4
从地址可以看出block内部的x跟外部的x不是同一个,不过内容都是10
block的作用:在 NSLog(@"%d", x); 中,因为使用到“局部变量”x,所以myBlock会建立x的副本,在输出 NSLog(@"%d", x);的时候把x的值输出。
所以,可以得出结论,block里面用到了谁,block就会建立谁的副本,在使用的时候输出它的值。
4、常见面试题
(1)在使用外部变量时,默认不允许在块代码中直接修改外部变量的数值
上边这张图很明显的说明,默认情况下是不允许在块代码中直接修改外部变量的数值的
(2)如果要修改外部变量的值,需要在定义变量的时候用 __block 修饰
(3)__block的另一个特点:被__block修饰的变量,在值改变的时候,块代码内会同步被改变后的值
上面这段代码的输出结果是:
0xbfffc8e8
20
=== 0x8cadbb0
20
结果表明,在 x = 20; 之后,block内部的x值的副本也改变了
如果把 __block 去掉,输出结果是:
0xbfffc8e4
10
=== 0xa275f74
20
注意:在x = 20;之后调用block才会同步20的值,一定不要忘了代码是顺序执行的
提示:为了保证代码的可读性,通常不使用 __block
总结:上面介绍的block的用法,block本质上是创建了副本,要注意的是,副本的内容还是变量的内容,而副本在内存中存储的地址变了
(4)再来看block保存指针变量
1 // 指针包含指针的地址,和指向的地址 2 NSMutableString *str = [NSMutableString stringWithString:@"hello"]; 3 NSLog(@"%p %p", &str, str); 4 5 void(^myBlock)() = ^ { 6 // 修改指针指向内存空间的内容 7 [str setString:@"world"]; 8 NSLog(@"%p %p", &str, str); 9 NSLog(@"%@", str); 10 }; 11 12 myBlock(); 13 NSLog(@"%p %p", &str, str); 14 NSLog(@"%@", str);
输出结果是:
0xbfffc8e4 0x8c281f0
0x8d0b834 0x8c281f0
world
0xbfffc8e4 0x8c281f0
world
这幅图并不准确,block保存的副本具体在哪儿存着,我也不是太清楚,因为从地址观察,block副本很可能不是在栈中,不过,有一点确定的是,block肯定建立了一个副本。可以将block当做特殊的对象。
第3行代码,分别输出了栈中指针str的地址和堆中对象hello的地址。
第5行代码,block建立了str的副本指针,内容不变,地址变了;然后第7行代码修改了block中str指向的对象的内容,因此堆中对象的内容变为world。
第13行代码,可以说明,指针str的地址和指向的地址都没有变。
第14行代码,输出了str指针指向的对象内容。