什么是Block?
blocks是C语言的扩充功能。用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。
block的本质
- block本质上也是一个OC对象,它内部也有个isa指针
- block是封装了函数调用以及函数调用环境的OC对象
- block是封装函数及其上下文的OC对象
block捕获变量
int age=10;
void (^Block)(void) = ^{
NSLog(@"age:%d",age);
};
age = 20;
Block();
输出值为 age:10
原因:创建block的时候,已经把age的值存储在里面了。
auto int age = 10;
static int num = 25;
void (^Block)(void) = ^{
NSLog(@"age:%d,num:%d",age,num);
};
age = 20;
num = 11;
Block();
输出结果为:age:10,num:11
愿意:auto变量block访问方式是值传递,static变量block访问方式是指针传递(还是不太理解)
为什么block对auto和static变量捕获有差异?
auto自动变量可能会销毁的,内存可能会消失,不采用指针访问;static变量一直保存在内存中,指针访问即可
block对全局变量的捕获方式是?
block不需要对全局变量捕获,都是直接采用取值的
为什么局部变量需要捕获?
考虑作用域的问题,需要跨函数访问,就需要捕获
block的变量捕获(capture)
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制
block里访问self是否会捕获?
会,self是当调用block函数的参数,参数是局部变量,self指向调用者
block里访问成员变量是否会捕获?
会,成员变量的访问其实是self->xx,先捕获self,再通过self访问里面的成员变量
block语法
^返回值 (参数列表)表达式
例如:
^int (int count){return count + 1;}
block变量
Block变量类似于函数指针
声明Block类型变量仅仅是将声明函数指针类型变量的“*”变为^
int (^blk)(int)
block变量与c语言变量完全相同,可以作为以下用途:
自动变量
函数参数
静态变量
静态全局变量
全局变量
block类型
block的类型,取决于isa指针,可以通过调用class方法或者isa指针查看具体类型,最终都是继承自NSBlock类型
__NSGlobalBlock __ ( _NSConcreteGlobalBlock )
对象存储在数据区
__NSStackBlock __ ( _NSConcreteStackBlock )
对象存储在栈区
__NSMallocBlock __ ( _NSConcreteMallocBlock )
对象存储在堆区
注意:
- 堆:动态分配内存,需要程序员自己申请,程序员自己管理
- 栈:自动分配内存,自动销毁,先入后出,栈上的内容存在自动销毁的情况
void (^block1)(void) = ^{
NSLog(@"block1");
};
NSLog(@"%@",[block1 class]);
NSLog(@"%@",[[block1 class] superclass]);
NSLog(@"%@",[[[block1 class] superclass] superclass]);
NSLog(@"%@",[[[[block1 class] superclass] superclass] superclass]);
NSLog(@"%@",[[[[[block1 class] superclass] superclass] superclass] superclass]);
输出结果:
NSGlobalBlock
__NSGlobalBlock
NSBlock
NSObject
null
如何判断block是哪种类型?
- 没有访问auto变量的block是__NSGlobalBlock __ ,放在数据段
- 访问了auto变量的block是__NSStackBlock __
- [__NSStackBlock __ copy]操作就变成了__NSMallocBlock __
对每种类型block调用copy操作后是什么结果?
- __NSGlobalBlock __ 调用copy操作后,什么也不做
- __NSStackBlock __ 调用copy操作后,复制效果是:从栈复制到堆;副本存储位置是堆
- __NSMallocBlock __ 调用copy操作后,复制效果是:引用计数增加;副本存储位置是堆
NSGlobalBlock
如果一个 block 没有访问外部局部变量,或者访问的是全局变量,或者静态局部变量,此时的 block 就是一个全局 block ,并且数据存储在全局区。
//block1没有引用到局部变量
int a = 10;
void (^block)(void) = ^{
NSLog(@"hello world");
};
NSLog(@"block:%@", block);
// block2中引入的是静态变量
static int a1 = 20;
void (^block)(void) = ^{
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
NSStackBlock
在Block名前面加个__weak就是栈区block
__block int a = 10;
static int a1 = 20;
void (^__weak block)(void) = ^{
NSLog(@"hello - %d",a);
NSLog(@"hello - %d",a1);
};
NSLog(@"block:%@", block);
NSMallocBlock
当Block里有引用到局部变量时为堆区block
int a = 10;
void (^block1)(void) = ^{
NSLog(@"%d",a);
};
NSLog(@"block1:%@", block1);
__block int b = 10;
void (^block2)(void) = ^{
NSLog(@"%d",b);
};
NSLog(@"block2:%@", block2);
通过结果我们看到,首先block的地址是在栈区,而block1的地址是在堆区,而栈block引用的变量a的地址并没有变化,而堆block1引用的变量b的地址也相应变成了堆区0x283e0b1b8,并且后面使用的b的地址都是堆区上的。
总结:栈block存放在栈区,对局部变量引用只拷贝局部变量的地址,而堆block存放在堆区,并且直接将局部变量拷贝了一份到堆空间。
NSObject *objc = [NSObject new];
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));// 1
// block 底层源码
// 捕获 + 1
// 堆区block
// 栈 - 内存 -> 堆 + 1
void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕获 + 1 = 2
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"%@---%ld",objc, CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
运行结果:
奇怪为什么堆区block
里面的对象引用计数加2呢?而后面的mallocBlock
只加1呢?
首先objc在strongBlock里面必然会拷贝一份到堆区,所以会加1,但是他是从当前函数的栈区拷贝吗?并不是,对于堆区Block一开始编译时是栈block这时候objc对象地址拷贝了一份引用计数加1,后面从栈block变成堆block,又拷贝了一份引用计数又加1,所以这时候是3,weakBlock是栈block仅拷贝了一份,所以引用计数加1,这时候是4,mallocBlock从weakblock拷贝了一份,所以引用计数再加1,这时候是5,相当于strongBlock
= weakblock + void(^mallocBlock)(void) = [weakBlock copy];