在 iOS 开发中,Block 是一种闭包(Closure)的概念,可以将一段代码块作为一个对象进行传递和存储,类似于函数指针。Block 可以捕获其定义时所在范围内的变量,并在需要的时候执行这段代码块。Block 的使用可以方便地实现回调、异步操作、事件处理等功能。
Block其本质也是对象,是一段代码块。iOS之所以引入Block,是因为使用Block可以精简代码,减少耦合。
int (^multiply)(int, int) = ^(int a, int b) {
return a * b;
};
int result = multiply(3, 5); // 调用 Block
- (void)performOperationWithBlock:(void (^)(void))block {
block();
}
[self performOperationWithBlock:^{
NSLog(@"执行 Block");
}];
@property (nonatomic, copy) void (^completionBlock)(void);
self.completionBlock = ^{
NSLog(@"完成操作");
};
self.completionBlock();
在 iOS 开发中,Block 可以捕获其定义时所在范围内的外部变量,这种特性称为变量捕获(Variable Capture)。当 Block 内部引用外部变量时,Block 会自动捕获这些变量的值或引用,以便在 Block 执行时使用。在 Block 中使用外部变量可以实现数据共享和传递,但需要注意一些细节和注意事项。
变量类型 | 类型 | 捕获到block内部 | 访问方式 |
---|---|---|---|
局部变量 | auto | 可以 | 值传递 |
局部变量 | static | 可以 | 指针传递 |
全局变量 | — | 不可以 | 直接访问 |
局部变量 auto
(自动变量) ,我们平时写的局部变量,默认就有 auto
(自动变量,离开作用域就销毁)。比如:
int age = 20; // 等价于 auto int age = 20;
static 修饰的局部变量,不会被销毁。由下面代码可以看得出来,Block 外部修改static 修饰的局部变量,依然能影响 Block 内部的值:
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 文件,去了解详情。缺点是会永久存储,内存开销大。
全局变量不能也无需捕获到 Block 内部,因为全局变量是存放在全局静态区的,直接访问就完事了。缺点也是内存开销大。
在 Block 内部修改 Block 外部变量通常需要使用 __block
关键字来声明外部变量,以便在 Block 内部对外部变量进行修改。使用 __block
关键字可以使外部变量在 Block 内部变为可变,允许对其进行赋值操作。
下面是一个示例代码,演示如何在 Block 内部修改外部变量:
__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 内部修改外部变量,实现对外部变量的可变性,但需要注意内存管理和避免循环引用等问题,以确保代码的正确性和稳定性。
根据存储位置分类:
在 iOS 开发中,Block 可以存储在栈上或堆上,具有不同的生命周期和内存管理方式。下面详细介绍栈上的 Block 和堆上的 Block 的特点和区别:
特点:
内存管理:
注意事项:
特点:
copy
方法,可以将栈上的 Block 复制到堆上。内存管理:
copy
和 release
。注意事项:
__weak
修饰符来避免循环引用。总的来说,栈上的 Block 在作用域内有效,适合用于临时的、局部的逻辑处理;而堆上的 Block 可以延长生命周期,适合在不同作用域中传递和复用。开发者在使用 Block 时,需要根据实际情况选择合适的存储位置,以确保内存管理和逻辑执行的正确性。
在 iOS 开发中,可以通过一些方法来区分 Block 是存储在栈上还是堆上的。下面列举了几种方法来区分栈上的 Block 和堆上的 Block:
NSLog
打印 Block 的地址__block
变量__block
变量时,变量的值会被 Block 捕获,但是在作用域结束后,__block
变量的值会被更新为 Block 执行时的值。__block
变量时,变量的值会被复制到堆上的 Block 中,作用域结束后,__block
变量的值不会被更新。Block_copy
函数Block_copy
函数会将其复制到堆上,返回一个新的堆上 Block。Block_copy
函数仍会返回一个新的堆上 Block,但是地址不同于原始的堆上 Block。__weak
修饰符__weak
变量时,变量会被自动置为 nil,因为栈上的 Block不会强引用 __weak
变量。__weak
变量时,变量会正常工作,因为堆上的 Block会强引用 __weak
变量。通过以上方法,可以辨别出 Block 是存储在栈上还是堆上的。在实际开发中,了解 Block 的存储位置有助于正确管理内存和避免潜在的问题。
循环引用是指两个或多个对象之间互相持有对方的强引用,导致它们无法被释放,从而造成内存泄漏。在使用 Block 时,循环引用是一个常见的问题,特别是在 Block 中捕获了外部对象并且外部对象又持有了 Block。
下面是一个典型的循环引用场景:
@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 中持有了 self
,ViewController
的实例无法被释放,从而造成内存泄漏。
为了避免循环引用,可以采取以下方法:
使用 __weak
修饰符来避免循环引用,例如在 Block 中捕获 self
时使用 __weak
修饰符:
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
// 使用 weakSelf 避免循环引用
NSLog(@"Block is executed");
[weakSelf doSomething];
};
使用 __block
修饰符来避免循环引用,可以让 Block 持有一个弱引用的指针,而不是强引用:
__block typeof(self) blockSelf = self;
self.myBlock = ^{
// 使用 blockSelf 避免循环引用
NSLog(@"Block is executed");
[blockSelf doSomething];
};
通过以上方法,可以有效地避免 Block 的循环引用问题,确保内存管理的正确性。在实际开发中,特别是在使用 Block 时,需要注意避免循环引用问题,以避免内存泄漏和其他潜在的问题。