iOS开发基础75-iOS开发中的Block深度解析
Block是iOS和macOS开发中最有用且高效的特性之一。它能捕获并存储周围的上下文,为简化回调和异步操作提供了强有力的支持。本篇文章将详细介绍Block,包括其结构、底层内存布局及相关的内存管理机制。
什么是Block?
Block是一个封装了代码和数据容器的对象。它包含了代码逻辑,并且能够捕获周围的变量以便在未来执行时使用。这使得Block成为一个轻量级的基于闭包(Closure)的解决方案。
Block的基本使用
void (^exampleBlock)(void) = ^{
NSLog(@"Hello, Block!");
};
exampleBlock();
上面的代码声明了一个名为exampleBlock
的Block,它没有参数也没有返回值。当调用exampleBlock
时,Block中的代码被执行并打印“Hello, Block!”。
Block的类型和结构
Block是C语言、Objective-C和C++的语言扩展,使用^
符号来定义。Block分为栈(Stack)、全局(Global)和堆(Heap)三种类型。
栈上的Block
栈上的Block在定义时声明,它们内存布局在栈空间中,一旦超出作用域,Block将被销毁。
void (^stackBlock)(void) = ^{
NSLog(@"I'm a stack block");
};
全局Block
全局Block不存在捕获外部变量,内存永久存储在数据段,生命周期与程序一致。
void (^globalBlock)(void) = ^{
NSLog(@"I'm a global block");
};
堆上的Block
堆上的Block通过栈Block复制创建,生命周期由开发者控制,需手动释放。
void (^heapBlock)(void) = [stackBlock copy];
Block的底层结构
Block的底层是一个结构体,其核心是三个部分:Isa指针、描述符和捕获变量。通过LLVM编译后,你可以看到一个Block的基本结构:
struct Block_layout {
void *isa;
int flags;
int reserved;
void (*invoke)(void *, ...);
struct {
unsigned long int reserved;
unsigned long int size;
// optional helper functions
void (*copy_helper)(void *dst, const void *src);
void (*dispose_helper)(const void *src);
} *descriptor;
// captured variables
};
isa指针
指向Block在运行时的类对象。
flags
Block的消耗标志和属性位,这些标志表示Block的特性,例如是否捕获变量、Block类型等。
reserved
保留字段,通常未使用。
invoke
指向Block实现的函数指针。
descriptor
描述符,用于描述Block的附加信息,包括大小、辅助函数等。
Block的内存管理
Block的内存管理依赖于ARC(Automatic Reference Counting),帮助自动管理Block的内存引用计数。
ARC和Block
使用ARC时,Block会自动地拷贝在栈上的Block到堆上。如果Block捕获了外部变量,ARC会在其内存中保持该变量的引用,并在Block销毁时自动释放。
捕获变量
在Block中使用外部变量时,它们会被捕获到Block的内存布局中:
int a = 10;
void (^captureBlock)(void) = ^{
NSLog(@"Captured variable: %d", a);
};
上面的a
会被捕获到Block的内存布局中,成为Block的一部分。
__block修饰符
在Block内部修改外部变量,需要使用__block修饰符:
__block int a = 10;
void (^modifyBlock)(void) = ^{
a = 20;
};
modifyBlock();
__block修饰符会改变变量的存储位置,使其在Block中也能修改。
Block的底层内存变化
栈上的Block
栈上的Block定义时直接在栈上分配内存,生命周期与作用域一致:
void (^stackBlock)(void) = ^{
NSLog(@"I'm a stack block");
};
堆上的Block
当对一个栈上的Block进行copy操作,会在堆上分配内存,生命周期需要开发者管理:
void (^stackBlock)(void) = ^{
NSLog(@"I'm a stack block");
};
void (^heapBlock)(void) = [stackBlock copy];
ARC会在适当的时候自动为Block调用copy和release。
Block的循环引用
使用Block时容易引发循环引用(retain cycle)。例如,当Block捕获了一个对象,并在该对象中持有Block:
self.block = ^{
// 'self' is captured.
NSLog(@"%@", self);
};
此时self和Block互相持有,造成内存泄漏。解决方法是使用弱引用:
__weak typeof(self) weakSelf = self;
self.block = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
NSLog(@"%@", strongSelf);
};
总结
Block是简化代码、提供高效回调机制的强大工具。理解其底层结构、内存管理及常见问题如循环引用等,对于编写高效、健壮的iOS代码至关重要。掌握这些知识可以帮助开发者更好地利用Block的优势,避免常见的性能和内存问题。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!