Block概要规范
什么是Blocks?
- 一句话简单概括一下:带有自动变量(局部变量)的匿名函数
顾名思义:Block没有函数名,另外Block带有^标记,插入记号便于查找到Block - Block也被称作闭包、代码块。也就是说我们可以把我们想要执行的代码封装在这个代码块中,我们需要的时候直接进行调用
- Block共享局部作用域的数据。如果实现一个方法,并且该方法定义一个块,则该块可以访问该方法的局部变量和参数(包括堆栈变量),以及函数和全局变量(包括实例变量)。这种访问是只读的,如果使用__block修饰符来声明变量,我们就可以在Block内修改其值。这些我们后面再说
Block语法
格式
标准格式
like:
^int (int count){return count + 1;}
省略格式
返回值类型默认void
如果是void 我们也可以默认省略(void)
like:
^{return count + 1;}
Block变量
- Block变量类似于函数指针
- 声明Block类型变量仅仅是将声明函数指针类型变量的"*“变为”^"
int (^blk)(int)
block变量与c语言变量完全相同,可以作为以下用途:
- 自动变量
- 函数参数
- 静态变量
- 静态全局变量
- 全局变量
截获自动变量
定义时的:带有自动变量值在Block中表现为“截获自动变量值”。
int dmy = 256;
int val = 10;
void (^blk)(void) = ^{
printf("val = %d\n",val);
};
val = 2;
blk();
return 0;
可以看到val = 10
并没有改变
也就是说变量在代码运行到定义那一块时就被截获了,执行的时候已经不是原变量了
__block说明符
block可以截获变量,但是在块里不能修改变量的值
修改会报错
此时我们使用__block修饰符修饰变量,对需要在block内进行复制的变量,使用修饰符修饰,保证可以对变量进行赋值
int main(int argc, const char * argv[]) {
__block int val = 10;
void (^blk)(void) = ^{
// val = 5;
printf("val = %d\n",val);
};
val = 2;
blk();
return 0;
}
刚刚上面的代码 这样就会修改成2了
如果在内部赋值成5 就会变成5了
截获的自动变量
书上这里没有说什么有用的信息
需要注意一句话,在现在的Blocks中呢,截获自动变量的方法并没有实现对C语言数组的截获
也就意味着
const char text[] = "hello";
void(^blk)(void) = ^{
printf("%c\n",text[2]);
};
会报错
然而用指针表示这个数组就没问题了
Block的循环引用
- (id)init {
self = [super init];
blk_ = ^{NSLog(@"self = %@", self);};
return self;
}
初始化这个类的实例时,就会造成循环引用,因为Block语法赋值在了成员变量blk中,因此通过Block语法生成在栈上的Block此时由栈复制到堆,并持有这个self。
self持有Block,Block持有self,循环引用了。
使用__block变量可以避免循环引用
优点:
- 通过__block变量可以控制对象的持有空间
缺点:
- 为避免循环引用必须执行Block
Block的实现
Block的实现是基于指针和函数指针,Block属性是指向结构体的指针。
转c++源码然后进行分析
正常的Block流程
void(^myBlock)(void) = ^{
printf("myBlock");
};
myBlock();
源码部分
int main(int argc, const char * argv[]) {
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
return 0;
}
上票!这什么玩意
分析一下
初始化定义部分block语法变成了&__main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA));
调用block的过程是myBlock->FuncPtr(myBlock)
我们来看一下其中涉及到的对应的部分
初始化Block部分
//Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
所以我们可以看出,__main_block_impl_0结构体也就是Block结构体包含了三个部分:
- 成员变量impl;
- 成员变量Desc指针;
- __main_block_impl_0构造函数
struct __block_impl结构
包含Block实际函数指针的结构体
struct __block_impl {
void *isa; //用于保存Block结构体的实例指针
int Flags; //标志位
int Reserved; //今后版本升级所需的区域大小
void *FuncPtr; //函数指针
};
__block_impl
包含了Block实际函数指针FuncPtr
,FuncPtr
指针指向Block的主体部分,也就是Block对应OC代码中的^{...}
的部分- 还包含了标志位Flags,在实现block的内部操作时可能会用到
- 今后版本升级所需的区域大小Reserved
- __block_impl结构体的实例指针isa
struct __main_block_desc_0
Block附加信息结构体:包含今后版本升级所需区域的大小,Block的大小
static struct __main_block_desc_0 {
size_t reserved; // 今后版本升级所需区域大小
size_t Block_size; // Block 大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
__main_block_impl_0的构造函数
即然都是构造函数了,其就负责初始化__main_block_impl_0
结构体(也就是Block结构体)的成员变量
__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;
}
关于构造函数对各个成员变量的赋值我们回到上面刚刚转成C++的地方
struct __main_block_impl_0 temp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &temp;
该代码通过__main_block_impl_0
构造函数,生成的__main_block_impl_0结构体
(Block结构体)类型实例的指针,赋值给__main_block_impl_0结构体
(Block结构体)类型的指针变量myBlock
可以看到,调用Block结构体构造函数的时候,传入了两个参数
第一个参数:__main_block_func_0
- 其就是Block对应的Block语法的主体部分,看一下__main_block_func_0结构体在底层c++部分的定义
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("myBlock");
}
和我们OC中的代码块一样
这里传入的参数__cself是指向Block的值的指针变量,相当于OC中的self
第二个参数:__main_block_desc_0_DATA
包含该Block的相关信息
调用部分
Block结构体和成员变量以及分析完了 我们看一下main函数中是如何调用Block的
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
- myBlock结构体的第一个成员变量为__block_impl,所以myBlock的首地址,就是
__block_impl impl
的首地址,也就意味着我们第一个强制类型转换可以直接转换成__block_impl类型 ((void (*)(__block_impl *))
是__block_impl中Func的类型((__block_impl *)myBlock)->FuncPtr)
调用函数((__block_impl *)myBlock);
函数参数
对于这个调用参数我理解的意思就是传递其本身到func_0的参数__cself中去,这也就可以看出Block正是作为参数进行的传递。
Block的实质总结
用一句话来说,Block是个对象(其内部第一个成员为isa指针)
在构造函数中,我们可以看到impl.isa = &_NSConcreteStackBlock;
我们对isa指针进行了赋值,_NSConcreteStackBlock
相当于该block实例的父类。在将Block作为OC对象调用时,关于该类的信息放置于_NSConcretestackBlock中
- 对于对象来说,其内部第一个成员为isa指针;
- 对于其实封装了函数调用来说:Block内代码块封装了函数调用,调用Block,就是调用该封装的函数
- 对于执行上下文而言,Block的描述Desc,其描述对象包含了Block信息以及捕获变量的内存相关函数,还有Block所在的上下文?
Block的类型
//MRC环境下
int age = 10;
void (^block)(void) = ^{
NSLog(@"%d %d", height, age);
};
NSLog(@"%@\n %@\n %@\n",
[block class],
[[block class] superclass],
[[[block class] superclass] superclass]);
return 0;
对应的结果
__NSStackBlock __
NSBlock
NSObject
这也就说明上面的这个Block的类型是NSStackBlock
并且它最终继承自NSObject也可以说明Block的本质是OC对象
Block有三种类型
- NSMallocBlock(_NSConcreteMallocBlock) 对象存储在堆区
- NSStackBlock(_NSConcreteStackBlock) 对象存储在栈区
- NSGlobalBlock(_NSConcreteGlobalBlock)对象存储在数据区
简单来说
- 捕获了自动变量 就是Block就是栈类型
- 没有捕获就是数据区
- 不会有一创建就在堆区的,堆区的意义可以理解为和autorelease一样 延长作用域 Stack类型的Block进行了copy操作之后变成了堆区
详细了解一下下面三种情况
NSGlobalBlock
-
记述全局变量的地方使用Block就会默认为Global
-
Block语法的表达式没有截获自动变量
NSStackBlock
除了上述的情况 其他情况下创建的Block都是NSConcreteStackBlock对象
其存储在栈区。如果其所属的变量作用域结束,则该Block就会被废弃,如果Block使用了__block变量,则__block变量同样被废弃
NSMallocBlock
arc下没有栈block了。因为block会自动copy变成堆block
为了解决栈上的Block在变量作用域结束被废弃这一问题,Block提供了复制的功能,可以将Block对象和__block变量从栈区复制到堆区上。
所以后面即使栈区的作用域结束时,堆区上的Block和__block变量仍然可以继续存在,也可以继续使用
即书上的这个图
block作为属性,用什么关键字修饰?
mrc下:使用copy修饰。因为block申明在栈区,使用copy修饰可以将block从栈区copy到堆区。
arc下:使用copy与strong修饰都可以。因为就算用strong,程序会自动copy将block从栈区copy到堆区。
实际测试了一下 无论ARC还是MRC,strong和copy 都可以使对象到堆上 嗯…优化了吧
拷贝情况
在ARC环境下编译器会进行判断,三种情况会自动复制
- Block作为函数返回值返回
- 向方法或函数中传递Block,使用以下两种方法的情况下
-
- Cocoa框架的方法且方法名中含有
usingBlock
等时
- Cocoa框架的方法且方法名中含有
-
- GCD的API
- 将Block赋值给类的附有__strong修饰符的id类型 或 Block类型的成员变量时
当然有自动拷贝我们也可以手动拷贝
我们手动拷贝对不同的Block类有不同的效果
- 栈区 -> 从栈拷贝到堆
- 数据区 -> 不改变
- 堆区 -> 引用计数增加
__block变量的拷贝
在使用__block变量的Block从栈复制到堆上时,__block变量也会受到影响
按照oc的内存管理机制来管理,此时两者的关系就从block使用__block变成了block持有__block
Block截获变量
ARC的时候我们就了解过,变量的几种修饰符,我们来看一下Block如何捕获不同修饰符的类型变量
先扔个结论后面好用
全局变量: 不捕获
局部变量: 捕获值
静态全局变量: 不捕获
静态局部变量: 捕获指针
先看正常的
局部变量的例子
int c = 30;
int main() {
int a = 10, b = 20;
void(^myLocalBlock)(void) = ^{
NSLog(@"%d\n%d\n%d\n",a,b,c);
};
void(^Block)(int, int, int) = ^(int a, int b, int c) {
NSLog(@"%d\n%d\n%d\n",a,b,c);
};
a = 40;
b = 50;
myLocalBlock();
Block(a,b,c);
return 0;
}
直接访问会被捕获
间接不会被捕获
Block表达式截获所使用的局部变量的值,保存了该变量的瞬时值。
Block的自动变量的截获只针对Block中使用的自动变量
直接访问局部变量
带着疑问,为什么仅仅截获瞬时值,而不是局部变量的当前值?看一下对应的c++源码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int a;
int b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_1f40e5_mi_0,a,b,c);
}
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 a = 10, b = 20;
void(*myLocalBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
a = 40;
b = 50;
((void (*)(__block_impl *))((__block_impl *)myLocalBlock)->FuncPtr)((__block_impl *)myLocalBlock);
return 0;
}
- 可以看到
__main_block_impl_0
结构体中多了两个成员变量a,b,这两个的值来自__main_block_impl_0
构造函数中传递的值
构造函数加冒号就是相当于一个赋值的过程
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
- 在
__main_block_func_0
结构体中,变量a,b的值使用的是__cself获取的值
即a = __cself->a; b = __cself->b;
这也就说明了a,b只是Block内部的变量,改变Block外部的局部变量值,并不能改变Block内部的变量值 - 我们也可以发现全局变量并没有存储在Block的结构体中,而是在调用的时候通过直接访问的方式来调用
小小的总结一下这个流程
Block语法部分的源码就是
调用__main_block_impl_0
的构造函数,构造函数正常有两个参数,func_0和desc_0,在截获自动变量时,会把需要截获的自动变量也放入参数列表中,同时__main_block_impl_0
中也会增加两个成员变量a,b,构造函数带参数就是自动给这两个成员变量赋值。在调用func_0时,直接通过__cself(相当于self,其传递的参数也是其本身)的a、b(此时的a,b就是成员变量中的,因为上面的构造函数已经赋值了)
通过传值间接访问局部变量
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, int a, int b, int c) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_ce211a_mi_0,a,b,c);
}
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 a = 10, b = 20;
void(*Block)(int, int, int) = ((void (*)(int, int, int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
a = 40;
b = 50;
((void (*)(__block_impl *, int, int, int))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block, a, b, c);
return 0;
}
__main_block_impl_1
结构体并没有变量a、变量b,说明通过直接传值的方式,变量并没有存进Block的结构体中。- func_0函数中,直接通过参数列表把a,b传进去了,并且在调用时直接传入a,b,c的值这是和直接调用局部变量不同的。
static修饰变量
static int c = 30;
int main() {
static const int a = 10;
static int b = 20;
void (^Block)(void) = ^{
printf("a = %d, b = %d, c = %d\n",a, b, c);
};
b = 100;
c = 100;
Block(); // 输出结果:a = 10, b = 100, c = 100
}
通过上面这个demo我们可以看出
Block对象不会捕获静态全局变量和静态局部变量
同样过一下源码
static int c = 30;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const int *a;
int *b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int *_a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const int *a = __cself->a; // bound by copy
int *b = __cself->b; // bound by copy
printf("a = %d, b = %d, c = %d\n",(*a), (*b), c);
}
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() {
static const int a = 10;
static int b = 20;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &a, &b));
b = 100;
c = 100;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
- 同样根据
__main_block_impl_0
结构体中,静态局部变量static int b
以指针的形式添加为成员变量,而静态局部变量static const int a
以const int *
的形式添加为成员变量。而全局静态变量并没有添加为成员变量
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const int *a;
int *b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int *_a, int *_b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
- 再看func_0结构体部分,静态全局变量直接访问
静态局部变量和静态局部常量都是通过指针传递,但是const修饰的无法进行赋值操作
我们为什么能获取static变量最新的值?
- static修饰的,均存储在全局存储区,该地址在程序运行过程中一直不会改变,所以能访问最新值。
- static在修饰后,全局变量直接访问,局部变量指针访问(只要含static变量)。
const修饰变量
const int c = 30;
int main() {
const int a = 10;
int b = 20;
void (^Block)(void) = ^{
printf("a = %d, b = %d, c = %d\n",a, b, c);
};
Block(); // 输出结果:a = 10, b = 50, c = 60
}
const int c = 30;
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
const int a;
int b;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const int _a, int _b, int flags=0) : a(_a), b(_b) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
const int a = __cself->a; // bound by copy
int b = __cself->b; // bound by copy
printf("a = %d, b = %d, c = %d\n",a, b, c);
}
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() {
const int a = 10;
int b = 20;
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
可以看到
- const全局变量仍然是直接访问的
- const局部变量和正常的自动变量一样,依然是值传递
总结
全局变量: 不捕获
局部变量: 捕获值
静态全局变量: 不捕获
静态局部变量: 捕获指针
const修饰的和局部变量一样
Block截获对象
int main() {
id obj = [NSMutableArray array];
void (^Block)(void) = ^{
NSLog(@"%@",obj);
};
[obj addObject:@1];
Block();
}
//结果为1
#import "Person.h"
int main() {
Person *person = [[Person alloc]init];
person.age = 20;
void (^Block)(void) = ^{
printf("%d",person.age);
};
person.age = 30;
Block();
}
//结果输出为30
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__strong id obj;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __strong id _obj, int flags=0) : obj(_obj) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__strong id obj = __cself->obj; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_09fb31_mi_0,obj);
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
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};
int main() {
id obj = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, obj, 570425344));
((void (*)(id, SEL, ObjectType _Nonnull __strong))(void *)objc_msgSend)((id)obj, sel_registerName("addObject:"), (id _Nonnull)((NSNumber *(*)(Class, SEL, int))(void *)objc_msgSend)(objc_getClass("NSNumber"), sel_registerName("numberWithInt:"), 1));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
- 我们看到
__main_block_impl_0
结构体中多了一个成员变量__strong id obj;
,因为obj是自动变量,所以这里捕获了自动变量obj作为_main_block_impl_0
结构体的成员变量。 - fun0都是指针截获的(其实也不能这样说,应该说在结构体外面自动变量是什么类型,结构体里面成员变量也是什么类型,id和person都是指针类型,所以结构体里面也是指针类型),func_0也有
__strong id obj = __cself->obj;
Person *__strong person = __cself->person;
这样赋值的操作 - 同时还多出来两个函数指针
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->obj, (void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->obj, 3/*BLOCK_FIELD_IS_OBJECT*/);}
针对这两个函数指针
__main_block_copy_0
作用就是调用_Block_object_assign
,相当于retain,将对象赋值在对象类型的结构体变量__main_block_impl_0
中。在栈上的Block复制到堆时会进行调用。__main_block_dispose_0
调用_Block_object_dispose
,相当于release,释放赋值在对象类型的结构体变量中的对象。在堆上Block被废弃时会被调用。
__block修饰符
__block修饰局部变量
int main() {
__block int a = 10, b = 20;
void (^Block)(void) = ^{
printf("%d \n%d \n", a, b);
};
a = 100;
b = 100;
Block();
}
//100 100
struct __Block_byref_a_0 {
void *__isa;
__Block_byref_a_0 *__forwarding;
int __flags;
int __size;
int a;
};
struct __Block_byref_b_1 {
void *__isa;
__Block_byref_b_1 *__forwarding;
int __flags;
int __size;
int b;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_b_1 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_a_0 *a = __cself->a; // bound by ref
__Block_byref_b_1 *b = __cself->b; // bound by ref
printf("%d \n%d \n", (a->__forwarding->a), (b->__forwarding->b));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_assign((void*)&dst->b, (void*)src->b, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);_Block_object_dispose((void*)src->b, 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};
int main() {
__attribute__((__blocks__(byref))) __Block_byref_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
__Block_byref_b_1 b = {(void*)0,(__Block_byref_b_1 *)&b, 0, sizeof(__Block_byref_b_1), 20};
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_a_0 *)&a, (__Block_byref_b_1 *)&b, 570425344));
(a.__forwarding->a) = 100;
(b.__forwarding->b) = 100;
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
上票!
就加了一个__block修饰符就成这了
看一下具体是咋实现的
一个道理,从__main_block_impl_0
说起
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_a_0 *a; // by ref
__Block_byref_b_1 *b; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, __Block_byref_b_1 *_b, int flags=0) : a(_a->__forwarding), b(_b->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__block int a
__block int b
分别变成了__Block_byref_a_0 *a; __Block_byref_b_1 *b;
结构体类型的指针 (为什么是结构体指针,因为通过__block修饰的变量可能不止一个?)对于用__block修饰的变量,不管使用了没,都会相应的生成一个结构体
拿其中任何一个结构体举个例子(只有成员变量不一样奥别尬黑)
struct __Block_byref_a_0 {
void *__isa; //标识对象类的isa实例变量
__Block_byref_a_0 *__forwarding; //传入变量的地址
int __flags; //标志位
int __size; //结构体大小
int a; //存放a实际的值,和之前不加__block修饰符时一致
};
来看一下main函数中这两个的赋值情况
a = {
void *__isa = 0,//对于c语言基础数据类型会是0,因为他们不是对象,没有所属类
__Block_byref_a_0 *__forwarding = &a,
int __flags = 0,
int __size = sizeof(__Block_byref_a_0),
int a = 10;
};
可以看到__isa指针值传空,__forwarding指向了局部变量a本身的地址,__flags分配了0,__size为结构体大小,a赋值为10。
所以在其中,__forwarding其实就是局部变量a本身的地址。
我们可以看到main函数中,
(a.__forwarding->a) = 100;
(b.__forwarding->b) = 100;
所以通过__block来修饰的变量,在Block的主体部分中改变值的原理其实是通过指针传递的方式进行修改。
那么堆上和栈上都有我们的__block,我们如何找到我们需要的那个?
这就用到了__forwarding指针,它在没有复制的时候就是简单的指向自己,而当进行复制以后,就会指向堆上的那个__block变量
栈上不持有仅仅是使用,复制到堆上才持有 同时调用copy那个函数
__block修饰对象
int main() {
__block Person *person = [[Person alloc]init];
void (^Block)(void) = ^{
person = [[Person alloc]init];
NSLog(@"%@",person);
};
Block();
}
struct __Block_byref_person_0 {
void *__isa;
__Block_byref_person_0 *__forwarding;
int __flags;
int __size;
void (*__Block_byref_id_object_copy)(void*, void*);
void (*__Block_byref_id_object_dispose)(void*);
Person *__strong person;
};
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_person_0 *person = __cself->person; // bound by ref
(person->__forwarding->person) = ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"));
NSLog((NSString *)&__NSConstantStringImpl__var_folders_pt_w7b9v92s197g97khtskqdnn40000gn_T_main_b381a2_mi_0,(person->__forwarding->person));
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->person, (void*)src->person, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->person, 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};
int main() {
__attribute__((__blocks__(byref))) __Block_byref_person_0 person = {(void*)0,(__Block_byref_person_0 *)&person, 33554432, sizeof(__Block_byref_person_0), __Block_byref_id_object_copy_131, __Block_byref_id_object_dispose_131, ((Person *(*)(id, SEL))(void *)objc_msgSend)((id)((Person *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("Person"), sel_registerName("alloc")), sel_registerName("init"))};
void (*Block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_person_0 *)&person, 570425344));
((void (*)(__block_impl *))((__block_impl *)Block)->FuncPtr)((__block_impl *)Block);
}
依旧是老样子 从__main_block_impl_0
开始看
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_person_0 *person; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_person_0 *_person, int flags=0) : person(_person->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
并无大碍,继续看一下person_0和对应的初始化
struct __Block_byref_person_0 {
void *__isa = 0;
__Block_byref_person_0 *__forwarding = &person
int __flags = 33554432
int __size = sizeof(__Block_byref_person_0),
void (*__Block_byref_id_object_copy)(void*, void*) = __Block_byref_id_object_copy_131,
void (*__Block_byref_id_object_dispose)(void*) = __Block_byref_id_object_dispose_131,
Person *__strong person;
};
flags = 33554432 即二进制的 1 << 25
BLOCK_HAS_COPY_DISPOSE = (1 << 25), // compiler 译:compiler 含有copy_dispose助手【即拥有copy和dispose函数】
嗯…没懂
然后这里有两个函数 __Block_byref_id_object_copy_131
和 __Block_byref_id_object_dispose_131
static void __Block_byref_id_object_copy_131(void *dst, void *src) {
_Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
}
static void __Block_byref_id_object_dispose_131(void *src) {
_Block_object_dispose(*(void * *) ((char*)src + 40), 131);
}
不就是调用_Block_object_assign
和 _Block_object_release
为什么要加40?
struct __Block_byref_person_0 {
void *__isa; //8字节
__Block_byref_person_0 *__forwarding; //8字节
int __flags; // 4字节
int __size; //4字节
void (*__Block_byref_id_object_copy)(void*, void*); //8字节
void (*__Block_byref_id_object_dispose)(void*); //8字节
Person *__strong person;
};
impson_0的地址和person的地址相差40字节,所以+40是为了找到person指针,通过person指针进行调用吗?嗯…有点道理
__block修饰局部变量,这个变量在block内外属于同一个地址上的变量,可以被block内部的代码修改。
那么这里具体的逻辑其实就是:
Block的内存管理
我们先回顾一下之前学习的变量/对象是怎么管理内存的
干预:程序员手动管理,本质还是要系统管理内存的分配和释放
- 自动局部基本类型变量,因为是值传递,内存是跟随Block,不用干预
- static局部基本类型变量,指针传递,由于分配在静态区,故不用干预
- 全局变量,存储在数据去,不用干预
- 局部对象变量,如果在栈上,不用干预。但Block在拷贝到堆时,对其retain,在Block对象销毁时,对齐release
我发现只要牵扯引用计数就不对劲了
几个问题
为啥是1 …
难道ARC和MRC截获__block对象时都不会调用assign方法吗…idon’tknow
还有一个问题
这个问题可能可以大概理解一下 栈上的Block不会持有对象,仅仅是使用,copy到堆上,持有了一次,变成2,如果加__block变量,还会是1,因为MRC环境下assign这个不会被调用。
对于这个3
对象持有一次
栈上的block没有持有 而是这个第四行成员变量这个指针持有一次
堆上copy持有一次
总共为3
循环引用的问题
- 什么时候会发生循环引用,如何解决?
一个对象中强引用了block,在block中又使用了该对象,就会发生循环引用。
解决是:将该对象使用__weak或者__block修饰符修饰之后再在block中使用。 - 变量前加block和weak和strong的区别
- __strong是为了防止block持有的对象提前释放,__weak和__block是解决循环引用
- __block不管是ARC还是MRC都可以使用,可以修饰对象和基本数据类型
- __weak只能在ARC模式下使用,也只能修饰对象,不能修饰基本数据类型
- __block对象可以在Block中被重新赋值,__weak不可以
- 对于__block变量MRC如何解决循环引用?ARC如何解决?
- MRC解决循环引用用__block,禁止Block对所引用的对象进行retain操作.
- ARC时期__block并不能禁止Block对所引用的对象进行强引用。解决办法可以是在Block中将变量置空,因为需要在Block中对Block进行操作,所以还是需要__block修饰符
- __block在MRC下有两个作用
-
- 允许在BLock中访问和修改局部变量
-
- 进制Block对所引用的对象进行retain操作
- ARC下仅仅只有一个作用
-
- 允许在BLock中访问和修改局部变量
iOS开发“强弱共舞——weak和strong配套使用解决block循环引用问题
- __weak是为了解决循环引用
- __strong是为了防止block持有的对象提前释放