iOS block简介

版权声明:本文为博主原创文章,转载请注明出处。 https://blog.csdn.net/u011619283/article/details/87454289

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语言函数的不同之处:

  1. 没有函数名。
  2. 带有^

例如:

^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"
)

猜你喜欢

转载自blog.csdn.net/u011619283/article/details/87454289
今日推荐