OC中称Block swift中称闭包 其实是同一种东西 block是OC中对闭包的实现.
什么是block或者闭包呢?
其实就是匿名函数,对比函数和函数指针:
函数:具有特定功能的代码块
函数指针:指向函数的指针
闭包:除具备“函数”和“函数指针”的所有功能外,还包括声明它的上下文(如作用域内的自由变量等)
闭包的用途:
通常来说,block都是一些简短代码片段的封装,适用于工作单元,通常用来做并发任务,遍历,以及回调.
Block的声明:
Block的定义和函数的声明差不多,就是把函数名改为(^blockName)即可.以下为Block的声明代码:
有返回值的:
int (^sumBlock)(int, int);
无返回值的:
void (^myBlock)(int, int);
block的声明需要使用copy声明
@property (nonatomic, copy) ...
闭包在swift中的声明
有返回值的:
var successCallBack:(([String: AnyObject?]) -> (int))?
无返回值的:
var successCallBack:(([String: AnyObject?]) -> (void))?
Block的使用:
- 声明 void (^blockName)();
- 定义 blockName = ^(){NSLog(@"执行block");};
- 执行 blockName();
iOS中有三种类型的block
1.NSGlobalBlock 全局静态
void (^blkGlobal)(void);
blkGlobal = ^(
printf("NSGlobalBlock1");
);
NSLog(@"NSGlobalBlock1: %@", blkGlobal);
NSLog(@"NSGlobalBlock2:%@", ^{
printf("NSGlobalBlock2");
});
2.NSConcreteStackBlock 栈block
int i = 0;
NSLog(@"NSConcreteStackBlock1:%@", ^{
printf("NSConcreteStackBlock2: %d", i);
});
3.NSConcreteMallocBlock 堆(Heap)block
void (^blkStack)(void);
//等号是赋值运算,相当于block,进行copy,即相当于调用block的copy方法
blkStack = ^{
printf("NSConcreteMallocBlock1: %d", i);
};
NSLog(@"NSConcreteMallocBlock1: %@", [ ^{
printf("NSConcreteMallocBlock2: %d", i);
} copy]);
block的使用注意事项
1.在block内直接调用类的实例变量会使self(类的实例)引用计数加1,这样可能会引起循环引用问题(可以用__weak或者local_var处理)
2.使用null的block程序会crash,使用前判断一下:
if (blockVar){
// doing something
};
3.在多线程环境下(block中的weakSelf有可能被析构的情况下),需要先将self转为strong指针,避免在运行到某个关键步骤时self对象被析构.
__block变量和其他变量在block内的读写情况
类型 | 基本数据类型在block内的读写 | block对OBJC对象类型的深浅拷贝 |
---|---|---|
局部自动变量 | 只读 | mutable copy |
全局变量/extern | 读写 | deep copy |
static变量 | 读写 | deep copy |
__block变量 | 读写 | deep copy |
Block的循环引用
原因:
一个对象A有Block类型的属性,从而持有这个Block.如果Block的代码块中使用到这个对象A或者仅仅是用到了A对象的属性,会使Block也持有A对象,导致两者互相持有,不能在作用域结束后正常释放.
解决原理:
对象A照常持有Block,但是Block不能强引用持有对象A,以打破循环
解决方法:
方法一:
对Block内要使用的对象A使用__weak进行修饰,Block对对象A弱引用打破循环
1.__weak className
__block ViewController *weakSelf = self;
2.__weak typeof(self)
__weak typeof(self) weakSelf = self;
3.Reaction Cocoa 中的@weakify 和 @strongify:
@weakify(self);
self.blk = ^{
@strongify(self);
NSLog(@"In Block: %@", self);
};
方法二:
对Block内要使用的对象A使用__block进行修饰,并在代码块内使用完__block变量后将其设为nil,并且该block必须至少执行一次
__block XXController *blkSelf = self;
self.blk = ^{
NSLog(@"In Block: %@", self);
blkSelf = nil;
};
self.blk(); //该block必须执行一次,否则还是内存泄露
此时: XXController对象持有Block对象blk,blk对象持有__block变量 blkSelf(类型为编译器创建的结构体),__block变量blkSelf在执行不blk()之后被置为nil(__block变量结构体的__forwarding指针指向了nil),不再持有XXController对象,打破循环.
方法二使用__block打破循环的方法优点是:
1.可通过__block变量动态控制有XXController对象的时间,运行时决定是否将nil或者其他变量赋值给__block变量.
2.不能是有__weak的系统中,使用__unsafe __unretained来替代__weak打破循环,可能有野指针问题,使用__block可避免该问题.
缺点:
1.必须手动保证__block变量最后设置为nil
2.block必须执行一次,否则__block不为nil循环引用仍存在
因此,建议避免使用第二种方法,直接使用__weak打破Block循环引用会更好.
方法三:
将在Block内要使用到的对象(一般为self对象),以Block参数的形式传入Block就不会捕获该对象,而将该其作为参数使用,其生命周期系统的栈自动管理,不造成内存泄露.
即原来使用__weak的写法:
__weak typeof (self) = self;
self.blk = ^{
__strong typeof(self) strongSelf = weakSelf;
NSLog(@"use property: %@", strongSelf);
};
self.blk();
改为Block传参写法后:
self.blk = ^ (UIViewController *vc){
NSLog(@"use property: %@", vc.name);
};
self.blk(self);
优点:
1.简化了两行代码,更优雅
2.更明确的API设计,告诉API使用者,该方法的Block直接使用传进来的参数对象,不会造成循环引用,不用调用者再使用weak避免循环
截取自动变量值
Block表达式可截获所使用的自动变量的值.
截获:保存自动变量的瞬间值.
因为是“瞬间值”,所有声明Block之后,即使在Block外修改自动变量的值,也不会对Block内截获的自动变量值产生影响.
例如:
int i = 10;
void (^blk)(void) = ^{
NSLog(@"In Block, i = %d", i);
};
i = 20 //block外修改变量i,不影响block内的变量
blk(); //执行打印 i = 10
NSLog(@"i = %d", i); //i = 20
__block 说明符号
自动变量截获的值为Block声明时刻的瞬间值,保存后就不能改写该值.如需对自动变量进行重新赋值,需要在变量声明前附加__block说明符,这时该变量称为__block变量.
例如:
__blcok int i = 10; //i 为__block变量,可在block中重新赋值
void (^blk)(void) = ^{
NSLog(@"In Block, i = %d", i);
};
i = 20;
blk(); // 打印 i= 20
NSLog(@"i = %d", i); //i = 20
自动变量值为一个对象的情况
当自动变量为一个类的对象,且没有使用__block修饰时,虽然不可以在Block内对该变量进行重新赋值,但可以修改该对象的属性.
如果该对象是一个Mutable的对象,例如NSMutableArray,则还可以在Block内对NSMutableArray进行元素的增删:
NSMutableArray *array =[ [NSMutableArray alloc] initWithObjects: @"1", @"2", nil];
NSLog(@"Array count : %d", array.count); //array count: 2
void (^blk)(void) = ^{
[array removeObjectAtIndex: 0];
// array = [NSMutableArray new]; 没有__block 修饰,编译失败
};
blk();
NSLog(@"Array count : %d", array.count); // array count : 1