#######以前只是看博客园,这么长时间了,也要有点干货分享给大家,不能只索不予,希望对大家有点帮助########
一.block的基本概念
/**
1. block的概念和特点:
1> block是 C语言中的一种数据类型
2> block是一个提前准备好的能工作的代码块,可以在任何需要的时候被执行。
3> 本质上是轻量级的匿名函数,可以作为其他函数的参数或返回值。
4> block本身可能有一个参数列表,也可能有一个返回值。
5> 可以将block赋给一个变量,并在需要的时候调用,就像调用一个普通函数一样。
*/
2> block是一个提前准备好的能工作的代码块,可以在任何需要的时候被执行。
3> 本质上是轻量级的匿名函数,可以作为其他函数的参数或返回值。
4> block本身可能有一个参数列表,也可能有一个返回值。
5> 可以将block赋给一个变量,并在需要的时候调用,就像调用一个普通函数一样。
*/
二.定义block
1、无参无返回值的block
(1) 格式:
void (^block的名称)() = ^ { 代码实现; };
(2) 定义block
// 定义block
void(^myBlock)() = ^ {
NSLog(@"hello");
};
// 定义函数
void demo() {
}
(3) 定义block有以下特点:
-
类型比函数多了一个^ 和 小括号。
-
设置数值,需要有一个等号, 等号右边有一个 ^, 内容是{}括起来的一段代码。
-
如果block没有参数,等号右边的小括号可以省略。
-
block定义完成之后,只是准备好的一个代码块,不能自己执行,这一点跟函数类似,函数必须被调用才会执行。
(4) 注意
定义block的时候,把它当成数据类型。
执行block的时候,把它当成函数。
2、带参无返回值的block
(1) 格式
void (^block的名称)(参数类型) = ^(参数列表) { 代码实现; };
(2) 定义block
// 定义带参数的block
void (^sumBlock)(int, int) = ^(int x, int y) {
NSLog(@"%d", x + y);
};
// 执行block
sumBlock(1, 5);
注意: 等号右边必须设置参数的名称。
3、带参带返回值的block
(1) 格式
返回值类型 (^block的名称)(参数类型) = ^返回值类型(参数列表) { 代码实现; };
(2) 定义block
int (^sumBlock2)(int, int) = ^ int (int a, int b) {
return a + b;
};
int sum = sumBlock2(10, 20);
NSLog(@"%d", sum);
4、block定义的速记符号
速记符号:inlineBlock 能够快速敲出一个block的基本结构
returnType (^blockName)(parameterTypes) = ^(parameters) {
statements;
};
注意:可以利用inlineBlock辅助记忆,但是不要依赖速记符号,必须能够手写定义block。
三.block常见面试题
1、第一道:block引用外部变量
void blockDemo1() {
int x = 10;
void (^myBlock)() = ^ {
NSLog(@"%d", x);
};
x = 20;
myBlock();
}
(1) 提问: 输出是多少?
(2) 答案: 输出是10.
(3) 原因: 定义block的时候,如果引用了外部变量,会对外部变量做一个copy,记录住定义block时变量的数值。
(4) 如果后续在block外部修改x的值,不会影响block内部的数值变化!
为什么?
因为我们定义的变量x是保存在栈区的,而block引用外部变量,对其做copy操作,会将外部变量copy到堆区。这样的话,在block外部修改的只是栈区的x,当然不会影响到block内部的x。
(5) 验证:
void blockDemo1() {
int x = 10;
NSLog(@"定义前: %p", &x); // 栈区
void (^myBlock)() = ^ {
NSLog(@"%d", x);
NSLog(@"in block: %p", &x); // 堆中的地址
};
NSLog(@"定义后: %p", &x); // 栈区
x = 20;
myBlock();
int x = 10;
NSLog(@"定义前: %p", &x); // 栈区
void (^myBlock)() = ^ {
NSLog(@"%d", x);
NSLog(@"in block: %p", &x); // 堆中的地址
};
NSLog(@"定义后: %p", &x); // 栈区
x = 20;
myBlock();
}
执行结果如下:
2、第二道:block内部修改外部变量的值
仍然是第一道题,不过如果就想在block内部修改x,该怎么做?
void blockDemo2() {
int x = 10;
NSLog(@"定义前: %p", &x);
void (^myBlock)() = ^ {
x = 80;
NSLog(@"%d", x);
NSLog(@"in block: %p", &x);
};
NSLog(@"定义后: %p", &x);
x = 20;
myBlock();
}
(1) 提问: 上面的代码有没有问题?若有问题,该怎么更正?更正后的输出结果是多少?
(2) 答案:
/**
(1) 如果在block内部直接修改x,会报错。因为默认情况下,不允许block内部修改外部变量的值。
(2) 如果想在block内部修改外部变量的值,需要使用__block修饰外部变量。
(1) 如果在block内部直接修改x,会报错。因为默认情况下,不允许block内部修改外部变量的值。
(2) 如果想在block内部修改外部变量的值,需要使用__block修饰外部变量。
*/
(3) 更正:
void blockDemo2() {
// 使用 __block 说明不再关心x数值的变化
// 定义block的时候,如果引用了外部使用__block修饰的变量,block定义之后,外部变量的地址同样会变成堆中的地址
__block int x = 10; // 在类型前面添加 __block
NSLog(@"定义前: %p", &x); // 栈区
void (^myBlock)() = ^ {
x = 80;
NSLog(@"%d", x);
NSLog(@"in block: %p", &x); // 堆中的地址
};
NSLog(@"定义后: %p", &x); // 堆中的地址
x = 20;
myBlock();
// 使用 __block 说明不再关心x数值的变化
// 定义block的时候,如果引用了外部使用__block修饰的变量,block定义之后,外部变量的地址同样会变成堆中的地址
__block int x = 10; // 在类型前面添加 __block
NSLog(@"定义前: %p", &x); // 栈区
void (^myBlock)() = ^ {
x = 80;
NSLog(@"%d", x);
NSLog(@"in block: %p", &x); // 堆中的地址
};
NSLog(@"定义后: %p", &x); // 堆中的地址
x = 20;
myBlock();
}
3> 输出结果是 80。
执行结果如下:
注意:
(1) 使用 __block 说明不再关心x数值的变化
(2) 定义block的时候,如果引用了外部使用__block修饰的变量,block定义之后,外部变量的地址同样会变成堆中的地址
也就是说:只要使用__block修饰了变量x,那么在block定义完成之后,再去修改x,修改的都是堆区的x。
3、第三道
void blockDemo3() {
// 指针记录的是地址
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
void (^myBlock)() = ^ {
[strM setString:@"lisi"];
};
myBlock();
NSLog(@"%@", strM);
}
- 提问: 以上代码有没有问题?
- 答案: 没有问题
- 分析: 如下
void blockDemo3() {
// 指针记录的是地址
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
NSLog(@"定义前: %p %p", strM, &strM);
void (^myBlock)() = ^ {
[strM setString:@"lisi"];
NSLog(@"in block: %p %p", strM, &strM);
// strM = [NSMutableString stringWithString:@"lisi"];
};
NSLog(@"定义后: %p %p", strM, &strM);
myBlock();
NSLog(@"%@", strM);
// 指针记录的是地址
NSMutableString *strM = [NSMutableString stringWithString:@"zhangsan"];
NSLog(@"定义前: %p %p", strM, &strM);
void (^myBlock)() = ^ {
[strM setString:@"lisi"];
NSLog(@"in block: %p %p", strM, &strM);
// strM = [NSMutableString stringWithString:@"lisi"];
};
NSLog(@"定义后: %p %p", strM, &strM);
myBlock();
NSLog(@"%@", strM);
}
执行结果如下:
1>.首选要明确以下关系:
strM 本质是一个指针变量,表示的是字符串对象的地址 0x1001038d0
&strM 表示的是指针strM在栈区的地址
block内部默认不能修改外部变量的值。在这道题中,外部变量是strM,它的值是0x1001038d0。
当我们在block内部使用 [strM setString:@”lisi”];这句代码,首先会将strM拷贝到堆区,这个时候strM的值并没有改变,改变是&strM(即strM的地址,因为strM由栈区来到了堆区)然后做 setString的操作,只是在修改strM指向的字符串对象的值,也没有修改strM的值。所以,如果block内部是[strM setString:@”lisi”];程序是没有问题的。
但是,如果block内部是: strM = [NSMutableString stringWithString:@”lisi”];相当于重新创建了一个字符串对象,让strM指向它,这样的话,很显然strM的指向发生了改变,那么它保存的地址也就发生了改变,即外部变量的值被改变了。因此会报错。
Block的内存管理:
循环遍历
我们常用的循环遍历有三种:
for循环
for-in循环
block遍历循环:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
// 先有一个数组
NSArray *array = @[@"1", @"2", @"3", @"4", @"5",];
// enumerateObjectsUsingBlock: 是 NSArray 的方法 :用于block的枚举对象
// obj: 数组元素
// idx: 数组下标
// *stop: 是否停止遍历
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
if (idx == 2) {
*stop = YES;
}
}];
// idx: 数组下标
// *stop: 是否停止遍历
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
NSLog(@"%@", obj);
if (idx == 2) {
*stop = YES;
}
}];
}
控制台输出结果:
2016-01-29 18:44:47.312 block 遍历[5011:700719] 1
2016-01-29 18:44:47.312 block 遍历[5011:700719] 2
2016-01-29 18:44:47.312 block 遍历[5011:700719] 2
2016-01-29 18:44:47.312 block 遍历[5011:700719] 3