版本
Xcode 9.1
block简介
block是一个OC对象,于iOS4开始引入。其本身封装了一段代码,可被当作变量、当作参数或作为返回值。block常用于GCD、动画、排序及各类回调传值中。
注:图片来自这里
示例1:
// 创建一个block
int(^myBlock)(int) = ^(int num) {
return num * 3;
};
// 调用block
NSLog(@"%d",myBlock(3)); // 结果输出9
示例2:
如果需要重复声明多个相同参数和返回值的block,我们可以用typedef来定义block类型。
// 声明一个block
typedef int(^myBlock)(int);
// 实例化第一个block
myBlock block1 = ^(int num) {
return num * 3;
};
// 实例化第二个block
myBlock block2 = ^(int num) {
return num * 4;
};
// 调用block
NSLog(@"%d, %d",block1(3),block2(3)); // 结果为9, 12
block分类
按照存储区域可分为三类:
- NSGlobalBlock
- NSStackBlock
- NSMallocBlock
1. NSGlobalBlock
全局block——没有用到外界变量,或者只用到全局变量、静态(static)变量。生命周期从创建到应用程序结束。
示例:
/* 情况1:不使用外部变量 */
// 声明一个没有用到外部变量的block
void (^globalBlock1)(void) = ^{
};
// 调用block
globalBlock1();
// 查看block类型
NSLog(@"globalBlock1:%@",globalBlock1);
/* 情况2:使用全局变量和静态变量 */
// 新建静态变量
static NSString *staticStr = @"我是静态变量";
// 声明一个使用全局变量和静态变量的block
void (^globalBlock2)(void) = ^{
NSLog(@"%@, %@",globalStr,staticStr);
};
// 调用block
globalBlock2();
// 查看block类型
NSLog(@"globalBlock2:%@",globalBlock2);
结果如下:
2. NSStackBlock
栈block——用到局部变量、成员属性/变量,且没有强指针引用。生命周期由系统管理,超出作用域(函数返回时)马上被销毁。
/* 情况1:用到局部变量 */
// 新建一个局部变量
NSString *localStr = @"我是局部变量";
// 查看block类型, 直接在NSLog里面调用使用了局部变量的block,这样就没有强指针引用了
NSLog(@"stackBlock1:%@",^{NSLog(@"%@",localStr);});
/* 情况2:用到成员属性/变量 */
// 给成员变量、成员属性赋值
_variateStr = @"我是成员变量";
self.propertyStr = @"我是成员属性";
// 查看block类型, 直接在NSLog里面调用使用了成员属性/变量的block,这样就没有强指针引用了
NSLog(@"stackBlock2:%@",^{NSLog(@"%@, %@",_variateStr,self.propertyStr);});
结果:
3. NSMallocBlock
堆block——有强指针引用或使用有copy修饰的成员属性/变量。没有强指针引用即销毁,生命周期需手动管理。
/* 情况1:用到强指针引用 */
// 新建一个局部变量
NSString *localStr = @"我是局部变量";
// 声明一个使用强指针引用的block
void (^mallocBlock1)(void) = ^{
NSLog(@"%@",localStr);
};
// 调用block
mallocBlock1();
// 查看block类型
NSLog(@"mallocBlock1:%@",mallocBlock1);
/* 情况2:用到有copy修饰的成员属性/变量 */
// 给成员变量、成员属性赋值
_variateStr = @"我是成员变量";
self.propertyStr = @"我是成员属性";
// 声明一个使用有copy修饰的成员属性的block
void (^mallocBlock2)(void) = ^{
NSLog(@"%@, %@",_variateStr, self.propertyStr);
};
// 调用block
mallocBlock2();
// 查看block类型
NSLog(@"mallocBlock2:%@",mallocBlock2);
结果:
block访问外部变量
1. 访问局部变量
在block中可以访问局部变量,但不能直接修改局部变量。
示例1:
// 声明局部变量local
int local = 100;
// 声明block
void(^myBlock)(void) = ^{
// local++; // 修改local值会报错
NSLog(@"local1 = %d", local);
};
// 在调用block之前改变local的值
local = 101;
// 调用block
myBlock();
// 查看local改变后的值
NSLog(@"local2 = %d", local);
打印结果:
原理分析:
在block定义时是将局部变量的值传给block变量所指向的结构体,因此在调用block之前对局部变量进行修改并不会影响block内部的值。同时传进来的内部值也是不可修改的。
但是,我们有时候又想在block内部修改block外的局部变量,应该怎么办呢?
使用__block修饰的局部变量,在block中可以直接修改
示例2:
// 声明局部变量local (用__block修饰)
__block int local = 100;
// 声明block
void(^myBlock)(void) = ^{
NSLog(@"修改前local1 = %d", local);
local++; // 不会报错
NSLog(@"修改后local1 = %d", local);
};
// 在调用block前修改local值
local = 200;
// 调用block
myBlock();
// 查看local改变后的值
NSLog(@"local2 = %d", local);
打印结果:
原理分析:
在局部变量前使用__block修饰,block定义时是将局部变量的指针传给block变量所指向的结构体,因此在调用block之前对局部变量进行修改会影响block内部的值。同时内部的值也是可以修改的。
2. 访问静态变量
在block中可以访问静态变量,也可以直接修改静态变量。
示例:
// 声明静态变量staticInt
static int staticInt = 100;
// 声明block
void(^myBlock)(void) = ^{
NSLog(@"修改前staticInt1 = %d", staticInt);
staticInt++; // 不会报错
NSLog(@"修改后staticInt1 = %d", staticInt);
};
// 在调用block前修改staticInt值
staticInt = 200;
// 调用block
myBlock();
// 查看staticInt改变后的值
NSLog(@"staticInt2 = %d", staticInt);
打印结果:
原理分析:
block定义时是将静态变量的指针传给Block变量所指向的结构体,因此在调用block之前对静态变量进行修改会影响block内部的值。同时内部的值也是可以修改的。
3. 访问全局变量
在block中可以访问全局变量,也可以直接修改全局变量。
示例:
// 先在@implementation前定义一个全局变量globalInt
// 声明block
void(^myBlock)(void) = ^{
NSLog(@"修改前globalInt1 = %d", globalInt);
globalInt++; // 不会报错
NSLog(@"修改后globalInt1 = %d", globalInt);
};
// 在调用block前修改globalInt值
globalInt = 200;
// 调用block
myBlock();
// 查看globalInt改变后的值
NSLog(@"globalInt2 = %d", globalInt);
打印结果:
原理分析:
全局变量所占用的内存只有一份,供所有函数(方法)共同调用,在block定义时并未将全局变量的值或者指针传给block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响block内部的值。同时内部的值也是可以修改的。
block在ARC下的内存管理
如果对象内部有一个block属性,而在block内部又访问了该对象,那么会造成循环引用
错误示例:
// 声明block成员属性
@property (nonatomic, copy) void(^myBlock)(void);
// 定义block
self.myBlock = ^{
NSLog(@"%@", self); // 此句造成循环引用,编译报错
};
// 执行block
self.myBlock();
解决办法:使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作。
正确示例:
// 声明block成员属性
@property (nonatomic, copy) void(^myBlock)(void);
// 声明一个弱引用指针对象
__weak typeof(self) weakSelf = self;
// 定义block
self.myBlock = ^{
NSLog(@"%@", weakSelf); // 使用弱引用指针对象
};
// 执行block
self.myBlock();
如果担心在调用Block之前引用的对象已经被释放,那么我们需要在Block内部再定义一个强指针来指向该对象
官方示例:
// 声明block成员属性
@property (nonatomic, copy) void(^myBlock)(void);
// 声明一个弱引用指针对象
__weak typeof(self) weakSelf = self;
// 定义block
self.myBlock = ^{
typeof(self) strongSelf = weakSelf; // 声明一个强引用指针对象
NSLog(@"%@", strongSelf); // 使用强引用指针对象
};
// 执行block
self.myBlock();
block的传值应用
先看看效果:
从界面1加载界面2,然后在界面2返回时回传textField里的text。方法步骤:
- 界面1(接收方):
1、定义(实现)一个block并传给界面2(的block属性)
2、在block中处理传递过来的值 - 界面2(传值方):
1、声明一个block属性,其具体实现由接收方来完成
2、在需要传值的地方调用block
界面1 .m文件:
// 跳转到界面2
- (IBAction)btnShowViewController2:(UIButton *)sender {
// 从storyboard实例化界面2
ViewController2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewController2"];
// 定义(实现)一个block并传给界面2(的block属性)
vc2.textBlock = ^(NSString *str) {
self.label.text = str; // 处理传回来的值
};
// 加载界面2
[self presentViewController:vc2 animated:YES completion:nil];
}
界面2 .h文件
@interface ViewController2 : UIViewController
// 声明block属性
@property (nonatomic, copy) void(^textBlock)(NSString *);
@end
界面2 .m文件:
// 返回界面1
- (IBAction)btnBack:(UIButton *)sender {
// 先判断block是否为空,为空则报错
if (self.textBlock) {
// 返回textField里的text
self.textBlock(self.textField.text);
}
// 退出界面2
[self dismissViewControllerAnimated:YES completion:nil];
}