Block基础
文章目录
- Block基础
-
- [一、先搞懂:Block 到底是什么?](#一、先搞懂:Block 到底是什么?)
- [二、Block 的标准语法](#二、Block 的标准语法)
- [三、Block 最核心能力:捕获外部变量](#三、Block 最核心能力:捕获外部变量)
-
- 捕获的变量类型及规则(分3类,清晰区分)
-
- 类别1:局部变量(最常用,重点掌握)
- 类别2:全局变量(无需捕获,直接访问)
- [类别3:静态变量(static,可直接修改,无需 __block)](#类别3:静态变量(static,可直接修改,无需 __block))
- [四、Block 常见使用场景(开发必用)](#四、Block 常见使用场景(开发必用))
-
- [场景 1:代码异步执行(网络请求、延时操作)](#场景 1:代码异步执行(网络请求、延时操作))
- [场景 2:回调传值(A 页面 → B 页面,B 做完通知 A)](#场景 2:回调传值(A 页面 → B 页面,B 做完通知 A))
- [五、Block 的内存问题(循环引用)](#五、Block 的内存问题(循环引用))
-
- [1. 什么是循环引用?](#1. 什么是循环引用?)
- [2. 解决方案:weakSelf 弱引用](#2. 解决方案:weakSelf 弱引用)
- [3.防止 Block 执行中 self 被销毁(strongSelf)](#3.防止 Block 执行中 self 被销毁(strongSelf))
- [六、Block 类型](#六、Block 类型)
- 七、总结
一、先搞懂:Block 到底是什么?
Block 就是一段 "可以打包保存、随时调用" 的代码块。
- 它像一个匿名函数(没有名字的函数)
- 它能捕获外部变量(这是它最强大的地方)
- 它可以当作参数传递 、当作变量存储
Block 和普通函数的区别(对比记忆)
| 对比维度 | 普通函数 | Block |
|---|---|---|
| 是否有名字 | 有(如 - (void)sayHello;) | 无(匿名) |
| 能否捕获外部局部变量 | 不能(只能通过参数传递) | 能(默认只读,加 __block 可修改) |
| 能否当作变量存储 | 不能 | 能(存在栈/堆/全局区) |
| 能否当作参数传递 | 不能(只能传递函数指针) | 能(直接传递 Block 变量) |
二、Block 的标准语法
完整格式
返回值类型 (^block变量名)(参数列表) = ^返回值类型(参数列表) {
// 代码块
};
- 无返回值、无参数
objc
void (^myBlock)(void) = ^{
NSLog(@"我是一个最简单的Block");
}
- 有参数、无返回值
objc
// 定义:接收一个字符串参数
void (^sayHello)(NSString *) = ^(NSString *name){
NSLog(@"你好,%@", name);
};
// 调用
sayHello(@"小明");
- 有参数、有返回值
objc
// 定义:计算两个数的和
int (^sumBlock)(int, int) = ^(int a, int b){
return a + b;
};
// 调用
int result = sumBlock(10, 20);
NSLog(@"结果:%d", result); // 输出 30
三、Block 最核心能力:捕获外部变量
捕获的变量类型及规则(分3类,清晰区分)
类别1:局部变量(最常用,重点掌握)
局部变量:定义在函数/方法内部的变量(比如在 main 函数里定义的 int age = 18),Block 对其捕获规则如下:
-
默认:只能"读取",不能"修改"(Block 会捕获变量的"副本",而非变量本身)。
-
想修改:必须在变量前面加 __block 关键字(双下划线+block),此时 Block 捕获的是变量的"地址",而非副本,就能正常修改。
objc
// 示例1:未加 __block,只能读取,不能修改
int age = 18; // 局部变量
void (^readBlock)(void) = ^{
NSLog(@"年龄(读取):%d", age); //正常读取,输出 18
// age = 20; //报错:Variable is not assignable (missing __block type specifier)
};
readBlock();
// 示例2:加 __block,可修改
__block int height = 170; // 加 __block 关键字
void (^changeBlock)(void) = ^{
height = 180; //正常修改
NSLog(@"身高(修改后):%d", height); // 输出 180
};
changeBlock();
类别2:全局变量(无需捕获,直接访问)
全局变量:定义在函数/方法外部的变量(比如在 main 函数外面定义的 int globalNum = 100),Block 无需捕获,可直接读取、修改,无需加 __block。
objc
// 全局变量(定义在 main 函数外面)
int globalNum = 100;
int main(int argc, const char * argv[]) {
@autoreleasepool {
void (^globalBlock)(void) = ^{
globalNum = 200; // 直接修改,无需 __block
NSLog(@"全局变量修改后:%d", globalNum); // 输出 200
};
globalBlock();
}
return 0;
}
类别3:静态变量(static,可直接修改,无需 __block)
静态变量:用 static 修饰的局部变量(比如 static int staticNum = 50),Block 会捕获它的"地址",因此可直接修改,无需加 __block。
objc
int main(int argc, const char * argv[]) {
@autoreleasepool {
static int staticNum = 50; // 静态局部变量
void (^staticBlock)(void) = ^{
staticNum = 150; // 直接修改,无需 __block
NSLog(@"静态变量修改后:%d", staticNum); // 输出 150
};
staticBlock();
}
return 0;
}
四、Block 常见使用场景(开发必用)
场景 1:代码异步执行(网络请求、延时操作)
objc
// 延时 2 秒执行一段代码
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2 秒后执行");
});
场景 2:回调传值(A 页面 → B 页面,B 做完通知 A)
比如:第二个页面选择完数据,通知第一个页面
objc
// B 页面.h 文件
@property (nonatomic, copy) void(^selectResult)(NSString *data);
// B 页面.m 选择完调用
self.selectResult(@"我是选择的数据");
// A 页面使用
BViewController *bVC = [[BViewController alloc] init];
bVC.selectResult = ^(NSString *data){
NSLog(@"收到 B 页面的数据:%@", data);
};
五、Block 的内存问题(循环引用)
1. 什么是循环引用?
简单说:
-
对象 A 强引用 Block
-
Block 内部强引用了对象 A
→ 两者互相抓住不放,
谁都不释放,造成内存泄漏。
2. 解决方案:weakSelf 弱引用
objc
// 第一步:定义弱引用 self
__weak typeof(self) weakSelf = self;
// 第二步:Block 内部只用 weakSelf,不用 self
self.myBlock = ^{
// 安全,不会循环引用
NSLog(@"%@", weakSelf.name);
};
3.防止 Block 执行中 self 被销毁(strongSelf)
block执行流程:
-
页面 / 控制器
self已经提前退出、销毁(比如用户退出当前页面) -
但异步 Block 还没来得及执行,还在排队
-
等轮到 Block 执行时:原来的 self 早就被系统回收销毁了
objc
// 1. 外层弱引用:打破循环引用,允许页面正常销毁
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
//2.内部再强引用一次,保证执行代码时 self 还在
__strong typeof(weakSelf) strongSelf = weakSelf;
// 判空:防止页面早就销毁、weakSelf 已经是 nil
if (strongSelf) {
strongSelf.name = @"测试";
}
};
六、Block 类型
Block 存放在内存 3 个地方,开发不用手动管:
- NSGlobalBlock(全局区):没访问外部变量
- NSStackBlock(栈区):访问了变量,MRC 用
- NSMallocBlock (堆区):copy 后,ARC 下最常用
七、总结
- Block = 可保存、可调用、可传参的代码段
- 语法记住:
返回值 (^名称)(参数) = ^{} - 修改变量用
__block - 内部用 self 必须
__weak防循环引用 - 实际开发主要用于回调、异步、遍历