block是什么:
Block是匿名函数(属性保存,在任何地方funptr()调用),但是它的本质还是个对象。(简单的从可以用%@打印就可以看出来)。block底层其实就是个结构体。到OC中就是个对象。
Block类型:(分三类)
全局block:
block内部没有访问它之外的auto局部变量,(staic局部变量,全局变量不算)
堆block、栈block:
block定义后存在栈区,如果作用域改变,就会导致block可能被回收,所以当赋值操作时,编译器在block赋值的时候自动copy一份到堆上,来延长block的生命周期
小知识:
ios 内存空间主要分为:堆、栈、全局/静态区、代码区、数据区
1、栈区(stack)(注意oc下声明的block变量会有个默认的__strong,所以即使是auto变量的block,也会变copy到堆上))
由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。优点是快速高效,缺点时有限制,数据不灵活。[先进后出]
block循环引用问题
看这里https://www.jianshu.com/p/846148cd3e1a
__block
为什么局部变量需要用__block?
当然不是所有的局部变量都需要用__block修饰,只有在需要在block内部改变变量值,并且是auto变量,才需要使用__block修饰。
变量类型 | 捕获到变量内部 | 传递方式 |
---|---|---|
局部变量auto | 捕获 | 传递值 |
局部变量static | 捕获 | 传递指针 |
全局变量 | 不捕获 | 直接取值 |
如表格所示:auto变量在block内部只拿到了值,在block内部捕获到的值的地址和原变量在内存中的地址不是同一个,再怎么修改都无法让原变量产生变化,因为本质上不是同一个变量。
结论在上面,那么我们看看代码:
int blockTestStr = 123;
void (^testMyblock)(void) = ^{
blockTestStr+1;
};
然后将代码转成c的代码看看:
int blockTestStr = 123;
void (*testMyblock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, blockTestStr));
可以发现,block定义中调用了__main_block_impl_0
函数,并且将__main_block_impl_0
函数的地址赋值给了block。
看下 __main_block_impl_0
函数内部结构:
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
int blockTestStr;
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _blockTestStr, int flags=0) : blockTestStr(_blockTestStr) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
再看一下(void *)__main_block_func_0
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int blockTestStr = __cself->blockTestStr; // bound by copy
blockTestStr+1;
}
很熟悉,就是我们block块中的代码,所以__main_block_func_0
函数中其实存储着我们block中写下的代码。而__main_block_impl_0
函数中传入的是(void *)__main_block_func_0
,也就说将我们写在block块中的代码封装成__main_block_func_0
函数,并将__main_block_func_0
函数的地址传入了__main_block_impl_0
的构造函数中保存在结构体内。
&__main_block_desc_0_DATA
是干什么呢
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
中存储着两个参数,reserved
和Block_size
,并且reserved
赋值为0而Block_size
则存储着__main_block_impl_0
的占用空间大小。最终将__main_block_desc_0
结构体的地址传入__main_block_func_0
中赋值给Desc。
看完源码应该就知道为什么定义block之后修改局部变量blockTestStr的值,在block调用的时候无法生效。
原因就是(void *)__main_block_func_0
中保存到block内部blockTestStr并不是原来的 blockTestStr
看源码
int blockTestStr = __cself->blockTestStr; // bound by copy
看转换的时候自动生成的注释就知道,自动变量val虽然被捕获进来了,但是是用 __cself->val
来访问的。Block仅仅捕获了val的值,并没有捕获val的内存地址。因此在block内部怎么改自动变量都没用,因为block内部修改的是变量的副本。
static修饰的静态局部变量
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
int *blockTestStr = __cself->blockTestStr; // bound by copy
(*blockTestStr)+1;
}
明显可以看到捕获的是地址。
全局变量
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) {
blockTestStr+1;
}
可以看到结构体里面没有定义blockTestStr成员变量,代码块里也是直接调用blockTestStr。因为全局变量无论在哪里都可以访问。
言归正传那为什么__block可以让auto变量可以在block内部被修改呢,直接看代码
struct __main_block_impl_0 {
struct __block_impl impl;
struct __main_block_desc_0* Desc;
__Block_byref_blockTestStr_0 *blockTestStr; // by ref
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_blockTestStr_0 *_blockTestStr, int flags=0) : blockTestStr(_blockTestStr->__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_blockTestStr_0 *blockTestStr = __cself->blockTestStr; // bound by ref
(blockTestStr->__forwarding->blockTestStr)+1;
}
可以看到结构体里面多了一段代码,注释by ref清楚明白的告诉我们,传地址。没错,__block修饰之后,传的不再是值,而是地址。
个人理解,有问题请指正