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的优势,避免常见的性能和内存问题。

posted @   Mr.陳  阅读(4830)  评论(0编辑  收藏  举报
编辑推荐:
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
阅读排行:
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· NetPad:一个.NET开源、跨平台的C#编辑器
· PowerShell开发游戏 · 打蜜蜂
· 凌晨三点救火实录:Java内存泄漏的七个神坑,你至少踩过三个!
点击右上角即可分享
微信分享提示