写一个简单的block demo验证分析一下block的本质:bash
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void(^myBlock)(void) = ^{
NSLog(@"this is a block");
};
myBlock();
NSLog(@"%@",myBlock);
}
return 0;
}
复制代码
使用命令 xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m
编译成C++代码,找到C++代码最后咱们能够看到block被编译成一个结构体iphone
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;
}
};
复制代码
第一个属性__block_impl
又是一个结构体函数
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
复制代码
这里isa
指针是指向了block的类型
,这个后面再说,在后面贴的C++代码中能够注意一下,都是block的类型。
咱们这个简单的myBlock内部只有一句NSLog的打印,被封装在了__main_block_func_0
这个函数中测试
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_26cca1_mi_0);
}
复制代码
__main_block_func_0
的函数地址又做为__main_block_impl_0
构造函数的第一个参数fp,被赋值给impl
的FuncPtr
ui
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
调用FuncPtr,也就是调用myBlock中封装的函数。
根据以上对源码的分析 ,也验证了咱们最开始说的两点block的本质。this
上面探究block本质的demo代码特别简单,下面咱们由深到浅的继续探究block是如何获取外部变量的。spa
所谓auto
变量也叫自动变量,就是离开做用域(他所在的{}
)就会自动销毁,也就是局部变量,由于auto
能够省略,因此咱们平时均可以直接定义局部变量,不须要在前面加上auto修饰符。
先看一段示例代码:指针
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
int age = 10;
void(^myBlock)(void) = ^{
NSLog(@"this is a block - %d ",age);
};
age = 20;
myBlock();
}
return 0;
}
复制代码
那么请问这段代码的打印age的值是多少呢?有人以为是20,有人以为是10,这里确定的告诉你们,或者你们手动写一写这段代码测试一下打印结果,实际上是10.仍是用命令编译成C++代码一看究竟code
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int age = __cself->age; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_edbb92_mi_0,age);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
int age = 10;
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
age = 20;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
复制代码
一样的结构体,一样的调用方式,上面说过的内容就不重复了,主要看局部变量age是如何被block获取到的:cdn
__main_block_impl_0
结构体中添加了一个age
属性,构造函数中也添加了age
,: age(_age)
是C++语法,表示默认执行age = _age
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age));
直接把age的值获取到,而且保存在结构体内部,并且是值传递
age = 20;
虽然修改了age的值,可是结构体内部的age的值并不会发生改变与auto
相对的,还有static
修饰的变量,那么block
对static
变量捕获的方式和auto
变量有什么不一样呢?看下面的测试代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
auto int age = 10;
static int height = 50;
void(^myBlock)(void) = ^{
NSLog(@"this is a block - %d ,height is %d",age, height);
};
age = 20;
height = 100;
myBlock();
}
return 0;
}
复制代码
输出
block[87871:3465064] this is a block - 10 ,height is 100
复制代码
很明显咱们在block定义以后修改static变量的值,block内部是能够知道的,再看C++代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int age;
int *height;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _age, int *_height, int flags=0) : age(_age), height(_height) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
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_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_716be0_mi_0,age, (*height));
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
auto int age = 10;
static int height = 50;
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, age, &height));
age = 20;
height = 100;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
复制代码
根据上面编译完的代码能够很明显看到变量age和height被block获取时的区别,height被结构体__main_block_impl_0
存储的是地址。
继续根据测试代码和C++代码,分析block对变量的捕获
#import <Foundation/Foundation.h>
int age = 10;
static int height = 50;
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void(^myBlock)(void) = ^{
NSLog(@"this is a block - %d ,height is %d",age, height);
};
age = 20;
height = 100;
myBlock();
}
return 0;
}
复制代码
C++代码:
#pragma clang assume_nonnull end
int age = 10;
static int height = 50;
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;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_1de4db_mi_0,age, height);
}
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)};
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
age = 20;
height = 100;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
return 0;
}
复制代码
能够得出结论:
其实self
的捕获,才是开发中常常遇到的状况,也是引发block循环引用的罪魁祸首
。固然self的捕获能够归到上面两类中,但仍是想拿出来单独分析一下。也算是对上面两类变量分析结果的实践吧。
#import "Person.h"
@implementation Person
- (void)test_block {
void(^block)(void) = ^{
NSLog(@"this is a block - %p",self);
};
}
@end
复制代码
先思考一下,这里
self
会被捕获到block中嘛,self在这里是一个auto变量仍是static变量仍是全局变量呢?先分析出是什么变量,下一步咱们才能知道self是以什么形式被捕获到block中或者会不会被捕获到block中。
你知道答案了吗?
看看下面的C++代码:
struct __Person__test_block_block_impl_0 {
struct __block_impl impl;
struct __Person__test_block_block_desc_0* Desc;
Person *self;
__Person__test_block_block_impl_0(void *fp, struct __Person__test_block_block_desc_0 *desc, Person *_self, int flags=0) : self(_self) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
复制代码
能够获得结论,block是会捕获self
的,但是self是个局部变量嘛?这里又要提到method
的两个默认参数了self
和_cmd
,代码中的test方法被编译成_I_Person_test_block
,他有两个参数是self
和_cmd
,函数的参数就是局部变量
,而后被捕获到block中赋值给struct中的self
static void _I_Person_test_block(Person * self, SEL _cmd) {
void(*block)(void) = ((void (*)())&__Person__test_block_block_impl_0((void *)__Person__test_block_block_func_0, &__Person__test_block_block_desc_0_DATA, self, 570425344));
}
复制代码
同理,若是是block中访问self的成员变量(self.name)
或者经过self调用别的方法([self method])
,也一样会捕获self到block中。