在介绍今天的博客之前,你应该了解,block的三大类型,block的变量捕获机制,哪些情况下是NSMallcBlock,这些内容我前面的博客都有介绍的非常清楚,相信你看了会有很多收获.
通过这篇博客的学习,你将会了解到:
__block它当修饰符的时候,它的底层到底是怎么实现的.
首先看下面的代码:
这里为什么去修改变量的值会报错,大家知道吗?这时候我们去把当前的main.m文件转成c++文件,因为我们知道oc的底层就是c/c++,然后是汇编语言,然后是机器语言,可以通过下面的命令在终端执行:(注意age=20注释,不然转不了)
xcrun -sdk iphoneos clang -arch arm64 -rewrite-objc main.m -o main.cpp
我们想在一个函数里面修改另一个函数的age值,那是不是非常不可能啊,而上面的func_0里面访问的age是之前编译器里面存储的age,跟那个变量没有任何关系,要改也只能改block里面的age,所以我们上面那么写就是有问题.
如果我们在参数前面加一个static参数就不会报错了!请看下图
这里就不说为什么了,因为我们知道static它底层实现是传的是*age,也就是存的是地址值,所以我们只要拿到这个地址,可以随便修改这个变量
重点来了
请看下面的代码,我用__block来当作修饰符如下:
为什么这里用__block就是没有问题的呢?我们还是转成c++代码看一下底层的实现:
这里很清楚,只要我用__block修饰变量,就会吧变量包装成对象,上面能看到是结构体,这个非常清楚,至于为啥__forwarding访问后面我会再说
下面我来解释一下*__forwarding这个是什么东西.请看下面,是我把多余的删了的(大家可以对照原来的删一下多余的代码,便于我们观看):
下面的框框我只是删除了强制转换的代码,是一样的
这个赋值非常明显__forwarding赋值的就是&age,sizeof就是当前的结构体有多大,就是赋值size,而且最下面那个框框,他自己定义的age会传给第二个参数&age,赋值给*__forwarding,所以__Block_byref_age_0它的地址就是*__forwarding,这个也非常清楚
再看一下block内部也会把&age这个值传给age这个值(这个就不截图了,直接可以看得到 GDblock block = ((void(*)())&__main_block_impl_0((void*)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_age_0 *)&age,570425344));就是这段代码找__main_block_impl_0实现就能看出赋值)
请看下面的代码:
这种会不会报错?答案是不会,因为我并没有修改它,我只是在用它.
相信到此大家都会清楚:block存储着这个结构体的内存地址的值,修改的时候,会通过内存地址找到这个结构体,然后修改这个值
到此为止:__block修饰符修饰的变量 为什么block能修改,相信已经很清楚
总结:
1.__block可以用于解决block内部无法修改auto变量值的问题
2.__block不能修饰全局变量、静态变量(static)(大家自己尝试,会报错)
3.编译器会将__block包装成一个对象
接下来可能有些人有点疑惑的就是如果我 NSLog(@"%p",&age);这个age的地址到底是哪个地址?如下
这个age到底是哪个的?
如下:
下面我们就来证明一下这个问题,这个思路我之前的博客有,就是把底层实现的结构体,之前拿过来用
从这里我们可以看出,它并不是block里面存储的age的地址值,这2个值并不是一样,我可以告诉大家,它访问的就是age里面那个age=10的那个age地址,你看下图,我来证明
首先你要知道:大age结构体的地址值,就是它第一个元素的地址值,所以0x0000000101cb92a0就是age结构体里面的_isa的地址.这个要清楚
这里很清楚的证明了,确实是age结构体里面的那个age的值