[iOS] Block 的使用

[iOS] Block 的使用

前言

在做 Spotify 项目时在网络请求部分用到了很多的回调相关的知识,当时在 Block 上面有很多的疑问,以这篇博客做一个系统的学习 Block 相关的知识的回顾。

一、什么是 Block

一句话总结:Blocks 是带有 局部变量匿名函数(不带名称的函数)。

Blocks 也被称作 闭包代码块。展开来讲,Blocks 就是一个代码块,把你想要执行的代码封装在这个代码块里,等到需要的时候再去调用。

二、Block 的基本语法

objc 复制代码
// 1. 最简单的无参数无返回值的 Block
^ { 
    NSLog(@"Hello Block");
};

// 2. 有参数有返回值的 Block
^int (int a, int b) { 
    return a + b; 
};

// 3. 简化写法(省略返回值类型,编译器会自动推导)
^(int a, int b) { 
    return a + b; 
};

// 4. 如果没有参数,括号可以省略
^ {
    NSLog(@"无参数 Block");
};

这就是 Block 的几种形式

然后下面是 Block 的几种类型

objc 复制代码
// 1. NSGlobalBlock(全局区)
^ { NSLog(@"全局 Block"); } 

// 2. NSStackBlock(栈上)------ 只在没捕获外部变量且没被强引用时存在
void (^stackBlock)(void) = ^{
    NSLog(@"我是栈 Block");
};

// 3. NSMallocBlock(堆上)------ 被 copy 后就会变成这个
__block int a = 10;
void (^mallocBlock)(void) = ^{
    NSLog(@"捕获了变量 a = %d", a);
};

但一般情况下只关心 NSMallocBlock,因为只有它才能在超出作用域后继续存活。

下面是 Block 的声明定义还有 typedef

objc 复制代码
// 声明一个返回 void,参数为 int 的 Block 类型
typedef void (^MyBlock)(int);

// 使用
MyBlock block = ^(int count) {
    NSLog(@"count = %d", count);
};
block(100);

// 更复杂的例子
typedef NSString * _Nullable (^StringTransformBlock)(NSString * _Nonnull input);

StringTransformBlock transform = ^NSString *(NSString *input) {
    return [input stringByAppendingString:@"!!!"];  
};

三、Block 捕获外部的变量

下面就是 Block 外部变量相关的图表

外部变量类型 默认捕获方式 是否能在 Block 内修改 说明
普通局部变量 (int a) 值捕获(copy) 不能直接修改 只能读
static 变量 指针捕获 可以修改 因为本身在数据区
全局变量 不捕获,直接访问 可以修改 全局可见
__block 修饰的变量 指针捕获(特殊对象) 可以在 Block 内修改 必须加 __block
对象类型(NSString*) 强引用捕获(copy) 不能改变指针指向,但能调用方法 想改指针要加 __block
objc 复制代码
int val = 100;
__block int blockVal = 200;
NSString *str = @"origin";
__block NSString *blockStr = @"block";

void (^testBlock)(void) = ^{
    NSLog(@"val = %d", val);           // 100,值捕获
    // val = 300; // 编译错误,不能改
    
    blockVal = 999;                    
    NSLog(@"blockVal = %d", blockVal); // 999
    
    NSLog(@"str = %@", str);          
    // str = @"new"; // 编译错误
    NSLog(@"blockStr = %@", blockStr); 
    
    blockStr = @"changed";             // 可以实现,因为加了 __block
};

testBlock();

下面就是打印结果

四、Block 的循环引用相关的知识

在这里我们会介绍到三个修饰符,分别是__ weak, __ __ strong , block

__ weak

__ weak ,这个修饰符只能在ARC 模式下使用,他只能修饰对象,比如NSString 等,不能修饰基本的数据类型,同时__ weak 修饰的 Block 不可被重新赋值。

objc 复制代码
self.block = ^{
    NSLog(@"%@", self.title); // self -> block -> self
};

这就是一个典型的循环引用现象,你可能会好奇他为什么会是循环引用,不就是读取了一个属性值吗?

下面是详细的解释

在这里self.block 肯定是一个属性,他的声明方式肯定是长这个样子的

objc 复制代码
@property (nonatomic, copy) void (^block)(void);

然后因为他是 copy 属性,所以当我们开始执行这行代码的时候

objc 复制代码
self.block = ^ {...};

他等价于

objc 复制代码
self->_block = [^{...} copy];   // Block 从栈 copy 到堆

Block 被 copy 到堆上的同时,会把外部用到的对象(这里是 self)强引用一次(因为对象默认是按值捕获,也就是强引用捕获)。

所以现在的情况是:

  • self 强引用了 block(因为 self 有一个 copy 属性的 ivar 指向这个 Block)
  • block 也强引用了 self(因为 Block 内部捕获了 self)

形成了一个环形强引用

这样就会导致一个现象就是两个对象互相强引用,谁都释放不了导致内存泄漏。

所以这时我们就有一个方法来防止内存泄露

那就是用__ __weak__修饰符来修饰 self

objc 复制代码
__weak typeof(self) weakSelf = self;

这样就可以防止强引用。

__ strong

__ strong ,这个修饰符有一个特点他一般要和__ weak__一起使用,因为他要先用 __weak__打破循环,然后再局部强引用防止被释放。

objc 复制代码
__weak typeof(self) weakSelf = self;           // 第1步:打破 retain cycle
self.block = ^{
    __strong typeof(weakSelf) strongSelf = weakSelf;  // 第2步:局部强引用
    if (!strongSelf) return;
    // 安全使用 strongSelf
};

weakSelf 是为了block不持有self,避免Retain Circle循环引用。 在 Block 内如果需要访问 self 的方法、变量,建议使用 weakSelf。

strongSelf的目的是因为一旦进入block执行,假设不允许self在这个执行过程中释放,就需要加入strongSelf。block执行完后这个strongSelf 会自动释放,不会存在循环引用问题。 如果在 Block 内需要多次 访问 self,则需要使用 strongSelf。

__ block

__ block__是一个和 strong__还有 __weak__完全不一样的修饰词。他主要专注于外部就比如下面的这个例子

objc 复制代码
// 例子1:累加器
__block int count = 0;
void (^counter)(void) = ^{
    count++;
    NSLog(@"第 %d 次", count);
};
counter(); // 1
counter(); // 2

加了 __block:变量变成一个"包装对象",Block 和外面拿的是同一个对象变成了可以改的。

小结

这篇博客只是对 Block 浅尝辄止的了解,还有很多深层次的底层原理没有了解到位,后续的博客都会进行完善的。

相关推荐
坏小虎6 分钟前
Expo 快速创建 Android/iOS 应用开发指南
android·ios·rn·expo
光影少年1 小时前
Android和iOS原生开发的基础知识对RN开发的重要性,RN打包发布时原生端需要做哪些配置?
android·前端·react native·react.js·ios
北京自在科技1 小时前
Find My 修复定位 BUG,AirTag 安全再升级
ios·findmy·airtag
简单点了1 小时前
mac安装node环境
macos
简单点了2 小时前
mac安装vm装win11虚拟机
macos
Digitally2 小时前
如何不用 USB 线将 iPhone 照片传到电脑?
ios·电脑·iphone
todoitbo2 小时前
装了 QClaw 之后,我卸掉了好几个 Mac 软件
人工智能·macos·ai·软件·openclaw·qclaw
總鑽風11 小时前
搭建Spring Boot + ELK日志平台,实现可视化日志监控
spring boot·elk·macos
Sim148014 小时前
iPhone将内置本地大模型,手机端AI实现0 token成本时代来临?
人工智能·ios·智能手机·iphone
Digitally16 小时前
如何将 iPad 上的照片传输到 U 盘(4 种解决方案)
ios·ipad