在维基中闭包的定义如下:
在计算机科学中,闭包(英语:Closure),又称词法闭包(Lexical Closure)或函数闭包(function closures),是引用了自由变量的函数。这个被引用的自由变量将和这个函数一同存在,即使已经离开了创造它的环境也不例外。所以,有另一种说法认为闭包是由函数和与其相关的引用环境组合而成的实体。闭包在运行时可以有多个实例,不同的引用环境和相同的函数组合可以产生不同的实例
我们可以简单的把闭包理解成一个带有自动变量的匿名函数
看一个最简单的闭包
^{
printf("Hello,World \n");
}();
上面调用一个匿名的闭包输出了 “Hello,World”, 由于其是一个匿名的闭包,只能在定义完后立即使用()调用.所以应该把它定义在一个函数中。
如果我们想单独调用一个闭包, 那就需要给它一个名字了,不仅要有闭包的实现部分(像上面提到的),还得有闭包的类型申明部分
int main(int argc, const char * argv[]) {
void (^outPrint)(void) = ^{
printf("Hello,World \n");
};
int (^add)(int a, int b) = ^(int a, int b){
printf("%d\n", a+b);
return a+ b;
};
add(1, 2);
}
add 就是一个闭包名名称,跟函数名并无差异,我们可以通过add(1,2)调用。
add 同样可以定义在函数之外,这样在别的函数之中可以直接调用。
int (^add)(int a, int b) = ^(int a, int b){
printf("%d\n", a+b);
return a+ b;
};
在OC中闭包我们称其为block, block的形式如下:
[1]int (^[2]name)([3]int , int) = [4]^[5]int(int a, int b) {
};
[1] : block 返回值
^ block的标记符
[2] block 名字, 调用时使用
[3] 参数列表,只需要参数类型
[4] block 实现部份,参数列表中必须有参数名
[5] block返回值类型
在上面的部代码中我们没有看到[5]的内容,是因为当int作为返回值类型时是可以省略的,系统会默认为返回值类型为int
NSString* (^stringBlock) (int,int) = ^NSString*(int a,int b){
return [NSString stringWithFormat:@"%d%d", a, b];
};
在OC中block作为第一类型,可以作为函数参数,返回值,或直接调用。
当使用block作为函数的参数或返回值时,使用typedef绝对是一个好主意
typedef int(^add)(int a, int b);
这样看起来有点像一个函数指针,经过上面的定义我们就可以直接使用add来代替int(^add)(int a, int b)这一大串了。
OC block捕获上下文变量
根据作用域可分的变量类型:
- 全局变量: 定义在函数体之外的变量(gloabl 或者成员变量)
- 局部变量: 定义在函数体之内或者block之内,或者是函数,block参数
- 上下文变量: 定义在函数中,在函数的block之内调用。
对于全局变量和局部变量我们可以随意改变其值。
我们接下来要研究的就是上下文变量。
int a = 10;
^{
NSLog(@"%d", a + 10);
}();
在block中,捕获了上下文中的变量a,并使用了,但如果我们想在block中改变a的值,编译器是不会同意的(varibale is not assigable(miss __block type specifier))。如果想要改变a的值,我们应该这样
__block int a = 10;
^{
NSLog(@"====%d", a+=10);
}();
__block 为什么会有如此作用呢,我们先看一下block的构成。
我们先来生成 .c 文件
# include <stdio.h>
int main() {
int a = 10;
void(^printVari)() = ^{
printf("%d", a);
};
printVari();
}
通过使用clang -rewrite-objc 命令来生成它的c语言实现,下面是生成文件的一些核心代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __block_impl {
void *isa; // 所有objcect对象都拥有的属性,用来表明对象类型
int Flags; // 用一些二进制bit位来表示一些信息,比如复制block的时候它保存的信息
int Reserved; // 保留
void *FuncPtr; // 指向要执行的操作的函数指针
};
static struct __main_block_desc_0 {
size_t reserved; // 保留
size_t Block_size; // block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
printf("%d", a);
}
上面的代码中我们可以看出block的内存结构如下:
图中的varibales就是能捕获的外部变量。
从上面的代码中我们可以看出,我们所定义的block是由结构体__main_block_impl_0 来实现的, 在这个结构体中又包含了两个结构体 __block_impl impl, _main_block_desc_0,其中最关键的是他的初始化方法.
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a)
它把外部变量a作为函数一个参数,所以a的值被copy了一份,所以在block内部不能修改其外部的值。
Non-Local Variable 就是上面提到上下文变量。
如果要想改变外部的值,我们应该传一个指针,在现实中的代码确实也是这么做的
我们把代码修改一下:
# include <stdio.h>
int main() {
__block int a = 10;
^{
a= 1;
printf("%d", a);
}();
}
其中的block的初始化函数如下:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
这时候传进去的是一个结构体struct __Block_byref_a_0的指针,我们可以通过这个指针去改变外部的值。
哪几种作用域变量可以直接在block中更改呢?
1)静态全局变量
2) 全局变量
3) 静态局部变量
int global_val = 1;
static int static_global_val = 2;
int main(){
static int static_local_val = 3;
NSString* (^stringBlock) (int,int) = ^NSString*(int a,int b){
global_val += 1;
static_global_val += 5;
static_local_val += 10;
return @"123";
};
}
我们看一下转换后的代码
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int * _static_local_val, int flags=0) : static_local_val(_ _static_local_val) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在初化的时候,全局的变量都没有被当作参数传进来,直接使用的是全局的变量,而局部静态变量被当作一个指针参数,所以可以直接修改。
block的内存管理 __weak, __strong
什么时候会引起内存泄露?
在ARC下,只有在作为成员变量的block中使用self才会引起内存泄露(其实我们不用管这些,如果你犯这样的错误编译器会告诉你的)。
原因: block作为成员变量,其属性为copy,self会对其强引用,在ARC模式下会把当前栈中的block复制到堆中,当block中用到self时,block会对self进行强引用以保证自己在执行过程中不会被释放掉。这样block跟self就出现循环引用无法释放
测试方法:在ThirdViewController的viewDidLoad里面调用相应的block,然后重写dealloc方法,在方法里输出『对象已释放』。 通过FirstViewController push到ThirdViewController,然后再返回,如果输出『对象已释放』表明没有循环引用,如果没有输出那么就循环引用了。
__weak就是用来打破循环引用的。
@interface ThirdViewController ()
@property (nonatomic, copy) void(^outChar)();
@property (nonatomic, copy) NSString *name;
@end
@implementation ThirdViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.outChar = ^{
NSLog(@"name = %@", weakSelf.name);
};
}
self.outChar();
现在我们看一下,编译器不再提示了, 运行结果也显示释放完了,
but, 再看一段代码
@interface ThirdViewController ()
@property (nonatomic, copy) void(^outChar)();
@property (nonatomic, copy) NSString *name;
@end
@implementation ThirdViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.name = @"1223";
self.outChar = ^{
[weakSelf test];
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[weakSelf test];
});
};
self.outChar();
}
- (void)test {
NSLog(@"name = %@", self.name);
}
Push到ThirdViewController然后立即返回
2017-02-27 13:20:17.211 Networking[41812:2470863] name = 12232017-02-27 13:20:21.199 Networking[41812:2470863] 第三个页面已经释放了
从输出结果我们可以看出,方法test只执行了一遍,当4秒后想执行第二次的时候当前self已经被释放了。
这时候我们就需要__strong了,它能够在一个block里强引用一个weakSelf,保证其在block执行过程中不会被释放,在block结束之后会释放了,从而不会形成循环引用
@interface ThirdViewController ()
@property (nonatomic, copy) void(^outChar)();
@property (nonatomic, copy) NSString *name;
@end
@implementation ThirdViewController
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.name = @"1223";
self.outChar = ^{
[weakSelf test];
__strong typeof(self)strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[strongSelf test];
});
};
self.outChar();
}
- (void)test {
NSLog(@"name = %@", self.name);
}
- (void)dealloc {
NSLog(@"第三个页面已经释放了");
}