什么是Block?
Block (块),封装了函数调用以及调用环境的 OC 对象,Objective-C闭包(可以在内部访问外部的值),相当于C语言的函数指针,把一个函数写在一个函数内部,而OC并没有函数(方法)嵌套这一语法
void(^blockName)();
int(^blockName2)(int a, int b, int c);
格式: 返回值 (^block名称)(形参列表)
^
代表块的符号
void(^blockName)(void) = ^{
};
void(^blockName)(int a, int b) = ^(int a, int b){
};
int(^blockName)(void) = ^int{
return 3;
};
实现部分的返回值可以省略,像这样:
int(^blockName)(void) = ^{
return 3;
};
int(^blockName)(int a, int b) = ^int(int a, int b){
return 3 + a * b;
};
实现部分的返回值int
同样可以省略
//无参数无返回值
blockName();
//有参数有返回值
int result = blockName(7, 12);
现在已声明的blockName
代表一个块,那么调用这个块既可以通过blockName(7, 12);
,也可以这样:
int result = ^(int a, int b) {
return 3 + a + b;
}(7, 12);
Block作为形式参数在方法中的声明与上述格式略有不同(块的名称在外面)
现在Jaxon
类和Jacky
类中分别实现以下方法:
Jaxon.h
- (void)askJackyForHelp: (void(^)(int num))blockName isOK: (void(^)(BOOL boolValue))completion;
Jaxon.m
- (void)askJackyForHelp:(void (^)(int))blockName isOK:(void (^)(BOOL))completion {
blockName(3);
//传入completion块的参数非1即0
completion(arc4random() % 2);
}
Jacky.m
- (void)helpDoWith: (int)num {
NSLog(@"帮忙做事%d次", num);
}
接下来在main
函数中调用:
Jaxon* jaxon = [[Jaxon alloc] init];
[jaxon askJackyForHelp:^(int num) {
Jacky* jacky = [[Jacky alloc] init];
[jacky helpDoWith: num];
} isOK:^(BOOL boolValue) {
//成功和失败的概率各占一半
if (boolValue) {
NSLog(@"帮忙成功");
} else {
NSLog(@"帮忙失败");
}
}];
运行结果:
这样是不是可以起到代理的作用,Jaxon
委托Jacky
帮忙做事,Jaxon实现不了的委托Jacky实现,因此Block块也可以用于界面传值或其他需要使用代理模式的程序设计中
文章开头也提到了块其实也是一种对象,可以将ta理解为一种数据类型
那么也可以用typedef
关键字给Block起别名,看以下示例:
typedef void(^Help)(int num);
typedef void(^Finish)(BOOL boolValue);
上面的方法也就可以这样声明:
- (void)askJackyForHelp:(Help)blockName isOK:(Finish)completion;
块的属性关键字一般需要是是copy
:
@interface Jaxon : NSObject
//无别名
@property (nonatomic, copy)void(^helpBlock)(int num);
//有别名
//@property (nonatomic, copy)Help helpBlock;
- (void)askMyselfDo;
@end
@implementation Jaxon
- (void)askMyselfDo {
self.helpBlock(5);
}
@end
main函数:
Jaxon* jaxon = [[Jaxon alloc] init];
jaxon.helpBlock = ^(int num) {
NSLog(@"我自己做%@次", @(num));
};
[jaxon askMyselfDo];
关于copy
关键字,编者也简单了解一下,底层原理以后再加以详细的剖析:
在
ARC
环境下,编译器会根据情况自动将栈上的 block 复制到堆上,比如以下几种情况: 手动调用 block 的
copy`方法时;
- block 作为函数返回值时(Masonry 框架中用很多);
- 将 block 赋值给
__strong
指针时;- block 作为 Cocoa API 中方法名含有
usingBlock
的方法参数时;- block 作为 GCD API 的方法参数时。
block 作为属性的写法:
ARC
下写strong
或者copy
都会对 block 进行强引用,都会自动将 block 从栈 copy 到堆上;
建议都写成copy
,这样 MRC 和 ARC 下一致。
Block刚创建时存放在栈区,使用时copy
到堆区
为保证Block内部能正常访问到外部的变量,Block有一种变量捕获机制
auto
变量:正常定义出来的变量默认都是auto类型,只是省略了
auto int age = 20;
auto类型的局部变量会被捕获
到block块内部,访问方式为值传递
int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
NSLog(@"%d %p", age, &age);
};
age = 20;
//可以打印出来,说明block块是可以访问到外部信息的
blockName();
NSLog(@"%d %p", age, &age);
auto
类型的局部变量被捕获到block块内部时,block内部会自动生成一个相同的成员变量,用来存储这个变量的值,因此打印的block外部的age
地址与内部age
地址不一样值传递
,修改外部age
变量的值,不会影响到block内部的变量Block内部只能调用外部变量,不能修改:
Block 默认情况下是使用被捕获的外部变量的只读拷贝,因此在 Block 内部无法直接修改外部变量的值
解决办法如下:
static
修饰(原因:捕获static类型的局部变量是指针传递
,可以访问到该变量的内存地址)__block
(我们只希望临时用一下这个变量临时改一下而已,而改为 static 变量和全局变量会一直在内存中)当变量被__block
修饰时,block可以修改外部全局变量:
__block int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
age = 30;
NSLog(@"%d %p", age, &age);
};
blockName();
NSLog(@"%d %p", age, &age);
static
类型的局部变量会被捕获
到block内部,访问方式指针传递
static int age = 10;
NSLog(@"%d %p", age, &age);
void(^blockName)(void) = ^ {
NSLog(@"%d %p", age, &age);
};
age = 20;
blockName();
NSLog(@"%d %p", age, &age);
static
类型的局部变量被捕获到block内部时,block块内部会生成一个相同类型的指针,指向捕获到内部的age
变量的地址指针传递
,修改外部的age
变量的值,会影响到block
内部的age
变量全局变量不会被捕获
到block内部,访问方式为直接访问
int _age = 10;
static int _height = 175;
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSLog(@"%d %p", _age, &_age);
void(^blockName)(void) = ^ {
_age = 30;
NSLog(@"%d %p", _age, &_age);
};
blockName();
_age = 20;
NSLog(@"%d %p", _age, &_age);
}
return 0;
}
对于对象
类型的局部变量,block会连同ta的所有权修饰符
一起捕获
为什么局部变量需要捕获,全局变量不用捕获呢?