// 最简单的 Block
void (^sayHello)(void) = ^{
NSLog(@"Hello");
};
// 带参数的 Block
void (^saySomething)(NSString *) = ^(NSString *words) {
NSLog(@"%@", words);
};
// 带返回值的 Block
int (^add)(int, int) = ^(int a, int b) {
return a + b;
};
// Block 的本结构
struct Block_layout {
void *isa; // 指向类对象,证明 Block 是对象
int flags; // 状态标志,包含 Block 的类型和引用计数信息
int reserved; // 保留字段
void (*invoke)(void *, ...); // 函数指针,指向 Block 的实现代码
struct Block_descriptor *descriptor; // 描述信息,包含 Block 的大小、复制和释放函数等
// 捕获的变量
};
结构解析:
isa
指针:表明 Block 是一个对象,指向其类型信息(_NSConcreteGlobalBlock
、_NSConcreteStackBlock
或 _NSConcreteMallocBlock
)Flags
:记录 Block 的状态信息,如是否被复制到堆上、是否包含 C++ 对象等reserved
:保留字段,用于未来扩展invoke
:指向 Block 要执行的代码descriptor
:包含 Block 的描述信息,如大小、复制和释放函数等补充说明:
copy
、release
等消息isa
指针决定了 Block 的类型和行为// 基本格式:@?<返回值类型参数1类型参数2类型...>
// 示例1:无参数无返回值的 Block
"@?<v>" // void (^)(void)
// 示例2:带基本类型参数的 Block
"@?<vi>" // void (^)(int)
// v 表示返回值类型为 void
// i 表示参数类型为 int (注意:基本类型不需要@前缀)
// 示例3:带对象参数的 Block
"@?<v@>" // void (^)(id)
// v 表示返回值类型为 void
// @ 表示参数类型为 id(对象类型)
// 示例4:带 Block 参数的 Block
"@?<v@?" // void (^)(void (^)())
// v 表示返回值类型为 void
// @? 表示参数类型为 Block
// 示例5:带返回值的 Block
"@?<i@>" // int (^)(id)
// i 表示返回值类型为 int (基本类型不需要@前缀)
// @ 表示参数类型为 id
// 示例6:带多个参数的 Block
"@?<viB@>" // void (^)(int, BOOL, id)
// v 表示返回值类型为 void
// i 表示第一个参数为 int
// B 表示第二个参数为 BOOL
// @ 表示第三个参数为 id
基本类型(int、BOOL等)不需要加@前缀
返回值类型如果是对象,需要加@
参数列表中的类型按顺序排列,不需要分隔符
对于具体类名的对象,可以使用@"ClassName"格式
@?
:表示这是一个 Block 类型< >
:包含 Block 的签名信息
<返回值类型参数1类型参数2类型...>
v
:voidi
:intc
:charB
:BOOLf
:floatd
:doubleq
:NSIntegerQ
:NSUInteger// 复杂示例
typedef NSString *(^ComplexBlock)(int, void (^)(BOOL), NSArray<NSNumber *> *);
// 对应的类型编码
"@?<@i@?@>"
// 或更精确的(如果希望包含类名):
"@?<@"NSString"i@?@"NSArray">"
逐部分解析:
部分 | 编码 | 对应类型 |
---|---|---|
返回值 | @ 或 @“NSString” | NSString * |
参数1 | i | int(基本类型,不加 @) |
参数2 | @? | void (^)(BOOL)(Block 参数) |
参数3 | @ 或 @“NSArray” | NSArray *(对象类型) |
// 定义在全局作用域
void (^globalBlock)(void) = ^{
NSLog(@"我是全局 Block");
};
特点:
- (void)example {
int num = 10;
void (^stackBlock)(void) = ^{
NSLog(@"我是栈 Block: %d", num);
};
}
特点:
- (void)example {
int num = 10;
// 方式1:手动复制到堆
void (^heapBlock1)(void) = [^{
NSLog(@"我是堆 Block: %d", num);
} copy];
// 方式2:赋值给属性自动复制到堆
self.block = ^{
NSLog(@"我是堆 Block: %d", num);
};
}
特点:
操作场景 | ARC 环境 | MRC 环境 |
---|---|---|
赋值给 strong 变量 | 自动 copy 到堆 | 需要手动调用 copy |
作为函数返回值 | 自动 copy 到堆 | 需要手动调用 copy |
传递给 GCD API | 自动 copy 到堆 | 自动 copy 到堆 |
作为属性(copy) | 自动处理 | 必须显式 copy |
作为局部变量 | 保持在栈上 | 保持在栈上 |
作为全局变量 | 保持在数据区 | 保持在数据区 |
int num = 10;
void (^block)(void) = ^{
NSLog(@"%d", num); // 只能读取,不能修改
};
编译后的结构:
// 编译器生成的 Block 结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int num; // 捕获的变量
};
// 使用示例
struct __main_block_impl_0 block = {
.impl = {...},
.Desc = {...},
.num = 10 // 值复制
};
特点:
__block int num = 10;
void (^block)(void) = ^{
num = 20; // 可以修改
NSLog(@"%d", num);
};
编译后的结构:
// __block 变量的包装结构体
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding;
int __flags;
int __size;
int num; // 原始变量
};
// Block 结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_num_0 *num; // 通过指针引用
};
特点:
__block
修饰符会将被修饰的变量包装成一个结构体NSObject *obj = [[NSObject alloc] init];
void (^block)(void) = ^{
NSLog(@"%@", obj); // 强引用
};
编译后的结构:
// Block 结构体
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
NSObject *__strong obj; // 强引用
// 编译器生成的辅助函数
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*) = __main_block_copy_0;
void (*dispose)(struct __main_block_impl_0*) = __main_block_dispose_0;
};
特点:
retain
操作增加对象的引用计数retain
对象__weak
修饰符来避免底层实现原理:
__block
变量,会生成一个包装结构体copy
操作会递归复制所有捕获的变量__block
变量通过包装结构体实现共享访问,对象类型通过引用计数管理实现内存管理。__block
变量通过共享结构体实现同步修改,对象类型通过自动引用计数管理生命周期。__block
变量有指针间接访问开销,对象类型有引用计数管理开销。@interface DownloadManager : NSObject
@property (nonatomic, copy) void (^progressBlock)(CGFloat progress);
@property (nonatomic, copy) void (^completionBlock)(void);
@end
@interface ViewController : UIViewController
@property (nonatomic, strong) DownloadManager *downloadManager;
@end
@implementation ViewController
- (void)setup {
// 错误写法:形成循环引用
self.downloadManager = [[DownloadManager alloc] init];
// 下载进度回调
self.downloadManager.progressBlock = ^(CGFloat progress) {
// ViewController 持有 downloadManager
// progressBlock 持有 ViewController
[self updateProgress:progress]; // 直接使用 self 导致循环引用
};
// 下载完成回调
self.downloadManager.completionBlock = ^{
// ViewController 持有 downloadManager
// completionBlock 持有 ViewController
[self handleDownloadComplete]; // 直接使用 self 导致循环引用
};
}
- (void)updateProgress:(CGFloat)progress {
// 更新进度条
}
- (void)handleDownloadComplete {
// 处理下载完成
}
@end
__weak
修饰符打破循环引用@implementation ViewController
- (void)setup {
self.downloadManager = [[DownloadManager alloc] init];
// 使用 weak 引用打破循环
__weak typeof(self) weakSelf = self;
// 下载进度回调
self.downloadManager.progressBlock = ^(CGFloat progress) {
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf updateProgress:progress];
};
// 下载完成回调
self.downloadManager.completionBlock = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
[strongSelf handleDownloadComplete];
};
}
@end
__block
修饰的变量在 Block 复制到堆时也会被复制在 iOS 开发中,Block 最常用的场景之一就是异步编程。通过 Block 可以优雅地处理异步操作的结果,避免回调地狱。
示例:数据加载
// 定义回调 Block 类型
typedef void (^Completion)(id result, NSError *error);
- (void)loadData:(Completion)completion {
// 在后台线程执行耗时操作
dispatch_async(dispatch_get_global_queue(0, 0), ^{
id result = [self fetchData];
NSError *error = nil;
// 回到主线程更新 UI
dispatch_async(dispatch_get_main_queue(), ^{
if (completion) {
completion(result, error);
}
});
});
}
使用说明:
typedef
定义 Block 类型,提高代码可读性if (completion)
检查 Block 是否存在Block 还可以用于实现链式调用语法,使代码更加流畅和易读。这种模式在构建对象时特别有用。
示例:Person 对象构建
@interface Person : NSObject
- (Person *(^)(NSString *))name;
- (Person *(^)(NSInteger))age;
@end
@implementation Person
- (Person *(^)(NSString *))name {
return ^Person *(NSString *name) {
_name = name;
return self; // 返回 self 实现链式调用
};
}
@end
// 使用示例
Person *person = [[Person alloc] init];
person.name(@"张三").age(20); // 链式调用
使用说明:
在 iOS 开发中,Block 广泛用于各种回调场景,如网络请求、数据加载等。
示例:网络请求回调
- (void)requestData:(void (^)(NSArray *data, NSError *error))completion {
[self.network requestWithCompletion:^(id response, NSError *error) {
if (completion) {
completion(response, error);
}
}];
}
使用说明:
UIKit 框架中的动画 API 大量使用 Block 来处理动画过程和完成回调。
示例:视图动画
[UIView animateWithDuration:0.3
animations:^{
// 动画过程中的属性修改
view.alpha = 0.5;
view.frame = newFrame;
}
completion:^(BOOL finished) {
// 动画完成后的处理
if (finished) {
[view removeFromSuperview];
}
}];
使用说明:
animations
Block 定义动画过程completion
Block 处理动画完成Foundation 框架中的集合类提供了基于 Block 的枚举方法,使集合操作更加灵活。
示例:数组遍历
[array enumerateObjectsUsingBlock:^(id obj, NSUInteger idx, BOOL *stop) {
// 处理每个元素
if ([obj isEqual:targetObject]) {
*stop = YES; // 可以提前终止遍历
}
}];
使用说明:
stop
参数提前终止遍历避免不必要的 Block 复制
全局 Block 无需 copy,栈 Block 仅在需要时复制到堆,注意 __block 变量会随 Block 一起复制。
减少捕获变量数量
仅捕获必要变量,避免大型对象,高频场景优先使用全局 Block。
强弱引用策略
外部用 __weak 防循环引用,内部转 __strong 保对象存活,避免内部频繁创建临时对象。
内存布局优化
高频访问变量前置,减少捕获变量总数,评估 __block 变量的替代方案。
Instruments 三剑客
Allocations 追踪内存生命周期,Leaks 检测循环引用,Time Profiler 分析执行耗时
使用 LLDB 调试 Block
(lldb) po block # 打印 Block 对象
(lldb) p/x (long)block->isa # 查看 Block 的类型
(lldb) p/x (int)block->flags # 查看 Block 的 flags
(lldb) p *(struct __block_impl *)block # 查看 Block 捕获的变量
问题排查三板斧
工具链推荐
Block 是 Objective-C 中的特殊对象,通过 isa 指针继承自 NSObject,实现了匿名函数的功能。它有三种类型:全局 Block(不捕获变量,存储在数据区)、栈 Block(捕获变量,存储在栈上)和堆 Block(由栈 Block 复制而来,存储在堆上)。Block 可以捕获变量,对于基本类型采用值复制,使用 __block
修饰符可以修改捕获的变量,对象类型默认是强引用。在内存管理方面,Block 遵循 Objective-C 的内存管理规则,需要注意循环引用问题,可以通过 __weak
和 weak-strong dance 模式解决。Block 的类型编码使用 @?<返回值类型参数类型>
的格式,支持基本类型、对象类型和 Block 类型。在实际开发中,Block 广泛应用于异步编程、回调处理、动画和集合操作等场景,使代码更加简洁和灵活。
如果觉得本文对你有帮助,欢迎点赞、收藏、关注我,后续会持续分享更多 iOS 底层原理与实战经验!
版权说明:如非注明,本站文章均为 扬州驻场服务-网络设备调试-监控维修-南京泽同信息科技有限公司 原创,转载请注明出处和附带本文链接。
请在这里放置你的在线分享代码