1、Block的声明
格式:
返回值 (^Block名称) (参数类型);
举例:
//无参无返回值的Block
void (^Block01)();
//有参无返回值的Block
void (^Block02)(int);
//无参有返回值的Block
int (^Blcok03)();
//有参有返回值的Block
int (^Blcok04)(NSString*, NSString *);
2、Block的定义
格式:
返回值类型 (^Block名称) (参数类型) = ^(参数) {
代码体
};
举例:
//定义无参无返回值的Block
void (^Block01)() = ^(){
NSLog(@"无参无返回值的Block");
};
//定义有参无返回值的Block
void (^Block02)(NSString *) = ^(NSString * name) {
NSLog(@"%@",name);
};
//定义有返回值无参Block(如果没有参数,=后面()可以省略)
int (^Block03) () = ^() {
return 88;
};
//定义有参有返回值的Block
int (^Block04)(int,int) = ^(int a, int b) {
return a + b;
};
Tips:快速生成Block定义:在方法体内输入
inlineBlock
,选中提示,就会自动生成
3、Block的调用
//调用无参无返回值Block
Block01();
//调用有参无返回值Block
Block02(@"lee");
//调用无参有返回值Block
int a = Block03();
//调用有参有返回值Block
int b = Block04(66,88);
4、Block使用场景
- 保存代码
-
在一个方法中定义,在另一个方法中使用
- 这样就需要将Block声明为成员属性
方法1:
@property (nonatomic,strong) int (^Block)(int ,int); //Block怎么声明就怎么定义成属性
-
方法2:给Block类型起个别名
typedef void (^BlockType) (); //BlockType:Block类型(有无参数无、有返回值等等)
@property (nonatomic,strong) BlcokType block;
- 然后给成员属性赋值
self.Block = ^(int a, int b) {
return a + b;
};
- 在别的方法中直接调用成员属性的Block即可
NSLog(@"%d",self.Block(66,88));
- 在一个类中定义,在另一个类中使用(常用传值)
需求:A控制器跳转到B控制器,当点击B控制器view的时候,让B控制器传一个字符串回A,并dismiss B控制器
- 第一步:在B控制器声明一个Block属性
/** Block属性 */
@property(nonatomic,strong) void (^ Block) (NSString *);
- 第二步:在跳转到B控制器之前,给B控制器的Block属性赋值(定义B控制器的Block)
//点击按钮跳转到下一个控制器
-(IBAction)nextVCButtonClick
{
LGTestVC * testVC = [[LGTestVC alloc] init];
testVC.Block = ^(NSString * name,UIViewController * VC){
NSLog(@"%@",name);
[VC dismissViewControllerAnimated:YES completion:nil];
};
[self presentViewController:testVC animated:YES completion:nil];
}
- 在B控制器调用Block即可传值(做事情)
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (_Block != nil)
{
_Block(@"lee",self);
}
}
- 作为方法参数使用
注意:Block当做参数来使用,并不是马上调用;要在Block做什么事情由外部决定,什么时候做事情由方法内部决定。举个比较常用的例子:
self.view.transform = CGAffineTransformMakeScale(0.5, 0.5);
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
- 作为方法返回值使用(多用于框架封装)
Blcok作为返回值使用多用于框架封装,比较典型的一个例子就是:
Masnoary
, 最大的特点就是可以很方便的使用点语法,也就是链式编程。具体的Block作为返回值使用请参考摩卡奇童鞋的这篇文章 ,在此不再赘述
5、Block内存管理
- MRC:
- 如果Block没有引用外部局部变量,Block就保存在内存的全局区
如果Block引用了外部局部变量,Block就保存在内存的栈内存区
-
Block只能使用copy,不能使用retain(使用retain还是保存在栈里面,会被销毁)
注意:
block可以访问局部变量,但是不能修改.如果想要修改局部变量,需要在变量前加__block
ARC
- 如果Block没有引用外部局部变量,Block就保存在内存的全局区
- 只要Block引用了外部局部变量,Block就保存在堆内存中
- Block最好不要使用copy,应该使用strong
6、解决循环引用
Block循环引用原因:Block会对里面所有外部局部变量(强指针变量)进行一次强引用,从而导致循环引用。
//需要将外部的强指针变量弱化
__weak typeof(self) weakSelf = self;
_block = ^{
NSLog(@"%@",weakSelf);
};
如果Block里面有非即时操作(延时操作),由于Block内部是弱指针指向,当{}结束之后就被销毁,从而导致延时操作没办法执行,需要在Block内部将弱化的指针再变为强指针
__weak typeof(self) weakSelf = self;
_block = ^{
NSLog(@"%@",weakSelf);
//将已经弱化的self指针,转换为强指针以便进行延时操作,否则就执行不到延时操作
__strong typeof(weakSelf) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf);
});
};
Tips:以下4种情况打印结果分别是多少?
情况1:
- (void)viewDidLoad
{
[super viewDidLoad];
int a = 3;
void (^ block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
————————————————————————————————————
情况2:
- (void)viewDidLoad
{
[super viewDidLoad];
static int a = 3; ***区别点:静态变量***
void (^ block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
————————————————————————————————————
情况3:
- (void)viewDidLoad
{
[super viewDidLoad];
__block int a = 3; ***区别点:被__block修饰***
void (^ block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
————————————————————————————————————
情况4:
__block int a = 3; ***区别点:全局变量***
- (void)viewDidLoad
{
[super viewDidLoad];
void (^ block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();
}
总结:
- 如果是局部变量(
情况1
),Block是值传递,打印结果是3; - 如果是静态变量(
情况2
),全局变量(情况4
),被__block修饰(情况3
),Block是指针传递(地址传递),打印结果是5;