iOS高级理论: Block介绍和使用

一、Block概要

1. Block的定义

在 iOS 开发中,Block 是一种闭包(Closure)的概念,可以将一段代码块作为一个对象进行传递和存储,类似于函数指针。Block 可以捕获其定义时所在范围内的变量,并在需要的时候执行这段代码块。Block 的使用可以方便地实现回调、异步操作、事件处理等功能。

2. 为什么要引入Block

Block其本质也是对象,是一段代码块。iOS之所以引入Block,是因为使用Block可以精简代码,减少耦合。

二、Block的用法

2.1 作为变量使用
objective-c 复制代码
int (^multiply)(int, int) = ^(int a, int b) {
    return a * b;
};
int result = multiply(3, 5); // 调用 Block
2.2 作为方法参数
objective-c 复制代码
- (void)performOperationWithBlock:(void (^)(void))block {
    block();
}
[self performOperationWithBlock:^{
    NSLog(@"执行 Block");
}];
2.3 作为属性或实例变量
objective-c 复制代码
@property (nonatomic, copy) void (^completionBlock)(void);
self.completionBlock = ^{
    NSLog(@"完成操作");
};
self.completionBlock();

三、Block与外界变量

在 iOS 开发中,Block 可以捕获其定义时所在范围内的外部变量,这种特性称为变量捕获(Variable Capture)。当 Block 内部引用外部变量时,Block 会自动捕获这些变量的值或引用,以便在 Block 执行时使用。在 Block 中使用外部变量可以实现数据共享和传递,但需要注意一些细节和注意事项。

变量类型 类型 捕获到block内部 访问方式
局部变量 auto 可以 值传递
局部变量 static 可以 指针传递
全局变量 --- 不可以 直接访问
3.1 局部变量 auto

局部变量 auto (自动变量) ,我们平时写的局部变量,默认就有 auto (自动变量,离开作用域就销毁)。比如:

objective-c 复制代码
int age = 20; // 等价于 auto int age = 20;
3.2 局部变量 static

static 修饰的局部变量,不会被销毁。由下面代码可以看得出来,Block 外部修改static 修饰的局部变量,依然能影响 Block 内部的值:

objective-c 复制代码
static int height  = 30;
int age = 20;
void (^block)(void) =  ^{
     NSLog(@"age is %d height = %d",age,height);
};
age = 25;
height = 35;
block();

// 执行结果
age is 20 height = 35

这是为什么会改变呢?

因为 age 是直接值传递,height 传递的是 *height,也就是说直接把内存地址传进去进行修改了。大家可以根据上面讲解内容尝试编译成 cpp 文件,去了解详情。缺点是会永久存储,内存开销大。

3.3 全局变量

全局变量不能也无需捕获到 Block 内部,因为全局变量是存放在全局静态区的,直接访问就完事了。缺点也是内存开销大。

3.4 在Block内如何修改Block外部变量

在 Block 内部修改 Block 外部变量通常需要使用 __block 关键字来声明外部变量,以便在 Block 内部对外部变量进行修改。使用 __block 关键字可以使外部变量在 Block 内部变为可变,允许对其进行赋值操作。

下面是一个示例代码,演示如何在 Block 内部修改外部变量:

objective-c 复制代码
__block int externalVariable = 10;
void (^block)(void) = ^{
    externalVariable = 20;
    NSLog(@"内部修改后的外部变量值:%d", externalVariable);
};
block(); // 输出:内部修改后的外部变量值:20
NSLog(@"外部变量值:%d", externalVariable); // 输出:外部变量值:20

在上面的示例中,通过使用 __block 关键字声明外部变量 externalVariable,使得在 Block 内部可以修改外部变量的值。当调用 Block 后,外部变量的值被成功修改,并且在 Block 外部也可以访问到修改后的值。

需要注意的是,在使用 __block 关键字声明外部变量时,需要注意内存管理的问题,特别是在 ARC(Automatic Reference Counting)环墨下,避免出现循环引用或内存泄漏的情况。确保在适当的时候解除对 Block 的强引用,以避免出现循环引用问题。

总的来说,使用 __block 关键字可以在 Block 内部修改外部变量,实现对外部变量的可变性,但需要注意内存管理和避免循环引用等问题,以确保代码的正确性和稳定性。

四、Block的分类

根据存储位置分类:

  • 栈上的 Block:默认情况下,Block 存储在栈上,当 Block 离开作用域时会被销毁。
  • 堆上的 Block:通过调用 copy 方法,可以将栈上的 Block 复制到堆上,以延长 Block 的生命周期。

在 iOS 开发中,Block 可以存储在栈上或堆上,具有不同的生命周期和内存管理方式。下面详细介绍栈上的 Block 和堆上的 Block 的特点和区别:

4.1 栈上的 Block
  1. 特点

    • 默认情况下,Block 存储在栈上。
    • 栈上的 Block 在定义它的作用域内有效,当作用域结束时,Block 会被销毁。
    • 栈上的 Block 不能跨作用域使用,一旦超出作用域范围,Block 就会失效。
  2. 内存管理

    • 栈上的 Block 不需要手动管理内存,由系统自动管理。
    • 当 Block 被复制到堆上时,系统会自动将其从栈上复制到堆上,以延长 Block 的生命周期。
  3. 注意事项

    • 将栈上的 Block 传递给异步执行的方法时,需要注意避免在 Block 执行过程中访问已经释放的栈上变量,可以通过将 Block 复制到堆上来避免此问题。
4.2 堆上的 Block
  1. 特点

    • 通过调用 copy 方法,可以将栈上的 Block 复制到堆上。
    • 堆上的 Block 的生命周期可以延长,不会受到作用域的限制。
    • 堆上的 Block 可以在不同的作用域中传递和使用,不会因为作用域结束而失效。
  2. 内存管理

    • 堆上的 Block 需要手动管理内存,需要在不再需要使用 Block 时手动释放。
    • 在 ARC 环境下,系统会自动管理 Block 的内存,无需手动调用 copyrelease
  3. 注意事项

    • 当 Block 捕获外部变量时,如果 Block 存储在堆上,需要注意避免循环引用问题,可以使用 __weak 修饰符来避免循环引用。

总的来说,栈上的 Block 在作用域内有效,适合用于临时的、局部的逻辑处理;而堆上的 Block 可以延长生命周期,适合在不同作用域中传递和复用。开发者在使用 Block 时,需要根据实际情况选择合适的存储位置,以确保内存管理和逻辑执行的正确性。

4.3 区分栈上的 Block与堆上的 Block

在 iOS 开发中,可以通过一些方法来区分 Block 是存储在栈上还是堆上的。下面列举了几种方法来区分栈上的 Block 和堆上的 Block:

4.3.1. 使用 NSLog 打印 Block 的地址
  • 栈上的 Block:栈上的 Block 在作用域结束后会被销毁,所以可以通过在 Block 定义后立即打印 Block 的地址来查看 Block 的地址是否相同。
  • 堆上的 Block:堆上的 Block 在复制到堆上后会有不同的地址,可以通过在 Block 复制到堆上后打印 Block 的地址来查看是否有变化。
4.3.2. 使用 __block 变量
  • 栈上的 Block :栈上的 Block 捕获 __block 变量时,变量的值会被 Block 捕获,但是在作用域结束后,__block 变量的值会被更新为 Block 执行时的值。
  • 堆上的 Block :堆上的 Block 捕获 __block 变量时,变量的值会被复制到堆上的 Block 中,作用域结束后,__block 变量的值不会被更新。
4.3.3. 使用 Block_copy 函数
  • 栈上的 Block :对栈上的 Block 使用 Block_copy 函数会将其复制到堆上,返回一个新的堆上 Block。
  • 堆上的 Block :对堆上的 Block 使用 Block_copy 函数仍会返回一个新的堆上 Block,但是地址不同于原始的堆上 Block。
4.4.4. 使用 __weak 修饰符
  • 栈上的 Block :栈上的 Block 捕获 __weak 变量时,变量会被自动置为 nil,因为栈上的 Block不会强引用 __weak 变量。
  • 堆上的 Block :堆上的 Block 捕获 __weak 变量时,变量会正常工作,因为堆上的 Block会强引用 __weak 变量。

通过以上方法,可以辨别出 Block 是存储在栈上还是堆上的。在实际开发中,了解 Block 的存储位置有助于正确管理内存和避免潜在的问题。

五、Block的循环引用

循环引用是指两个或多个对象之间互相持有对方的强引用,导致它们无法被释放,从而造成内存泄漏。在使用 Block 时,循环引用是一个常见的问题,特别是在 Block 中捕获了外部对象并且外部对象又持有了 Block。

下面是一个典型的循环引用场景:

objective-c 复制代码
@interface ViewController : UIViewController
@property (nonatomic, copy) void (^myBlock)(void);
@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    
    self.myBlock = ^{
        // Block 中捕获了 self,导致循环引用
        NSLog(@"Block is executed");
        [self doSomething];
    };
}

- (void)doSomething {
    // Block 中捕获了 self,导致循环引用
    NSLog(@"Doing something");
}

@end

在上面的代码中,ViewController 的实例持有一个 Block,并且在 Block 中捕获了 self,导致了循环引用。当 ViewController 的实例被释放时,由于 Block 中持有了 selfViewController 的实例无法被释放,从而造成内存泄漏。

为了避免循环引用,可以采取以下方法:

  1. 使用 __weak 修饰符来避免循环引用,例如在 Block 中捕获 self 时使用 __weak 修饰符:

    objective-c 复制代码
    __weak typeof(self) weakSelf = self;
    self.myBlock = ^{
        // 使用 weakSelf 避免循环引用
        NSLog(@"Block is executed");
        [weakSelf doSomething];
    };
  2. 使用 __block 修饰符来避免循环引用,可以让 Block 持有一个弱引用的指针,而不是强引用:

    objective-c 复制代码
    __block typeof(self) blockSelf = self;
    self.myBlock = ^{
        // 使用 blockSelf 避免循环引用
        NSLog(@"Block is executed");
        [blockSelf doSomething];
    };

通过以上方法,可以有效地避免 Block 的循环引用问题,确保内存管理的正确性。在实际开发中,特别是在使用 Block 时,需要注意避免循环引用问题,以避免内存泄漏和其他潜在的问题。

相关推荐
Roc.Chang几秒前
macos 使用 nvm 管理 node 并自定义安装目录
macos·node.js·nvm
三劫散仙4 小时前
Mac vscode 激活列编辑模式
macos
程序猿看视界5 小时前
如何在 UniApp 中实现 iOS 版本更新检测
ios·uniapp·版本更新
endingCode8 小时前
45.坑王驾到第九期:Mac安装typescript后tsc命令无效的问题
javascript·macos·typescript
dr李四维9 小时前
iOS构建版本以及Hbuilder打iOS的ipa包全流程
前端·笔记·ios·产品运营·产品经理·xcode
️ 邪神9 小时前
【Android、IOS、Flutter、鸿蒙、ReactNative 】自定义View
flutter·ios·鸿蒙·reactnative·anroid
soulteary9 小时前
突破内存限制:Mac Mini M2 服务器化实践指南
运维·服务器·redis·macos·arm·pika
小江村儿的文杰20 小时前
XCode Build时遇到 .entitlements could not be opened 的问题
ide·macos·ue4·xcode
比格丽巴格丽抱20 小时前
flutter项目苹果编译运行打包上线
flutter·ios
网络安全-老纪1 天前
iOS应用网络安全之HTTPS
web安全·ios·https