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 时,需要注意避免循环引用问题,以避免内存泄漏和其他潜在的问题。

相关推荐
colorknight4 小时前
1.2.3 HuggingFists安装说明-MacOS安装
人工智能·低代码·macos·huggingface·数据科学·ai agent
奇客软件9 小时前
如何从相机的记忆棒(存储卡)中恢复丢失照片
深度学习·数码相机·ios·智能手机·电脑·笔记本电脑·iphone
GEEKVIP11 小时前
如何修复变砖的手机并恢复丢失的数据
macos·ios·智能手机·word·手机·笔记本电脑·iphone
kuai-12 小时前
MacOS配置python环境
开发语言·python·macos
一丝晨光13 小时前
继承、Lambda、Objective-C和Swift
开发语言·macos·ios·objective-c·swift·继承·lambda
心存留恋就不会幸福18 小时前
Mac屏蔽系统更新,取出红点标记&&如果解锁hosts文件
macos
GEEKVIP1 天前
iPhone/iPad技巧:如何解锁锁定的 iPhone 或 iPad
windows·macos·ios·智能手机·笔记本电脑·iphone·ipad
KWMax1 天前
RxSwift系列(二)操作符
ios·swift·rxswift
Mamong1 天前
Swift并发笔记
开发语言·ios·swift
超爱找事1 天前
iMazing只能苹果电脑吗 Win和Mac上的iMazing功能有区别吗
windows·macos·电脑·数据备份·苹果·imazing