Block基础
1. 什么是Block
Block是对象,它封装和保存了一段代码,这段代码可以在任何时候执行。block可以作为函数参数或函数的返回值,本身又可作为参数或返回值。
2. Block和函数的相似性
1) 可以保存代码
2) 有返回值
3) 有形参
4) 调用方式一样
3. Block的语法
1) block作为本地变量
void(^block)() = ^{
NSLog(@"block本地变量");
};
block();
2) block作为类的成员属性
@property (nonatomic ,copy) void(^block)(NSString *param);
3) block作为函数参数
-(void)testWithBlock:(void(^block)(NSString *param))block;
4)使用typedef定义block类型
typedef void(^block)(NSString *param);
block blockName = ^(NSString *param) {
NSLog(%@,param);
};
4. Block可以访问局部变量,但是不能修改局部变量,如果要修改就要将局部变量加上关键字__block。
// 这种情况会报错
int num = 0;
int (^addBlock)(int a,int b) = ^(int a,int b){
num = a+b;
return num;
};
// 这种情况是正确的
__block int num = 0;
int (^addBlock)(int a,int b) = ^(int a,int b){
num = a+b;
return num;
};
//
将此代码在main方法里运行,注意:main.m文件中不能出现import UIKit和Applegate,要改为#include <stdio.h>
#include <stdio.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
int num = 20;
void (^testBlock)(void) = ^(){
printf("%d",num);
};
num = 30;
testBlock();
}
}
打开mian.m文件所在的文件夹,终端运行clang -rewrite-objc main.m,用clang编译器对源文件main.m中的objective-c代码转换成C代码放在main.cpp文件中,会在此文件夹下生成main.cpp文件,双击打开。
在mian方法中,num的定义与OC里相同。
1.不带__block修饰的变量
1)__block_imp 结构体
__block_imp 结构体是编译器生成的结构体,每一个block都会用到这个结构体
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr; // 函数指针,指向编译器生成的__main_block_func_0
};
2)__main_block_impl_0
__main_block_impl_0是一个函数名,有三个参数,又是一个结构体,结构体如下:
struct __main_block_impl_0 {
struct __block_impl impl; // __block_impl变量impl
struct __main_block_desc_0* Desc; //__main_block_desc_0 指针,指向编译器给我们生成的结构体变量
int num; //
// 构造体的构造函数
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
其中,
num(_num):
在 c++ 里面 指定_num(形参) 将来赋值给num 这个实参,也就是这个__main_block_impl_0 结构体中的 int num;在这里 int num = 20
impl.FuncPtr = fp;
将fp赋值给了 impl 结构体的 FuncPtr 参数
3)__main_block_func_0
编译器根据block代码生成的全局态函数,会被赋值给impl.FuncPtr
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int num = __cself->num; // 结构体访问自己的属性,访问num=20
printf("%d",num);
}
4) __main_block_desc_0
编译器根据block代码生成的block描述,主要是记录下__main_block_impl_0的大小,结构体如下:
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; // //这里就生成了__main_block_desc_0的变量__main_block_desc_0_DATA
5) void (*testBlock)(void)
void (^testBlock)(void) = ^(){
printf("%d",num);
};
对应于
void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
__main_block_impl_0((void )__main_block_func_0, &__main_block_desc_0_DATA, num)是创建了一个__main_block_impl_0结构体。 前面加了&,是取了这个实例的地址。最后(void ()())将它强转为一个函数地址。
整句话就是定义一个函数指针指向一个新创建的__main_block_impl_0实例的地址。这个实例的两个参数正是编译器生成的静态函数__main_block_func_0和__main_block_desc_0的变量__main_block_desc_0_DATA。
6)testBlock()
testBlock();
对应着
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
整句话就是通过函数指针testBlock调用函数FuncPtr,传的参数为指针testBlock本身。
int main(int argc, char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int num = 20;
void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, num));
num = 30;
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
}
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
调用testBlock的时候,虽然num变为30,但是打印的是FuncPtr存放的是num=20。
总的来说,当我们声明一个block变量a并为它赋值时,其实就是创建一个函数指针ptrA,再根据block a赋值的代码生成一个静态函数,而指针ptrA就指向这个静态函数。block a调用时就是使用函数指针ptrA调用生成的静态函数。
__block修饰的变量
#include <stdio.h>
int main(int argc, char * argv[]) {
@autoreleasepool {
int num = 20;
void (^testBlock)(void) = ^(){
printf("%d",num);
};
num = 30;
testBlock();
}
}
打印的是30.
1)__Block_byref_num_0
根据带__block修饰的变量num,编译器生成的结构体
struct __Block_byref_num_0 {
void *__isa;
__Block_byref_num_0 *__forwarding; // //指向被创建出来的__Block_byref_num_0实例
int __flags;
int __size;
int num;
};
2) __main_block_impl_0
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_num_0 *num; // 保存__Block_byref_num_0变量
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_num_0 *_num, int flags=0) : num(_num->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3) __main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_num_0 *num = __cself->num; // bound by ref
printf("%d",(num->__forwarding->num));
}
4) __main_block_copy_0和__main_block_dispose_0
这两个函数分别会在block被拷贝到堆和释构时调用的,作用是对__Block_byref_num_0实例的内存进行管理
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->num, (void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->num, 8/*BLOCK_FIELD_IS_BYREF*/);}
5)__main_block_desc_0
static struct __main_block_desc_0 {
size_t reserved;
size_t Block_size;
void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);// //回调函数指针,会被赋值为__main_block_copy_0
void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
6) main方法
int main(int argc, char * argv[]) {
{ __AtAutoreleasePool __autoreleasepool;
// 我们定义的__block int num转换后并不是一个简单的栈变量,而会是新建的__Block_byref_num_0堆变量*/
__attribute__((__blocks__(byref))) __Block_byref_num_0 num = {(void*)0,(__Block_byref_num_0 *)&num, 0, sizeof(__Block_byref_num_0), 20};
void (*testBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344));
(num.__forwarding->num) = 30;
((void (*)(__block_impl *))((__block_impl *)testBlock)->FuncPtr)((__block_impl *)testBlock);
}
}
使用了__block 所以创建了一个block 类型的结构体,取值的时候,拿到的是结构体的地址,只要把地址传递过去,就有了最高的操作权限,到时候再去取值就可以取到内存中最新的值。
最后的(num.__forwarding->num) = 30;拿到结构体里面的地址去修改num的值为30。