iOS block简介
Posted Haley_Wong
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS block简介相关的知识,希望对你有一定的参考价值。
Block 是ios 4 才引入的C语言扩充功能。
block是什么?
block 就是带有自动变量(就是局部变量)值的匿名函数。顾名思义就是带有自动变量(也就是局部变量)值的不带名称的函数。
为什么说是带有自动变量值呢
因为block会持有在其内部所使用的变量的值,即使外部对该变量的值做了修改,也不会影响block内部使用的值。所以就像是进入匿名函数时,自带一些数据值一样。
先回顾下C语言中的函数里可能使用的变量类型:
- 自动变量(局部变量)
- 函数的参数
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
其中,在函数的多次调用之间能够传递值的变量有:
- 静态变量(静态局部变量)
- 静态全局变量
- 全局变量
虽然这些变量的作用域不同,但在整个程序中,一个变量总保持在一个内存区域。因此,虽然多次调用函数,但该变量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量值。
int buttonId = 0;
void buttonCallback(int event)
printf("buttonId:%d event=%d\\n", buttonId, event);
上述代码,如果只有一个按钮,那么该源代码可以正常运行。但是,如果有多个按钮,那么回调就会出现异常:
int buttonId;
void buttonCallback(int event)
printf("buttonId:%d event=%d\\n", buttonId, event);
void setButtonCallbacks()
for (int i = 0; i < BUTTON_MAX; ++i)
buttonId = i;
setButtonCallback(BUTTON_IDOFFSET + i, &buttonCallback);
因为全局变量buttonId只有一个,所以所有回调都使用for循环最后的值,所以打印出来的结果buttonId 都一样。
如果要解决这个问题,可以不使用全局变量,回调方将按钮的ID作为函数参数传递。
void buttonCallback(int buttonId, int event)
printf("buttonId:%d event=%d\\n", buttonId, event);
如果,我们要实现这样的效果,就需要构造一个对象,保存回调,以及回调中的参数等。这会增加代码的长度。
这时候,如果使用Block就淡淡很多了。
void setButtonCallbacks()
for (int i = 0; i < BUTTON_MAX; ++i)
buttonId = i;
setButtonCallbackUsingBlock(BUTTON_IDOFFSET + i, ^(int event)
printf("buttonId:%d event=%d\\n", buttonId, event);
);
像这样,使用Block可以不声明创建C++和Objective-C类,也没有使用静态变量、静态全局变量或者全局变量时的问题,仅用编写C语言函数的源码代码量即可解决问题。这就是Block的好处。
带有自动变量值的匿名函数这一概念并不仅仅指Block,它还存在于其他许多程序语言中。在计算机科学中,次概念也称为闭包(Closure)、lambda计算等。
block的用法
block的语法
^
返回值类型
参数列表
表达式
它与一般的C语言函数的不同之处:
- 没有函数名。
- 带有
^
。
例如:
^int (int count)return count + 1;
当然,block 有很多简写方式,首先返回值类型是可以省略的。
省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句,就使用void类型。表达式中含有多个return语句时,所有return的返回值类型必须相同。
所以,前面的block省略返回值类型时,可以这样表示:
// 当返回值类型省略,参数列表不为空
^(int count)return count + 1;
如果,不适用参数时,参数列表也可以省略。比如下面这个block:
^void (void)printf("这是block");
可以省略为如下形式:
^printf("这是省略后的block");
当然了,如果只省略参数列表,不省略返回值类型也是可以的。
^int (void)return 1;
省略掉参数列表,可以这样表示:
^int return 1;
block类型变量
前面说到,block语法单从其记述方式上来看,除了没有名称以及带有^
以外,其他的与C语言函数定义相同。在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型变量。
例如:
int func(int count)
return count + 1;
int (*funcptr)(int) = &func;
同样地,在block语法下,可将block语法赋值给声明为block类型的变量中。源代码中一旦使用Block语法也就相当于生成了可赋值给Block类型变量的“值”。Block中由Block语法生成的值也被称为“Block”。所以“Block”既指Block语法,也指由Block语法所生成的值。
由声明函数指针类型变量的语法,可联想出声明block类型变量的示例:
int (^blk)(int);
声明block类型变量的语法:
返回值类型
(^变量名)
参数列表
1.在函数内部使用block类型变量
我们可以将Block赋值给Block类型变量:
int (^blk)(int) = ^(int count)return count + 1;;
2.作为函数的参数
在函数的参数中使用Block类型变量可以向函数传递Block。
C++语法是这样的:
void func(int (^blk)(int))
printf("这是使用block作为函数的参数例子");
当然,由于Objective-C函数的表示方式与C++有所不同,block参数的变量名放后面即可。
- (void)testUsingBlock:(void (^)(int count))block
3.作为对象的实例变量或者property属性
作为实例变量或property属性使用时,与在函数中使用block类型变量一致。
interface ViewController : UIViewController
int (^instanceBlock)(int count);
property (nonatomic, copy) int (^propertyBlock)(int count);
end
赋值和使用过程,也与在函数中给block类型变量赋值一样。
- (void)viewDidLoad
[super viewDidLoad];
self.propertyBlock = ^int(int count)
NSLog(@"这是propertyBlock");
return count + 1;
;
instanceBlock = ^int (int count)
NSLog(@"这是instanceBlock");
return count + 1;
;
instanceBlock(1);
self.propertyBlock(2);
block类型变量重命名
由于在多种场景下使用Block类型变量时,记述方式极为复杂。我们可以使用typedef,为block类型变量重命名。
typedef int (^Block_t)(int count);
通过typedef重命名,我们就可以将block类型变量,像使用基本数据类型那样使用了。
typedef int(^Block_t)(int count);
@interface ViewController : UIViewController
// 实例变量
Block_t instanceBlock;
// property属性
@property (nonatomic, copy) Block_t propertyBlock;
// 函数的参数
- (void)testUsingBlock:(Block_t)block;
// block作为返回值
- (Block_t)test:(int)count;
@end
block 截获的变量的理解
前面说过,block会截获内部使用到的变量的值,怎么理解呢?
这里分别就基本数据类型和对象类型来解释。
截获基本数据类型的变量值
像 int / double /bool 等一些基本数据类型,就意味着在执行到block语法前,使用的这些变量的值是多少,那么在后面调用该block时,就是多少,即使在执行block前修改过,也不会影响block内部的值。
举例来说就是这样:
int val = 10;
int (^testBlk)(void) = ^
NSLog(@"val = %d", val);
return val;
;
val = 20;
testBlk();
打印出来的内容一定是:val = 10
。
截获对象类型的变量值
如果block内部使用了某个对象,那么block内部截获的就是在执行到block前,该变量对应指针指向的对象。
我创建了一个Model对象
还是拿代码举例来说:
// 1.创建model对象1
HLModel *model = [[HLModel alloc] init];
model.height = 10;
model.name = @"model1";
model.personArray = [NSMutableArray arrayWithObject:@"张三"];
NSLog(@"打印model:%@",model);
// 2.将model 赋值给变量tagetModel
HLModel *targetModel = model;
// 3.创建一个block变量
int (^testBlk)(void) = ^
NSLog(@"targetModel = %@", targetModel);
return 1;
;
// 4.创建model2,并将其赋值给targetModel
HLModel *model2 = [[HLModel alloc] init];
model2.height = 20;
model2.name = @"model2";
model2.personArray = [NSMutableArray arrayWithObject:@"李四"];
NSLog(@"打印model2:%@",model2);
targetModel = model2;
// 5.执行block
testBlk();
我重写了HLModel的description方法, 打印结果:
2019-02-17 13:25:05.550548+0800 TableViewTest[42121:1747768] 打印model:<HLModel:0x600000646780>, name = model1, personArray = (
"\\U5f20\\U4e09"
)
2019-02-17 13:25:05.550777+0800 TableViewTest[42121:1747768] 打印model2:<HLModel:0x600000612220>, name = model2, personArray = (
"\\U674e\\U56db"
)
2019-02-17 13:25:05.550967+0800 TableViewTest[42121:1747768] targetModel = <HLModel:0x600000646780>, name = model1, personArray = (
"\\U5f20\\U4e09"
)
可以看出block里面targetModel使用的依然是model的那个对象,不管后面是否将targetModel赋值为其他对象,都不会改变。
block中截获对象时,关于对象的属性的值是怎样的呢?
也就是说block结果的对象的属性,改变是否会起做用呢?
还是用源码示例来说话吧:
// 1.创建model对象1
HLModel *model = [[HLModel alloc] init];
model.height = 10;
model.name = @"model1";
model.personArray = [NSMutableArray arrayWithObject:@"张三"];
NSLog(@"打印model:%@",model);
// 2.将model 赋值给变量tagetModel
HLModel *targetModel = model;
// 3.创建一个block变量
int (^testBlk)(void) = ^
NSLog(@"targetModel = %@", targetModel);
return 1;
;
// 4.改变model的属性值
model.height = 20;
model.name = @"model2";
model.personArray = [NSMutableArray arrayWithObject:@"李四"];
// 5.执行block
testBlk();
打印结果:
2019-02-17 13:34:37.247169+0800 TableViewTest[42333:1759888] 打印model:<HLModel:0x6000030708a0>height = 10, name = model1, personArray = <0x600003e78c90> (
"\\U5f20\\U4e09"
)
2019-02-17 13:34:37.247378+0800 TableViewTest[42333:1759888] targetModel = <HLModel:0x6000030708a0>height = 20, name = model2, personArray = <0x600003e787b0> (
"\\U674e\\U56db"
)
由上面执行结果可知,尽管不能修改对象,但是可以修改对象的属性。
__block说明符
注意:这里是两个英文输入法下的_。
block截获的只是变量的瞬间值,不能再block中修改变量的值,否则会报编译错误。
比如这样:
int val = 10;
void (^testBlock)(void) = ^
val = 20;
;
val = 30;
编译器会报错:
Variable is not assignable (missing __block type specifier)
修改对象类型变量的值(赋值为新的对象,这样会改变变量的指针)也会报一样的错误。
修改对象的属性的值,或者调用对象的方法是没有问题的。
比如下面这样都是没有问题的:
// 1.创建model对象1
HLModel *model = [[HLModel alloc] init];
model.height = 10;
model.name = @"model1";
model.personArray = [NSMutableArray arrayWithObject:@"张三"];
NSLog(@"打印model:%@",model);
// 2.将model 赋值给变量tagetModel
HLModel *targetModel = model;
// 3.创建一个block变量
int (^testBlk)(void) = ^
targetModel.height = 30;
targetModel.name = @"model2";
targetModel.personArray = [NSMutableArray arrayWithObject:@"李四"];
NSLog(@"targetModel = %@", targetModel);
return 1;
;
// 4.执行block
testBlk();
执行结果如下:
2019-02-17 13:50:10.551448+0800 TableViewTest[42659:1778372] 打印model:<HLModel:0x6000020da860>height = 10, name = model1, personArray = <0x600002ed1830> (
"\\U5f20\\U4e09"
)
2019-02-17 13:50:10.551677+0800 TableViewTest[42659:1778372] targetModel = <HLModel:0x6000020da860>height = 30, name = model2, personArray = <0x600002ed0960> (
"\\U674e\\U56db"
)
如果,我们要在block内部修改截获的变量的值怎么办呢?
我们可以在变量前添加__block说明符。
示例代码:
__block int val = 10;
void (^testBlock)(void) = ^
val = 20;
NSLog(@"val = %d", val);
;
testBlock();
打印结果为:val = 20
。
注意:添加__block说明符的变量,在截获变量后,执行block前修改变量的值,也是会生效的。
比如,这样:
__block int val = 10;
void (^testBlock)(void) = ^
NSLog(@"val = %d", val);
;
val = 30;
testBlock();
打印结果是:val = 30
。
对象类型的变量使用方法与基本数据类型是一样的:
// 1.创建model对象1
HLModel *model = [[HLModel alloc] init];
model.height = 10;
model.name = @"model1";
model.personArray = [NSMutableArray arrayWithObject:@"张三"];
NSLog(@"打印model:%@",model);
// 2.将model 赋值给变量tagetModel
__block HLModel *targetModel = model;
// 3.创建一个block变量
int (^testBlk)(void) = ^
NSLog(@"targetModel = %@", targetModel);
return 1;
;
// 4.创建model2,并将其赋值给targetModel
HLModel *model2 = [[HLModel alloc] init];
model2.height = 20;
model2.name = @"model2";
model2.personArray = [NSMutableArray arrayWithObject:@"李四"];
NSLog(@"打印model2:%@",model2);
targetModel = model2;
// 5.执行block
testBlk();
打印结果是:
2019-02-17 13:57:19.764137+0800 TableViewTest[42878:1788332] 打印model:<HLModel:0x600001e3dd00>height = 10, name = model1, personArray = <0x600001034150> (
"\\U5f20\\U4e09"
)
2019-02-17 13:57:19.764301+0800 TableViewTest[42878:1788332] 打印model2:<HLModel:0x600001e39000>height = 20, name = model2, personArray = <0x60000102cab0> (
"\\U674e\\U56db"
)
2019-02-17 13:57:19.764451+0800 TableViewTest[42878:1788332] targetModel = <HLModel:0x600001e39000>height = 20, name = model2, personArray = <0x60000102cab0> (
"\\U674e\\U56db"
)
在block内修改变量的指针
// 1.创建model对象1
HLModel *model = [[HLModel alloc] init];
model.height = 10;
model.name = @"model1";
model.personArray = [NSMutableArray arrayWithObject:@"张三"];
NSLog(@"打印model:%@",model);
// 2.将model 赋值给变量tagetModel
__block HLModel *targetModel = model;
// 3.创建一个block变量
int (^testBlk)(void) = ^
// 4.创建model2,并将其赋值给targetModel
HLModel *model2 = [[HLModel alloc] init];
model2.height = 20;
model2.name = @"model2";
model2.personArray = [NSMutableArray arrayWithObject:@"李四"];
NSLog(@"打印model2:%@",model2);
targetModel = model2;
NSLog(@"targetModel = %@", targetModel);
return 1;
;
// 5.执行block
testBlk();
打印结果:
2019-02-17 14:00:18.474336+0800 TableViewTest[42947:1792414] 打印model:<HLModel:0x600003098f00>height = 10, name = model1, personArray = <0x600003e8c720> (
"\\U5f20\\U4e09"
)
2019-02-17 14:00:18.474493+0800 TableViewTest[42947:1792414] 打印model2:<HLModel:0x60000309c640>height = 20, name = model2, personArray = <0x600003e904b0> (
"\\U674e\\U56db"
)
2019-02-17 14:00:18.474625+0800 TableViewTest[42947:1792414] targetModel = <HLModel:0x60000309c640>height = 20, name = model2, personArray = <0x600003e904b0> (
"\\U674e\\U56db"
)
以上是关于iOS block简介的主要内容,如果未能解决你的问题,请参考以下文章