前言
什么是嵌套block?为了防止误解,先上代码
typedef void(^Block0)(NSString *str);
typedef void(^Block1)(NSString *str, Block0 block0);
本文所说的嵌套block是指,像Block1那样,参数中有block的block(返回值不一定是void)。
刚接触嵌套block时可能会觉得有点绕,其实没那么难。个人写代码更喜欢用block而不是notification或者delegate,因为业务逻辑不会过于分散。带参block是最常用的block,我们应该对block传递数据并不陌生。但当参数中又有block时,数据互传的形式可以有所改变,本文从这里开始。
分析
先从简单的参数不含block的block说起
typedef void(^Block0)(NSString *str);
@interface A : NSObject
@property (nonatomic, copy) Block0 block0;
@end
通常会在另一个类中这么用:
- (void)test {
[A a:^(NSString *str) {
// do something
}];
}
即,block0是通过外部赋值的,然后内部保存block并在某些时机调用。当然,我们也可以在A类内部提前将block定义好,然后内/外部在某些时机调用。但是并没有人这么写,因为这种写法与直接定义方法无异,我们正常的写法就是在内部实现一些方法,内/外部在需要的时候调用,写成block实在是有点傻(但是,当block为参数时,某些情况下我们确实会这么写)。
通过这个简单的例子只想说明一点,block即可在内部定义也可在外部定义,不要被固定思维局限(好像是废话,当然是内外都可定义,别急,继续往下看)
通常,类中的block直接通过外部赋值,然后内部在某些恰当的时机调用,如果有参数,调用的时候必然要传参。参数不含block时,通常block的参数都由内部传递(从外部传参进来,由内部调用,再在外部使用参数,这种做法也是傻到不行)。但是,当block的参数有block时,这个参数block从外部传进来就变得有意义了,毕竟block是匿名函数。
先从我们熟悉的block参数均为内部参数说起。
举个例子
typedef void(^Block0)(NSString *str);
typedef void(^Block1)(NSString *str, Block0 block0);
@interface A : NSObject
@property (nonatomic, copy, readonly) Block1 block1;
+ (instancetype)a:(Block1)block1;
- (void)execute;
- (void)executeWithBlock:(Block0)block;
@end
@implementation A
+ (instancetype)a:(Block1)block1 {
A *a = [[self alloc] init];
a->_block1 = [block1 copy];
return a;
}
- (void)execute {
Block0 block0 = ^(NSString *str){
NSLog(@"block0 execute: %@", str);
};
_block1(@"block1 parm", block0);
}
- (void)executeWithBlock:(Block0)block {
_block1(@"block1 parm", block);
}
@end
既然block参数均为内部参数,显然执行execute
方法,block1的参数NSString Block0
都从内部传递,这种写法对应的应用场景就是数据互传了。
数据互传
举个简单的例子:同事A君 B君
,A有事需要B做,B做完之后将结果反馈给A,A拿到结果继续做事,并将得到的结果反馈给B/C/D...以此类推。最简单的模型类似于TCP/IP中的三次握手
假设这么写:
- (void)viewDidLoad {
[super viewDidLoad];
A *a = [A a:^(NSString *str, Block0 block0) {
NSLog(@"block 1 execute: %@", str);
!block0 ? : block0(@"block 0 parm");
}];
[a execute];
}
什么意思?block1的参数NSString Block0
都从A中内部提供,但是Block0
的参数此时是从外部提供的。这样一来,当A中执行block1时,vc中可传递block0的参数,从而形成上面的数据传递模型。这里是最简单的A与vc互传,我们可以根据业务需求自行更改交叉传递模型,但是不建议多个对象交叉传递数据,2个互传最好。
响应式编程
前面说的是参数block由内部传递的情况,当由外部传递时,显然需要调用executeWithBlock:
- (void)viewDidLoad {
[super viewDidLoad];
A *a = [A a:^(NSString *str, Block0 block0) {
NSLog(@"block 1 execute: %@", str);
!block0 ? : block0(@"block 0 parm");
}];
// [a execute];
[a executeWithBlock:^(NSString *str) {
NSLog(@"block0 execute: %@", str);
}];
}
好像没什么区别?因为block0只写了一句简单的打印,如果有一个B
类,B
中也存在这种嵌套block,我们在block0中调用B
中的嵌套block会怎样?
@interface B : NSObject
@property (nonatomic, copy, readonly) Block1 block1;
+ (instancetype)b:(Block1)block1;
- (void)execute;
- (void)executeWithBlock:(Block0)block;
@end
@implementation B
+ (instancetype)b:(Block1)block1 {
B *b = [[self alloc] init];
b->_block1 = [block1 copy];
return b;
}
- (void)execute {
Block0 block0 = ^(NSString *str){
NSLog(@"Class B block0 execute: %@", str);
};
_block1(@"Class B block1 parm", block0);
}
- (void)executeWithBlock:(Block0)block {
_block1(@"Class B block1 parm", block);
}
@end
现在代码这样写:
- (void)viewDidLoad {
[super viewDidLoad];
A *a = [A a:^(NSString *str, Block0 block0) {
NSLog(@"block 1 execute: %@", str);
!block0 ? : block0(@"block 0 parm");
}];
// [a execute];
B *b = [B b:^(NSString *str, Block0 block0) {
NSLog(@"Class B block 1 execute: %@", str);
!block0 ? : block0(@"Class B block 0 parm");
}];
[a executeWithBlock:^(NSString *str) {
NSLog(@"block0 execute: %@", str);
[b execute];
}];
}
这样就可以达到联动的效果。当然,响应式编程我们更习惯结合链式编程来写,这样就可以不断的点下去。如果对链式编程不了解,可以先看这篇文章 传送门。根据初级链式编程的原理,我们同样可以将嵌套block应用到链式编程中,从而写出更高级的链式编程接口。
链式编程
在开始前,请确保已经理解链式编程基本原理 传送门
@class C;
typedef C *(^Block2)(NSString *str);
typedef C *(^Block3)(NSString *str, Block2 block2);
@interface C : NSObject
@property (nonatomic, copy) NSString *result;
- (Block2)t2;
- (Block3)t3;
@end
@implementation C
- (instancetype)init {
if (self = [super init]) {
_result = @"";
}
return self;
}
- (Block2)t2 {
return ^(NSString *str) {
_result = [NSString stringWithFormat:@"%@ %@", _result, str];
return self;
};
}
- (Block3)t3 {
return ^(NSString *str, Block2 block2) {
return block2(str);
};
}
@end
此时可以这样写:
- (void)viewDidLoad {
[super viewDidLoad];
C *c = [[C alloc] init];
Block2 block2 = ^(NSString *str) {
c.result = [NSString stringWithFormat:@"%@ %@", c.result, str];
return c;
};
c.t3(@"t3", block2).t2(@"t2");
NSLog(@"result: %@", c.result);
}
同理,这里block2的定义可以更复杂,具体实现根据业务逻辑来。
当我们将链式编程与响应式编程的写法结合,其实就是RAC
中神乎其神的写法了。
总结:带block参数的block的最大魅力在于,参数block可执行而非仅限于数据传递。
Have fun!