概述
- 闭包 = 一个函数「或指向函数的指针」 + 该函数执行的外部的上下文变量「自由变量」
- Block 是 Objective-C 对于闭包的实现
Block的特性
- 可以嵌套定义,定义Block方法和定义函数的方法相似
- Block可以定在方法内部或外部
- 只有调用Block的时候,才会执行{}内的代码
- 本质是对象,使代码高聚合
Block的定义和使用
- 无参数,无返回值
void(^Myblock1)(void) = ^{
NSLog(@"无参数,无返回值");
};
Myblock1();
- 有参数,无返回值
void(^Myblock2)(int a) = ^(int a){
NSLog(@"%d是我传入的参数,无返回值",a);
};
Myblock2(100);
- 有参数,有返回值
int(^Myblock3)(int,int) = ^(int a,int b) {
NSLog(@"%d 和 %d 是我传入的参数,有返回值",a,b);
return a + b;
};
Myblock3(11,12);
- 无参数,有返回值
int(^Myblock4)(void) = ^ {
NSLog(@"无参数,有返回值");
return 233;
};
Myblock4();
- 开发中用 typedef 定义 block
typedef int (^SnowBlock) (int,int);
这时 SnowBlock 就成为了一种Block类型
定义属性可以这样
@property (nonatomic, copy) SnowBlock myBlock;
使用时
self.myBlock = ^int(int a, int b) {
//TODO
return a + b;
};
Block与外界变量
接活自动变量(局部变量)值
-
默认情况
对于 block 外的变量引用,block默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量,不使用则不截获,因为截获的自动变量会存储于block的结构体内,会导致block体积变大。特别要注意的是默认情况下block只能方位不能修改局部变量的值。
int age = 27;
void(^AgeBlock)(void) = ^{
NSLog(@"age = %d",age);
};
age = 18
AgeBlock();
输出结果
-
__block 修饰的外部变量
对于用 __block 修饰的外部变量引用,block是复制其引用地址来实现访问的。block可以修改 __block 修饰的外部变量的值。
__block int age2 = 27;
void(^AgeBlock2)(void) = ^{
NSLog(@"age2 = %d",age2);
};
age2 = 18;
AgeBlock2();
输出结果
Block的copy操作
-
Block的存储域及copy操作
先来看看一个由C/C++/OBJC编译的程序占用内存分部的结构
其实,block有3种类型
1.全局块 —— NSConcreteGlobalBlock
2.栈块 —— NSConcreteStackBlock
3.堆块 —— NSConcreteMallocBlock
这三种block各自的存储域如下
全局块存在于全局内存中,相当于单例
栈块存在于栈内存中,超出其作用于则马上被销毁
堆块存在于堆内存中,是一个带引用计数的对象,需要自行管理其内存
简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块
遇到一个Block,我们怎么确定这个Block的存储位置 ?
(1)Block不访问外界变量(包括栈中和堆中的变量)
Block既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。
(2)Block访问外界变量
MRC环境下:访问外界变量的Block默认存在栈中。
ARC环境下:访问外界变量的Block默认存在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。
ARC下,访问外界变量的Block为什么要自动从栈区拷贝到堆区?
栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,如同一般的自动变量。当然,Block中的__block变量也同时废弃。
为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。在开启ARC时,大多数情况下编译器会恰当地进行判断是否需要将Block从栈复制到堆,如果有,自动生成将Block从栈复制到堆的代码。
Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。
在非ARC情况下需要开发者调用copy方法手动复制,由于开发中几乎都是ARC模式,所以手动复制内容不再过多研究。
将Block从栈上复制到堆上相当消耗CPU,所以当Block设置在栈上也能够使用时,就不要复制了,因为此时的复制是在浪费CPU资源。
Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法如下
根据表得知,Block在堆中copy会造成引用计数增加,这与其他Objectice-C对象是一样的。虽然Block在栈中也是以对象的身份存在,但是栈块没有引用计数,因为不需要,栈区的内存编译器自动分配释放。
不管Block存储域在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。
在ARC有效是,多次调用copy方法完全没有问题
- __block 变量 与 __forwarding
在copy操作之后,既然__block变量也被copy到堆上去了,那么访问该变量是访问栈上的还是堆上的呢?
__forwarding
通过__forwarding,无论是在block中还是block外访问的__block变量,也不管该变量在栈上或堆上,都能顺利地访问同一个__block变量
防止 Block 循环引用
Block 循环引用的情况
某个类将block作为自己的属性变量,然后再block的方法体里又使用了该类本身
self.myBlock = ^int(int a, int b) {
[self dosomething];
return a + b;
};
解决办法
- ARC下:使用
__weak
__weak typeof(self) weakSelf = self;
self.myBlock = ^int(int a, int b) {
[weakSelf dosomething];
return a + b;
};
- MRC下:使用
__block
__block typeof(self) blockSelf = self;
self.myBlock = ^int(int a, int b) {
[blockSelf dosomething];
return a + b;
};
值得注意的是,在ARC下,使用 __block 也有可能带来循环引用。
Block 使用示例
- Block 作为变量
int (^sum) (int,int); //定义一个 Block 变量 sum
//给 Block 变量赋值
//一般 返回值省略
sum = ^int (int a,int b) {
return a + b;
};
int n = sum(10,12);// 调用
- Block 作为属性
// 给 Calculate 类型 sum2变量 赋值
typedef int (^Calculate)(int,int); //Calculate 就是类型名
Calculate sum2 = ^(int a,int b) {
return a + b;
};
int a = sum2(10,20);//调用 sum2 变量
typedef int (^SnowBlock) (int,int);
//作为对象的属性声明,copy后block会转移到堆中和对象一起
@property (nonatomic, copy) SnowBlock myBlock; //使用 typedef
@property (nonatomic, copy) int (^sumNumber)(int,int); //不使用 typedef
self.sumNumber = ^int(int a, int b) {
return a + b;
};
- 作为OC中的方法参数
无参数传递的 block
- (CGFloat)testTimeConsume:(void(^)(void))middleBlock {
// 执行前记录下当前的时间
CFTimeInterval startTime = CACurrentMediaTime();
middleBlock();
// 执行后记录下当前时间
CFTimeInterval endTime = CACurrentMediaTime();
return endTime - startTime;
}
调用
[self testTimeConsume:^{
// 放入 block 中的代码
}];
有参数传递的 block
- (CGFloat)testTimeConsume2:(void(^)(NSString *name))middleBlock {
// 执行前记录下当前的时间
CFTimeInterval startTime = CACurrentMediaTime();
NSString *name = @"有参数";
middleBlock(name);
// 执行后记录下当前时间
CFTimeInterval endTime = CACurrentMediaTime();
return endTime - startTime;
}
调用
[self testTimeConsume2:^(NSString *name) {
// 放入 block 中的代码 可以使用参数 name
// 参数 name 是实现代码中传入的 在调用时只能使用 不能传值
}];
- Block回调
Block回调是关于Block最常用的内容,比如网络下载,我们可以用Block实现下载成功与失败的反馈。开发者在block没发布前,实现回调基本都是通过代理delegate进行的,比如负责网络请求的原生类NSURLConnetion类,通过多个协议方法实现请求中的事件处理。而在最新的环境下,使用的NSURLSession已经采用block的方式处理任务请求了。各种第三方网络请求框架也都在使用block进行回调处理。这种转变很大一部分原因在于block使用简单,逻辑清晰,灵活。
比如有A、B两个界面。A界面中有个buttonA和一个label,B界面有一个UITextField和一个buttonB。A界面点击buttonA跳转到B界面,B界面的UITextField中输入内容后点击buttonB跳回到A界面,并将B界面中UITextField中的值传到A界面并显示在A界面的label上。
核心代码
A界面中 .m button点击触发函数
- (void)gotoB {
BlockExampleB *vcB = [BlockExampleB new];
[self.navigationController pushViewController:vcB animated:YES];
__weak typeof(self) weakSelf = self;
[vcB setCVBlock:^(NSString * string) {
weakSelf.textLabel.text = string;
}];
}
B界面中 .h
typedef void(^getVCBstringBlock) (NSString *string);
@property (nonatomic, copy) getVCBstringBlock CVBlock;
.m 中
- (void)back {
[self.navigationController popViewControllerAnimated:YES];
self.CVBlock(self.textField.text);
}
网友评论