以前看到Block觉得也没什么,不就是类似函数的东西,这东西在C#里就是委托,在Java里就是块,有什么稀奇的。但看到一点进阶的内容后,发现这个东西确实有用。
所以做下总结。
一、块的基本用法
块的语法构成:
^[返回值类型](形参1,形参2,...) { //执行体 }
在JS里,函数是可以做为变量的,OC的块也用变量接收,变量的声明语法:
//可以把形参名省略,只保留类型 返回值类型 (^块名) (形参1,形参2,...);
再来看几个例子就懂基本怎么用了:
int main(int argc , char * argv[]) { @autoreleasepool{ // 定义不带参数、无返回值的块 void (^printString)(void) = ^(void) { NSLog(@"我正在开始学习Objective-C的块"); }; // 使用printString调用块 printString(); // 定义带参数、有返回值的块 double (^hypot)(double , double) = ^(double num1, double num2) { return sqrt(num1 * num1 + num2 * num2); }; // 调用块,并输出块的返回值 NSLog(@"%g" , hypot(3, 4)); // 也可以先只定义块变量:定义带参数、无返回值的块 void (^print)(NSString*); // 再将块赋给指定的块变量 print = ^(NSString* info) { NSLog(@"info参数为:%@" , info); }; // 调用块 print(@"树狗狗"); } }
还有一点要注意,块可以访问程序局部变量的值,但不能进行修改:
int main(int argc , char * argv[]) { @autoreleasepool{ // 定义局部变量 int my = 20; void (^printVar)(void) = ^(void) { // 尝试对局部变量赋值,程序将会报错 // my = 30; // ① // 访问局部变量的值是允许的 NSLog(@"%d" , my); }; // 再次将my赋值为45 my = 45; // 调用块 printVar(); } }
上在程序尝试修改局部变量,但会报错,而调用块前,把局部变量修改为45后再调用块,这个时候输出的却是:20,因为块定义时会把局部变量的值保存,而不是运行时去读取。
但这却是可以解决的,OC提供了一个_block关键字,用在定义局部变量时,让块里可以等到运行时访问,或者修改都可以。这样用:
int main(int argc , char * argv[]) { @autoreleasepool{ // 定义__block修饰的局部变量 __block int my = 20; void (^printVar)(void) = ^(void) { // 运行时候访问、获取局部变量的值,此处输出45 NSLog(@"%d" , my); // 尝试对__block局部变量赋值是允许的 my = 30; // ① // 此处输出30 NSLog(@"%d" , my); }; // 再次将my赋值为45 my = 45; // 调用块 printVar(); // 由于块修改了__block局部变量的值,因此下面代码输出30 NSLog(@"块执行完后,my的值为:%d" , my); } }
既然可以完全访问,为什么不一开始就干脆去掉不能访问这条规则?难道仅仅是为了在运行时免去查找的过程么?搞不懂架构OC语言的人。
当然,如果块就这点用法的话,那它就没有什么必要存在了,在实际中,用的最多的是回调。
二、页面传值
要完成这样的一个功能:
在ViewController A中,点击按钮,push到ViewController B中,在B中的输入框输入值,返回到A中,在A中的Label上显示出来
第一种方法:协议代理。 就像Android里的Fragment传值那样。
首先定义一个协议:
//ViewController A要服从该协议,实现协议中的方法 @protocol TransportDelegate <NSObject> - (void)setTextValue:(NSString *)text; @end
ViewController A中代码:
//ViewControllerA.m 文件 @interface ViewController ()<TransportDelegate> @property (strong, nonatomic) IBOutlet UILabel *label; @end //点击Button进入下一个ViewCOntroller B页面 - (IBAction)nextBtnClicked:(id)sender { NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil]; //相当于让下一个视力控制器持有自身的一个引用 nextVC.delegate = self; [self.navigationController pushViewController:nextVC animated:YES]; } //实现协议TransportDelegate中的方法 #pragma mark - TransportDelegate method - (void)setTextValue:(NSString *)text { //self.nextVCInfoLabel是显示NextViewController传递过来的值 self.label.text = tfText; }
看到这,应该就可以猜到,ViewController B里,持有一个协议的引用,返回时,回调协议引用对象的方法:
@interface NextViewController : UIViewController @property (nonatomic, assign) id<TransportDelegate> delegate;
@property (strong, nonatomic) IBOutlet TextField* textField;
@end //NextViewController.m 文件 //返回前一个ViewController页面 - (IBAction)backBtnClicked:(id)sender { if (self.delegate && [self.delegate respondsToSelector:@selector(setTextValue:)]) { [self.delegate setTextValue:self.textField.text]; } [self.navigationController popViewControllerAnimated:YES]; }
这就完成了页面间传值。总结来说就是,让B持有一个A的引用,在B中回调A中的方法,引用的桥梁是协议。用起来很麻烦,传个值而已还要定义个什么协议! 用Block就简单很多。
第二种方法:用Block
ViewController A中代码:
- (IBAction)btnClicked:(id)sender { NextViewController *nextVC = [[NextViewController alloc] initWithNibName:@"NextViewController" bundle:nil]; //这里才给ViewController B的块变量赋值 nextVC.transportBlock = ^(NSString *text){ [self setLabelText:text]; }; [self.navigationController pushViewController:nextVC animated:YES]; } #pragma mark - setLabeText method - (void)setLabelText:(NSString *)text { self.label.text = text; }
还是一样,看到这里,应该就可以猜到了,ViewController B里,直接调用块变量就可以:
//NextViewController.h 文件 @interface NextViewController : UIViewController @property (nonatomic, copy) void (^transportBlock)(NSString *text);
@property (strong, nonatomic) IBOutlet TextField* textField;
@end //NextViewContorller.m 文件 - (IBAction)backBtnClicked:(id)sender { if (self.transportBlock) { self.transportBlock(self.textField.text); } [self.navigationController popViewControllerAnimated:YES]; }
简单了许多,不用实现协议,虽然看起来也像反向代理.