本文首发于个人我的博客:『不羁阁』html
文章连接:bujige.net/blog/iOS-Bl…c++
文中 Demo 地址:传送门git
本文用来介绍 iOS开发中 『Blocks』的基本使用。经过本文您将了解到:github
- 什么是 Blocks ?
- Blocks 变量语法
- Blocks 变量的声明与赋值
- Blocks 变量截获局部变量值特性
- 使用 __block 说明符
- Blocks 变量的循环引用以及如何避免
一句话总结:Blocks 是带有 局部变量 的 匿名函数(不带名称的函数)。编程
Blocks 也被称做 闭包、代码块。展开来说,Blocks 就是一个代码块,把你想要执行的代码封装在这个代码块里,等到须要的时候再去调用。bash
下边咱们先来理解 局部变量、匿名函数 的含义。多线程
在 C 语言中,定义在函数内部的变量称为 局部变量。它的做用域仅限于函数内部, 离开该函数后就是无效的,再使用就会报错。闭包
int x, y; // x,y 为全局变量
int fun(int a) {
int b, c; //a,b,c 为局部变量
return a+b+c;
}
int main() {
int m, n; // m,n 为局部变量
return 0;
}
复制代码
从上边的代码中,咱们能够看出:函数
匿名函数指的是不带有名称的函数。可是 C 语言中不容许存在这样的函数。post
在 C 语言中,一个普通的函数长这样子:
int fun(int a);
复制代码
fun 就是这个函数的名称,在调用的时候必需要使用该函数的名称 fun 来调用。
int result = fun(10);
复制代码
在 C 语言中,咱们还能够经过函数指针来直接调用函数。可是在给函数指针赋值的时候,一样也是须要知道函数的名称。
int (*funPtr)(int) = &fun;
int result = (*funPtr)(10);
复制代码
而咱们经过 Blocks,能够直接使用函数,不用给函数命名。
咱们使用
^
运算符来声明 Blocks 变量,并将 Blocks 对象主体部分包含在{}
中,同时,句尾加;
表示结尾。
下边来看一个官方的示例:
int multiplier = 7;
int (^ myBlock)(int)= ^(int num) {
return num * multiplier;
};
复制代码
这个 Blocks 示例中,myBlock 是声明的块对象,返回类型是 整型值,myBlock 块对象有一个 参数,参数类型为整型值,参数名称为 num。myBlock 块对象的 主体部分 为 return num * multiplier;
,包含在 {}
中。
参考上面的示例,咱们能够将 Blocks 表达式语法表述为:
^ 返回值类型 (参数列表) { 表达式 };
例如,咱们能够写出这样的 Block 语法:
^ int (int count) { return count + 1; };
复制代码
Blocks 规定能够省略好多项目。例如:返回值类型、参数列表。若是用不到,均可以省略。
^ (参数列表) { 表达式 };
上边的 Blocks 语法就能够写为:
^ (int count) { return count + 1; };
复制代码
表达式中,return 语句使用的是 count + 1
语句的返回类型。若是表达式中有多个 return 语句,则全部 return 语句的返回值类型必须一致。
若是表达式中没有 return 语句,则能够用 void 表示,或者也省略不写。代码以下:。
^ void (int count) { printf("%d\n", count); }; // 返回值类型使用 void
^ (int count) { printf("%d\n", count); }; // 省略返回值类型
复制代码
## 2.2 省略参数列表 ^ 返回值类型 (void) { 表达式 };
若是表达式中,没有使用参数,则用 void 表示,也能够省略 void。
^ int (void) { return 1; }; // 参数列表使用 void
^ int { return 1; }; // 省略参数列表类型
复制代码
## 2.3 省略返回值类型、参数列表:^ { 表达式 };
从上边 2.1 中能够看出,不管有无返回值,均可以省略返回值类型。而且,从 2.2 中能够看出,若是不须要参数列表的话,也能够省略参数列表。则代码能够简化为:
^ { printf("Blocks"); };
复制代码
Blocks 变量的声明与赋值语法能够总结为:
返回值类型 (^变量名) (参数列表) = Blocks 表达式
注意:此处返回值类型不能够省略,若无返回值,则使用 void 做为返回值类型。
例如,定义一个变量名为 blk 的 Blocks 变量:
int (^blk) (int) = ^(int count) { return count + 1; };
int (^blk1) (int); // 声明变量名为 blk1 的 Blocks 变量
blk1 = blk; // 将 blk 赋值给 blk1
复制代码
Blocks 变量的声明语法有点复杂,其实咱们能够和 C 语言函数指针的声明类比着来记。
Blocks 变量的声明就是把声明函数指针类型的变量
*
变为^
。
// C 语言函数指针声明与赋值
int func (int count) {
return count + 1;
}
int (*funcptr)(int) = &func;
// Blocks 变量声明与赋值
int (^blk) (int) = ^(int count) { return count + 1; };
复制代码
返回值类型 (^变量名) (参数列表) = 返回值类型 (参数列表) { 表达式 };
咱们能够把 Blocks 变量做为局部变量,在必定范围内(函数、方法内部)使用。
// Blocks 变量做为本地变量
- (void)useBlockAsLocalVariable {
void (^myLocalBlock)(void) = ^{
NSLog(@"useBlockAsLocalVariable");
};
myLocalBlock();
}
复制代码
@property (nonatomic, copy) 返回值类型 (^变量名) (参数列表);
做用相似于 delegate,实现 Blocks 回调。
/* Blocks 变量做为带有 property 声明的成员变量 */
@property (nonatomic, copy) void (^myPropertyBlock) (void);
// Blocks 变量做为带有 property 声明的成员变量
- (void)useBlockAsProperty {
self.myPropertyBlock = ^{
NSLog(@"useBlockAsProperty");
};
self.myPropertyBlock();
}
复制代码
- (void)someMethodThatTaksesABlock:(返回值类型 (^)(参数列表)) 变量名;
能够把 Blocks 变量做为 OC 方法中的一个参数来使用,一般 blocks 变量写在方法名的最后。
// Blocks 变量做为 OC 方法参数
- (void)someMethodThatTakesABlock:(void (^)(NSString *)) block {
block(@"someMethodThatTakesABlock:");
}
复制代码
[someObject someMethodThatTakesABlock:^返回值类型 (参数列表) { 表达式}];
// 调用含有 Block 参数的 OC方法
- (void)useBlockAsMethodParameter {
[self someMethodThatTakesABlock:^(NSString *str) {
NSLog(@"%@",str);
}];
}
复制代码
经过 3.2.3 和 3.2.4 中,Blocks 变量做为 OC 方法参数的调用,咱们一样能够实现相似于 delegate 的做用,即 Blocks 回调(后边应用场景中会讲)。
typedef 返回值类型 (^声明名称)(参数列表);
声明名称 变量名 = ^返回值类型(参数列表) { 表达式 };
复制代码
// Blocks 变量做为 typedef 声明类型
- (void)useBlockAsATypedef {
typedef void (^TypeName)(void);
// 以后就可使用 TypeName 来定义无返回类型、无参数列表的 block 了。
TypeName myTypedefBlock = ^{
NSLog(@"useBlockAsATypedef");
};
myTypedefBlock();
}
复制代码
先来看一个例子。
// 使用 Blocks 截获局部变量值
- (void)useBlockInterceptLocalVariables {
int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
printf("a = %d, b = %d\n",a, b);
};
myLocalBlock(); // 打印结果:a = 10, b = 20
a = 20;
b = 30;
myLocalBlock(); // 打印结果:a = 10, b = 20
}
复制代码
为何两次打印结果都是 a = 10, b = 20
?
明明在第一次调用 myLocalBlock();
以后已经从新给变量 a、变量 b 赋值了,为何第二次调用 myLocalBlock();
的时候,使用的仍是以前对应变量的值?
由于 Block 语法的表达式使用的是它以前声明的局部变量 a、变量 b。Blocks 中,Block 表达式截获所使用的局部变量的值,保存了该变量的瞬时值。因此在第二次执行 Block 表达式时,即便已经改变了局部变量 a 和 b 的值,也不会影响 Block 表达式在执行时所保存的局部变量的瞬时值。
这就是 Blocks 变量截获局部变量值的特性。
实际上,在使用 Block 表达式的时候,只能使用保存的局部变量的瞬时值,并不能直接对其进行改写。直接修改编译器会直接报错,以下图所示。
那么若是,咱们想要该写 Block 表达式中截获的局部变量的值,该怎么办呢?
若是,咱们想在 Block 表达式中,改写 Block 表达式以外声明的局部变量,须要在该局部变量前加上 __block
的修饰符。
这样咱们就能实现:在 Block 表达式中,为表达式外的局部变量赋值。
// 使用 __block 说明符修饰,更改局部变量值
- (void)useBlockQualifierChangeLocalVariables {
__block int a = 10, b = 20;
void (^myLocalBlock)(void) = ^{
a = 20;
b = 30;
printf("a = %d, b = %d\n",a, b); // 打印结果:a = 20, b = 30
};
myLocalBlock();
}
复制代码
能够看到,使用 __block 说明符修饰以后,咱们在 Block表达式中,成功的修改了局部变量值。
根据评论区增补一点:若是 Blocks 截获的是 Objective-C 对象,例如 NSMutablearray 类对象,对该对象调用变动的方法是不会编译报错的(例如调用
addObject:
方法)。可是若是对其调用赋值的方法,则会编译报错,就必需要加上 __block 说明符进行修饰了。
从上文中咱们知道 Block 会对引用的局部变量进行持有。一样,若是 Block 也会对引用的对象进行持有,从而会致使相互持有,引发循环引用。
/* —————— retainCycleBlcok.m —————— */
#import <Foundation/Foundation.h>
#import "Person.h"
int main() {
Person *person = [[Person alloc] init];
person.blk = ^{
NSLog(@"%@",person);
};
return 0;
}
/* —————— Person.h —————— */
#import <Foundation/Foundation.h>
typedef void(^myBlock)(void);
@interface Person : NSObject
@property (nonatomic, copy) myBlock blk;
@end
/* —————— Person.m —————— */
#import "Person.h"
@implementation Person
@end
复制代码
上面 retainCycleBlcok.m
中 main()
函数的代码会致使一个问题:person 持有成员变量 myBlock blk,而 blk 也同时持有成员变量 person,二者互相引用,永远没法释放。就形成了循环引用问题。
那么,如何来解决这个问题呢?
在 ARC 下,可声明附有 __weak 修饰符的变量,并将对象赋值使用。
int main() {
Person *person = [[Person alloc] init];
__weak typeof(person) weakPerson = person;
person.blk = ^{
NSLog(@"%@",weakPerson);
};
return 0;
}
复制代码
这样,经过 __weak,person 持有成员变量 myBlock blk,而 blk 对 person 进行弱引用,从而就消除了循环引用。
MRC 下,是不支持 __weak 修饰符的。可是咱们能够经过 __block 来消除循环引用。
int main() {
Person *person = [[Person alloc] init];
__block typeof(person) blockPerson = person;
person.blk = ^{
NSLog(@"%@", blockPerson);
};
return 0;
}
复制代码
经过 __block 引用的 blockPerson,是经过指针的方式来访问 person,而没有对 person 进行强引用,因此不会形成循环引用。
以上是 iOS 开发:『Blocks』详尽总结 (一)基本使用 的所有内容,能够用来了解 Block,入门使用。下一篇咱们经过 Block 由 OC 代码转变的 C++ 源码来抽丝剥茧的讲一下 Block 的底层原理。