Block
01基本认识
block的原理是怎样的?本质是什么?
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
封装了函数调用以及调用环境的OC对象
__block的作用是什么?有什么使用注意点?
block的属性修饰词为什么是copy?使用block有哪些使用注意?
block一旦没有进行copy操作,就不会在堆上
使用注意:循环引用问题
block在修改NSMutableArray,需不需要添加__block?
^{
NSLog(@"this is a block");
}();//调用的话在后面加上小括号就可以了
block本质上也是一个OC对象,它内部也有个isa指针
block是封装了函数调用以及函数调用环境的OC对象
block的底层结构如右图所示
block底层结构
我们可以把代码转成cpp文件的代码查看一下block的组成,他第一个变量就是isa指针,说明他本质是一个对象,他也会把外面访问变量在内部也创建一个,封装到block的结构体之中
cpp文件代码
struct __block_impl {
void *isa;//
int Flags;
int Reserved;
void *FuncPtr;//函数地址,block里面执行的那些代码放到这里
};
struct __ViewController__viewDidLoad_block_desc_1 {
size_t reserved;//保留的可能还有其他的用途
size_t Block_size;//block占据多少内存
};
struct __ViewController__viewDidLoad_block_impl_1 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_1* Desc;
//相当于构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;//下面的函数地址
Desc = desc;
}
};
//封装了block执行逻辑的函数
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_2f788f_mi_0);
}
- (void)viewDidLoad {
[super viewDidLoad];
^{
NSLog(@"this is a block");
}();
void (^block)(void) = ^{
NSLog(@"this is a block");
};
struct __ViewController__viewDidLoad_block_impl_1 *block1 =(__bridge struct __ViewController__viewDidLoad_block_impl_1 *)block;
block();
//创建一个block。调用了一个结构体的构造函数。返还给我门一个结构体
void (*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));//
//执行block
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
我们可以通过断点去内存查看,这段block的执行的代码的开始地址,就是FuncPtr的存的地址
02底层数据结构
03变量捕获01-auto变量
假如需要参数,重新生成cpp代码
void (^block)(int , int ) = ^(int a, int b)
{
NSLog(@"%i", a+b);
};
block(10,23);
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)((__block_impl *)block, 10, 23);
//更复杂引用外部
int age = 10;
void(^block)(void) = ^
{
NSLog(@"%i", age);//10
};
/*
int age = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
*/
age = 20;
block();
//内部实现
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int flags=0) : age(_age) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
引用外部的变量之后,我们发现block的内部结构发生了改变,多了一个age成员变量,这个变量的值是在编译的时候就传到了block的机构体内部,所以后面的更改不会影响他的值,因为block 执行函数里面的代码时候,使用的是自己的age,而不是外部的age.这就是为什么打印的是10
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_218f06_mi_0, age);
}
04-变量捕获02-static变量
为了保证block内部能够正常访问外部的变量,block有个变量捕获机制,所谓的捕获就是block内部有个专门的成员来存储那个传进来的值,
默认情况下我们定义出来的变量,前面都有一个auto,所以平时我们都是省略,离开作用域就会销毁。捕获的时候是值传递,
static 局部变量
int age = 10;
static int height = 10;
void(^block)(void) = ^
{
NSLog(@"%i-%i", age,height);//10-20
};
/*
int age = 10;
static int height = 10;
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));//他传进去的是地址
*/
age = 20;
height = 20;
block();
/*
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_3bdf0b_mi_0, age,(*height));//访问的是指针变量所指向的值。
}
*/
//内部代码实现
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
int *height = __cself->height; // bound by copy存的是外面变量的地址值
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_3bdf0b_mi_0, age,(*height));
}
为什么会有这样的差异,因为auto可能会销毁,age可能在内存中消失,我们以后访问的时候防止他消失我们访问不到数据,static就算是函数执行完了也会存在内存,不用担心他会销毁,所以可以传递地址
block捕获变量关系图
变量捕获03-全局变量
block使用外部的全局变量不会捕获到内部,而是直接使用
void(^block)(void) = ^
{
NSLog(@"%i-%i", _age,height_);//20-20
};
_age = 20;
height_ = 20;
block();
//
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_g1_c9nxm0n17mq959qfc_j0wtnr0000gn_T_main_1ee27a_mi_0, _age,height_);
}
//
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
因为他是一个全局变量,在哪里都可以访问他,所以不需要捕获,局部变量需要捕获是因为作用域的问题,可能出现跨函数访问变量,
-(void)test{
void (^block)(void)=^{
NSLog(@"%@", self);
};
block();
}
//
static void _I_Person_test(Person * self, SEL _cmd) {
void (*block)(void)=((void (*)())&__Person__test_block_impl_0((void *)__Person__test_block_func_0, &__Person__test_block_desc_0_DATA, self, 570425344));
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
}
//struct __Person__test_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_desc_0* Desc;
Person *self;
__Person__test_block_impl_0(void *fp, struct __Person__test_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
说明他会被捕获,说明self是一个局部变量。函数默认有两个参数self 和cmd.
_name可以堪称self->name;说明他也会被捕获,但是捕获的是self对象。而不是单独的对_name捕获。
用self.name,还是要捕获self