[iOS] Block 的使用

[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 浅尝辄止的了解,还有很多深层次的底层原理没有了解到位,后续的博客都会进行完善的。

相关推荐