一、Block
1.Block的基本概念
block是一种特殊的数据类型,可以保存一段代码在合适的时候调用。
功能相当于函数和方法。
区别: 1. 函数不能嵌套定义,block可以;
2. 函数作为形参或返回值必须是以指针的形式,block作为数据类型可以直接传。因此block比函数更加强大
2.Block的格式
三部分:block的声明、定义和调用;
- 声明: 返回值类型 (^block名称) (形参列表);
int (^Myblock) (int);
- 定义:block名称 = ^(形参列表){ … };
MyBlock = ^(int num1){...};
- 调用:block名称( );
MyBlock( );
注:定义的同时初始化
int (^sumBlock)(int,int) = ^(int num1,int num2){ return num1+num2};
3.Block的应用场景
typedef定义Block
typedef int(^calculate)(int,int); // typedef给Blcok起别名,别名即为Block名称。也是Block 的类型。
calculate sum = ^(int num1,int num2){ return num1+num2};
NSLog(@"sum = %d",sum(10,20));
应用场景:动画、多线程、网络请求回调等。
block作为数据类型,可以作为形参、返回值;
当代码复用时如果前后相同,中间不同时,可以使用block作为参数来简化代码。
4.Block的注意事项
- Block 可以访问外部变量的值;
int a = 10;
void (^MyBlock)() = ^{
NSLog(@"a = %d",a); //a = 10;
};
MyBlock();
- Block内部可以定义和外部同名的变量,访问的是block内部的变量;
int a = 10;
void (^MyBlock)() = ^{
int a=20;
NSLog(@"a = %d",a); //a = 20;
};
MyBlock();
- 默认情况下,Block不可以修改外部变量的值
因为Block内部使用外部定义的变量时,如果没有使用__block修饰,block内部的变量是readonly,因此是可读不可写。
int a = 10;
void (^MyBlock)() = ^{
a=20;
NSLog(@"a = %d",a); // 报错:variable isnot assignable(missing __block type specifier)
};
MyBlock();
- 为什么基本数据在block内部不能修改外部变量的值?加了__block后可以修改外部变量的值?
__block int a = 10;
void (^MyBlock)() = ^{
a=20
NSLog(@"a = %d",a); // a=20
};
MyBlock();
注:基本数据类型是值传递无法修改,而__block后变量是指针传递可以修改。
总结:
1)当是基本数据类型或对象类型,值传递。Block内部不可以修改外部变量的值;
2)当使用静态变量static、全局变量、__Block修饰的对象,指针传递。可修改外部变量的值。
3)何时使用__Block?
答:当外部变量是基本数据类型或对象,而Block内部想要修改或赋值外部变量时,可使用。但注意是修改或赋值时,使用不算。
=》需要,此处为赋值。
=》不需要,不是赋值不是修改;
二.Block的内存管理
Block在内存中分为全局block、堆Block、栈Block;
1. Block存储位置
当Block未使用外部变量时,Block是放在全局区;
当Block访问的外部变量是由static、全局变量修饰,Block仍然放在全局区;
当Block在MRC下,默认是放在栈中;
当Block在ARC下,默认放在堆中。
2. Block在MRC下
当Block在MRC下,Block默认存储在栈中。当对Block使用 copy
操作时,Block会转移到堆中。
如果Block在栈中,block访问外部对象, 不会对对象进行retain 操作;
如果Block在堆中,Block访问外部对象, 会对对象进行retain操作;
问1:Block在MRC下使用copy进行操作后,会造成内存泄漏吗?
答:会,因为Block使用copy后,将block从栈转移到堆中。当访问外部对象,会对对象进行retain,如果不进行release就会造成内存泄漏。
注:因此如果访问外部对象:一定使用__block修饰对象,这样即使block在堆中也不会对对象进行retain。
问2:Block作为属性使用什么进行修饰?为什么?
答:使用copy,因为在MRC下只有copy才能将Block从栈转移到堆中,避免代码块过掉对象被释放。而ARC中局部变量默认是强指针放在堆中,所以还是使用copy进行修饰。
3. Block在ARC下
当Block在ARC下,没有访问外部变量,Block默认放在全局区。
访问了外部变量,Block默认放在堆中。
问:ARC下使用copy还是strong? 可以使用weak吗?
答:不可以使用weak,因为ARC下局部变量默认强指针引用。如果改成弱指针则过了作用域,再去访问会造成坏访问的问题;
可以使用copy或strong,三方框架为了沿袭传统使用copy,当然也可以使用strong修饰;
四.Block在实际开发中的常用方式
1. Block用于模型,保存一段代码
在定义模型时,为了保证模型在创建时具备某种功能,可以使用block作为参数来实现。 比较常见:AFN网络请求…
// carItem.h
@property(noatomic,strong)void(^carBlcok)(); // 首先要添加一个block属性
+(instancetype)itemWithName:(NSString *)name carBlock:(void(^)())block; //定义一个快速方法,block做参数
// carItem.m
+(instancetype)itemWithName:(NSString *)name carBlock:(void(^)())block
{
CarItem *item=[[self alloc]init];
item.name = name;
item.carBlock = block;
return item;
}
// carTableViewController.m
-(void)viewDidLoad{
[super viewDidLoad];
CellItem *item = [CellItem itemWithName:@"BWM" carBlock:^{
NSLog(@"name = %@", item.name); // 添加功能
}]
}
2. Block作为参数进行传递
如:定义一个计算类,block作为参数传递;
// caculatorManager.h
@property(noatomic,assign)NSInteger result; // 定义一个Block调用时的传参属性
-(void)caculator:(NSInteger(^)(NSInteger))caculatorBlock //,block做参数
// caculatorManager.m
-(void)caculator:(NSInteger(^)(NSInteger))caculatorBlock{
if(caculatorBlock){
caculatorBlock(_result); //何时运算由内部决定:caculator本类
}
}
// ViewController.m
-(void)ViewDidLoad{
caculatorManager *mgr =[[caculatorManager alloc]init];
[mgr caculator:^NSInteger(NSInteger result){ //如何计算由外部决定:除caculator以外的调用类
result ++;
return result;
}];
}
重点:因此Block作为参数最重要的是:如何计算由外部决定,何时计算由内部决定; 和普通的调用正好相反。