目录
- Block概述
- Block定义方式
- Block保存代码
- Block传值
- Block对外部变量的传递
- Block做参数
- Block做返回值(实现链式编程)
- Block内存管理
- Block循环引用
- Block其他注意点
1. Block概述
Block是C语言级别和运行时方面的一个特征。Block封装了一段代码逻辑,用{}括起,和标准C语言中的函数/函数指针很相似。
2. Block定义方式
- 声明:(返回类型)(^声明的Block名称)(参数列表);
- 实现:^(返回类型)(参数列表){代码块}
- 返回类型(^block变量名)(参数列表) = ^(形参列表) {};
void(^MyBlock)(int a,int b) = ^(int a, int b){}
返回类型:void
Block名字:MyBlock
实际参数: int a ,int b
形式参数: int a ,int b
-
Block三种定义方式以及block类型
- 没有返回值,没有参数的定义方式
//返回值类型(^block的名字)(参数类型) = ^(参数类型和参数名) {};
void(^block)() = ^(){
NSLog(@"调用了block");
};
//当然,没有参数的时候可以把括号省去
void(^block)() = ^{
NSLog(@"调用了block");
};
- 有返回值,有参数的定义方式
//返回值类型(^block的名字)(参数类型) = ^(参数类型和参数名) {};
//如果有参数,定义的时候,必须要写参数,而且必须要有参数变量名
int(^block)(int) = ^(int a){
return 1;
};
- 定义时带有返回类型的(不常用)
int(^block)() = ^int{//这里的int就是这个block的返回值类型
return 1;
};
- 系统提供了一个定义block的宏
// block快捷方式 输入:inline
returnType(^blockName)(parameterTypes) = ^(parameters) {
statements
};
- block的调用
//定义block
void(^block)() = ^{
NSLog(@"调用了block");
};
//调用block
block();
- block的类型
//block有自己的类型,就想@"string"是NSString类型一样
//格式就是 返回值(^)(参数类型)
//比如这个block的类型就是: int(^)(int)
int(^block)(int) = ^(int a){
return 1;
};
//这个block的类型就是void(^)()
void(^block)() = ^{
NSLog(@"调用了block");
};
//在ARC中把block定义成属性要用strong类型,定义方式如下:
@property (nonatomic, strong) void(^block)();//这样在类中可以拿到self.block
//当然也可以取别名:
typedef void(^BlockType)();//BlockType不是变量名,而是这种类型的block的别名
//然后就可以这样
@property (nonatomic, strong) BlockType block;
3. Block保存代码
这种方式在app的设置界面中使用到,需求大约是这样:
设置界面每一行cell会做不同的事,有的是跳转界面,有的是switch开关,有的是需要显示一下AlertView.
这样的话,我们可以把需要执行的代码包装成block,放在cell的模型里面,当点击cell的时候,拿出模型中的block来执行。
//这是cell的模型
typedef void(^optionBlock)();
@interface SettingItem : NSObject
@property(nonatomic,copy)NSString *icon;//图标
@property(nonatomic,copy)NSString *title;//文字
@property(nonatomic,assign) Class destVc;//需要跳转的界面
@property(nonatomic,copy) optionBlock option;//需要执行的代码
@end
然后我们可以在didSelectRowAtIndexPath里面这么做:
-(void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath
{
[self.tableView deselectRowAtIndexPath:indexPath animated:YES];
if (item.option != nil) {//block存在就执行
item.option();
}else if ([item isKindOfClass:[SettingArrowItem class]]) {//执行跳转界面
SettingArrowItem *newItem = (SettingArrowItem *)item;
UIViewController *vc = [[newItem.destVc alloc]init];
vc.title = item.title;
[self.navigationController pushViewController:vc animated:YES];
}
}
4. Block进行传值
需求如下:
在ViewControllerOne跳到ViewControllerTwo
ViewControllerTwo拿到数据后dismiss
在ViewControllerOne打印数据
//ViewControllerOne.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{//点击控制器View的时候调用
ModalViewController *modalVc = [[ModalViewController alloc] init];
//对Block属性进行赋值
modalVc.block = ^(NSString *value) {
NSLog(@"%@",value);
};
[self presentViewController:modalVc animated:YES completion:nil];
}
//ViewControllerTwo.h:
@interface ModalViewController : UIViewController
@property (nonatomic, strong) void(^block)(NSString *value);
@end
//ViewControllerTwo.m:
-(void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
// 调用Block 传值给ViewControllerOne
if (_block) {
_block(@"123");
}
[self dismissViewControllerAnimated:YES completion:nil];
}
5. Block对外部变量的传递
一个很简单的问题:block使用外部变量,是值传递,还是指针传递。
- 值传递
//block为值传递只有一种情况:
int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();//这里调用block打印出的是3,是值传递
*注意:因为block中使用的外面变量的值是拷贝过来的即值拷贝,所以在调用之前修改外界变量的值,不会影响到block中拷贝的值.
结论:如果block访问的变量是局部变量,那么变量是值传递.
- 指针传递
static int a = 3;
void(^block)() = ^{
NSLog(@"%d",a);
};
a = 5;
block();//这里调用block打印出的是5,是指针传递
//另外全局变量,静态变量,__block修饰的变量都是指针传递
结论:如果是全局变量或者静态变量,那么变量是指针传递。
- 经过其他测试总结:
(1)如果是局部变量,Block是值传递
(2)如果是静态变量,全局变量,__block修饰的变量,block都是指针传递
6. Block做参数
第一次见到block当做参数是在AFN框架中,AFN帮你拿到数据以后,执行你传给他的block:
//这一个个对AFN进行简单封装的方法:
+(void)requestWihtMethod:(RequestMethodType)methodType
success:(void (^)(id response))success //是一个block
failure:(void (^)(NSError* err))failure //是一个block
{
AFHTTPSessionManager *manage = [AFHTTPSessionManager manager];
[manage GET:url parameters:params progress:^(NSProgress * _Nonnull downloadProgress) {
} success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) {
if (success) {
success(responseObject);//拿到数据后执行你传入的block
}
} failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) {
failure(error);//拿到数据后执行你传入的block
}];
}
}
接下来我们自定义一个计算器,在block里面自定义计算方式,将block传入计算器来进行计算:
//CalculatorManager.h:
@interface CacultorManager : NSObject
@property (nonatomic, assign) NSInteger result;//要计算的数据
-(void)calculatorWithMethod:(NSInteger(^)(NSInteger // 计算方法parameterresult))methodBlock;
@end
//CalculatorManager.m:
-(void)cacultor:(NSInteger (^)())cacultorBlock
{
if (cacultorBlock) {//判断block是否为空
_result = cacultorBlock(_result);//执行block中的计算方法
}
}
//ViewController.m:使用计算器
-(void)viewDidLoad {
[super viewDidLoad];
// 创建计算器管理者
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr calculator:^(NSInteger result){//自定义计算方法block,作为参数传进去
result += 5;
result += 6;
result *= 2;
return result;
}];
NSLog(@"%ld",mgr.result);
7. Block做方法返回值(链式编程)
我们平时写一些工具类的方法的时候(比如计算器)
//注:result是CalculatorManager的属性,用来保存计算结果
//如果计算方法这么写
-(int)add:(int)value{
_result += value;
return result;
}
//就要这么调用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:[mgr add:5]]]]]];//返回值是数字,要继续用mar调用add来执行操作
//如果计算方法这么写:
-(CalculatorManager *)add:(int)value
{
_result += value;
return self;
}
//就要这么调用:
CalculatorManager *mgr = [[CalculatorManager alloc] init];
[[[[[mgr add:5] add:5] add:5] add:6] add:7];//返回值是计算器本身,可以继续调用add方法
现在我们要用返回值是block的方法来实现链式编程:
//计算方法这么写,返回值是 返回值为CalculatorManager的block.
-(CalculatorManager *(^)(int))add//相当于一个get方法
{
return ^(int value){
_result += value;
return self;
};
}
//就可以这么调用
CalculatorManager *mgr = [[CalculatorManager alloc] init];
mgr.add(5).add(5).add(5).add(5);
下面简单介绍一下调用原理:
mgr.add相当于get方法的调用: [mgr add];
mgr.add返回的是一个block,所以你可以给他一个参数5,于是写成这样:mgr.add(5)
然后block返回的又是CalculatorManager,所以继续调用add
如此循环下去.......
8. Block内存管理
首先,在oc中block是一个对象,只有对象才涉及到内存管理
block的内存管理在MRC和ARC中有不同的地方,接下来将分别介绍
1.MRC:
Block存放位置:
int a = 3;//这是一个局部变量
void(^block)() = ^{
NSLog(@"调用block%d",a);
};
NSLog(@"%@",block);
//打印结果:<__NSStackBlock__: 0x7fc498746000>
//此时引用了外部局部变量,block放在栈里面
static int b = 2;
-(void)viewDidLoad {
[super viewDidLoad];
void(^block)() = ^{
NSLog(@"调用block%d",b);
};
NSLog(@"%@",block);
}
//打印结果:<__NSGloBalBlock__: 0x7fc498746000>
//此时引用了全局变量,block放在全局区
定义属性时:
@property (nonatomic, copy) void(^block)();
block只能使用copy,不能使用retain,
因为使用retain,block还是在栈里面 代码块过了方block就销毁了,再次访问self.block会出现坏内存访问
使用copy是放在堆里面,代码块过了不会销毁
MRC管理Block总结:
只要Block没有引用外部局部变量,Block放在全局区
只要Block引用外部局部变量,Block放在栈里面.
2.ARC:
Block存放位置:
int a = 3;//这还是一个局部变量
void(^block)() = ^{
NSLog(@"调用block%d",a);
};
NSLog(@"%@",block);
//打印结果:<__NSMallocBlock__: 0x7fc498746000>
//此时引用了外部局部变量,block放在堆里面
static int b = 2;
-(void)viewDidLoad {
[super viewDidLoad];
void(^block)() = ^{
NSLog(@"调用block%d",b);
};
NSLog(@"%@",block);
}
//打印结果:<__NSGloBalBlock__: 0x7fc498746000>
//此时引用了全局变量,block放在全局区
定义属性时:
@property (nonatomic, strong) void(^block)();
block只能使用strong,不要使用copy
因为当使用copy的时候,set方法是调用了copy帮你深拷贝一次,没有这个必要.
就像NSString一样,他一般都是@"a"这种常量,没必要再去深拷贝一次,所以NSString常量也用strong不用copy.
ARC管理Block总结:
只要Block没有引用外部局部变量,Block放在全局区
只要Block引用外部局部变量,Block放在堆里面.
block循环引用
9. Block的循环引用问题
- 为什么会产生循环引用:
因为block会给内部的强指针对象进行一次强引用,比如常见的传入block中的self进行强引用
并且在self中,block又是strong的,self对block是强引用
所以,你强引用我,我强引用你,谁也不会被释放,就造成了循环引用
所以,为了避免循环引用,我们要在block使用self之前,进行这一步操作:
__weak typeof(self) weakSelf = self;
在block中使用weakSelf,就不会产生循环引用问题了.
使用了__weak修饰符的对象,作用等同于定义为weak的property。自然不会导致循环引用问题.
- 接下来是双层block的循环引用问题:
先来看这样一个例子:
__weak typeof(self) weakSelf = self;
block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延时2秒执行下面的代码
NSLog(@"%@",weakSelf);
});
};
block();
这样执行下去,如果在延时时间2秒还没到,控制器就dismiss或者pop(总之是销毁)了,打印出的weakSelf是null,
为什么呢?
因为只有push self或者present self的控制器对self强引用,当self dismiss了或者pop了,就没有人对self强引用了(block对self没有强引用),根据ARC的内存管理原则,当没有人对一个对象强引用的时候,该对象就会销毁.
所以,当self dismiss了或者pop了,self就销毁了,2秒后block再访问self的时候,self已经不再了.
这时,我们要做如下处理:
__weak typeof(self) weakSelf = self;
block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;//这个是局部变量 栈内的强指针 当这个block执行完毕 这个指针就会释放
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{//延时2秒执行下面的代码
NSLog(@"%@",strongSelf);
});
};
block();
加上这一句:strong typeof(weakSelf) strongSelf = weakSelf; strong 就相当于定义为strong的property
那么当self销毁的之前(pop或者dismiss之前),有两个人对self强引用(一个是push self或者present self的控制器,一个是这个block中定义的strongSelf).
当控制器销毁的时候(pop或者dismiss时),push self或者present self的控制器不在强引用self,self失去一个强引用,但是self不会销毁,因为block中定义的strongSelf还在对self强引用.
但是你会问,那这么不会造成循环引用吗?不着急,继续往下看:
当延时2秒到了,block可以访问到strongSelf
当延时block代码块过了,strongSelf就会指向nil了(因为strongSelf是局部变量,存在栈内的强指针 当这个block执行完毕,这个指针就会释放)
此时就没有人对self进行强引用了,self也会销毁,
至此,大家都销毁了..
10. Block其他注意点
block中可以定义和外界同名的变量,并且如果在block中定义了和外界同名的变量,在block中访问的是block内部的变量。可以通过打印变量的地址看出两个变量地址不同。
因为block中使用的外面变量的值是拷贝过来的即值拷贝,所以在调用之前修改外界变量的值,不会影响到block中拷贝的值
默认情况下,在block内部不能改变外面变量的值,如果想在block中修改外界变量的值,必须在外界变量前面加上
__block
。如果在block中修改了外界变量的值,会影响到外界变量的值。如果block中访问到了外界的变量,block会将外界变量拷贝一份到堆内存中。