Block探究
在我们实际的开发过程中,block的使用可以说是经常遇到到的了吧,GCD,网络请求,动画都随处可见block的影子,能熟练的使用block是不是就意味着你真正的了解block呢?正所谓知其然,知其所以然。
-
基本使用
block.png1.无参无返回值的block
void (^block)() = ^{
NSLog(@"无参数无返回值的block");
};
block();
2.有参无返回值的block
void (^block)(int) = ^(int a){
NSLog(@"有参数无返回值的block %d",a);
};
block(5);
3.有参有返回值的block
int (^block)(int) = ^(int a){
NSLog(@"有参数有返回值的block %d",a);
return a + 5;
};
block(5);
- 作为属性的使用
typedef void (^sumBlock)(int,int);
@property (nonatomic, copy) sumBlock sumBlc;
self.sumBlc = ^(int a,int b){
NSLog(@"sum a + b = %d",a+b);
};
self.sumBlc(3,5);
- 作为函数调用
- (void)blockTest{
NSString *(^stringBlc)(int) = ^(int a){
// 需要定义的操作
return [NSString stringWithFormat:@"%dabc",a];
};
NSString * str1 = stringBlc(123);
NSString * str2 = stringBlc(345);
NSString * str3 = stringBlc(456);
}
-
底层实现
简单的介绍了一下block的使用,接下来我们就看看它的底层实现到底是什么样子的,首先创建一个Mac os程序,在main.m函数中写一个简单block
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 10;
void (^block)(int , int) = ^(int a, int b){
NSLog(@"this is my block a = %d , b = %d",a,b);
NSLog(@"this age is %d",age);
};
block(5,5);
}
return 0;
}
然后进入相应的文件夹,在终端输入clang -rewrite-objc main.m
,发现多了一个main.cpp的文件,这是一个9w多行的代码,在文件的最下面我们发现了main函数
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
(void*)__main_block_func_0,
&__main_block_desc_0_DATA,
age)
//如果block内部没有用到age,只有前两个参数
//__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DAT)
);
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
(__block_impl *)block, 5, 5);
}
return 0;
}
我们发现__main_block_impl_0
这个函数传入了3个参数__main_block_func_0
, &__main_block_desc_0_DATA
, age
,并且将函数的地址赋给了block,那我们看一下__main_block_impl_0
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;
}
};
__main_block_impl_0
是一个结构体,它里面有两个结构体:impl和 Desc,age 和一个构造函数,并且将传入的__main_block_func_0
赋值给了impl.FuncPtr,将传入的&__main_block_desc_0_DATA
赋值给了Desc,在构造函数中为age完成了赋值,这一狗仔函数主要是为这两个结构体赋值,那么我们接下来看看这两个结构体。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
impl
是一个__block_impl
类型的结构体,里面有一个isa指针,这个isa的赋值是block的地址,所以说block实质上是一个OC对象,Flags=0,FuncPtr是传入的__main_block_func_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)};
Desc
是一个__main_block_desc_0
类型的结构体,里面只有reserved和Block_size,而__main_block_desc_0_DATA
的作用就是保存了这个block的大小。
最后就只剩下__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_0d2748_mi_1,age);
}
这个函数首先传入了a,b两个参数,从__cself中取出block代码块要用的age,然后是两个NSLog,那我们可以看出这个__main_block_func_0
的作用就是保存了block的代码块实现。
至此,__main_block_impl_0
中出现的函数和结构体我们都已经看过了,我们可以大致分析如下:
- block的底层是一个
__main_block_impl_0
的函数,这个函数会传入3个参数(没有用到外部变量是2个):分别是__main_block_func_0
,&__main_block_desc_0_DATA
和用到的外部变量,将__main_block_func_0
赋值给impl
中的FuncPtr
,__main_block_desc_0_DATA
赋值给Desc
,age在构造函数中赋值。 -
__main_block_func_0
中保存的是block定义的代码块,__main_block_desc_0_DATA
中保存的是block的大小。
void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
(void*)__main_block_func_0,
&__main_block_desc_0_DATA,
age)
它们之间的关系可以用下图表示:
block的结构图.png
那我们看看block在这么调用的
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
(__block_impl *)block, 5, 5);
由于block指向的是__main_block_impl_0
的地址,而impl是__main_block_impl_0
结构体中的第一个成员,里面保存着block的代码块,将block转化为(__block_impl *)
类型去调用block的代码块。
-
__block做了什么
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 10;
void (^block)(int , int) = ^(int a, int b){
NSLog(@"this is my block a = %d , b = %d",a,b);
NSLog(@"this age is %d",age);
age = 20;
NSLog(@"this age is %d",age);
};
block(5,5);
}
return 0;
}
this is my block a = 5 , b = 5
this age is 10
this age is 20
我们知道如果age没有用__block
修饰的话,在block中直接修改age的值编译器直接会报错,加上就可以直接修改age的值,那__block
到底做了些什么?
将main.m文件clang之后我们发现以下不同:
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {
(void*)0,
(__Block_byref_age_0 *)&age,
0,
sizeof(__Block_byref_age_0),
10
};
void (*block)(int , int) = ((void (*)(int, int))&__main_block_impl_0(
(void *)__main_block_func_0,
&__main_block_desc_0_DATA,
(__Block_byref_age_0 *)&age,
570425344)
);
((void (*)(__block_impl *, int, int))((__block_impl *)block)->FuncPtr)(
(__block_impl *)block, 5, 5);
struct __Block_byref_age_0 {
void *__isa; // 0
__Block_byref_age_0 *__forwarding; // 指向自己的指针
int __flags; // 0
int __size; // __Block_byref_age_0的大小
int age; // age
};
首先定义了一个__Block_byref_age_0
类型的结构体age,__forwarding指向结构体age自己,里面封装了外部变量age。
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_age_0 *age; // by ref
__main_block_impl_0(void *fp,
struct __main_block_desc_0 *desc,
__Block_byref_age_0 *_age,
int flags=0)
: age(_age->__forwarding)
{
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
在__main_block_impl_0
内部有一个__Block_byref_age_0
类型的age,在构造函数中_age->__forwarding
指向了结构体自己。
static void __main_block_func_0(struct __main_block_impl_0 *__cself, int a, int b) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_0,a,b);
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_1,
(age->__forwarding->age));
(age->__forwarding->age) = 20;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_3s_8pdzhy3d5t7399crmzwwqs000000gn_T_main_8816bf_mi_2,
(age->__forwarding->age));
}
首先取出__main_block_impl_0
中的结构体age
,在修改age的值得时候,先通过__forwarding
找到结构体age的地址,再去修改age
中真正的age对象的值。
static void __main_block_copy_0(struct __main_block_impl_0*dst,
struct __main_block_impl_0*src) {
_Block_object_assign((void*)&dst->age, (void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {
_Block_object_dispose((void*)src->age, 8/*BLOCK_FIELD_IS_BYREF*/);}
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*);
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};
因为在C语言的结构体中,编译器没法很好的进行初始化和销毁操作。这样对内存管理来说是很不方便的。所以就在 __main_block_desc_0
结构体中间增加成员变量 void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*)
和void (*dispose)(struct __main_block_impl_0*)
,利用OC的Runtime进行内存管理。
这里的_Block_object_assign
和_Block_object_dispose
就对应着retain和release方法。
_Block_object_assign函数调用时机及作用
当block进行copy操作的时候就会自动调用__main_block_desc_0
内部的__main_block_copy_0
函数,__main_block_copy_0
函数内部会调用_Block_object_assign
函数。
_Block_object_assign函数会自动根据__main_block_impl_0
结构体内部的age是什么类型的指针,对age对象产生强引用或者弱引用。
_Block_object_dispose函数调用时机及作用
当block从堆中移除时就会自动调用__main_block_desc_0
中的__main_block_dispose_0
函数,__main_block_dispose_0
函数内部会调用_Block_object_dispose
函数。
_Block_object_dispose会对age对象做释放操作,类似于release,也就是断开对age对象的引用,而age究竟是否被释放还是取决于age对象自己的引用计数。