__block修饰符(四)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了__block修饰符(四)相关的知识,希望对你有一定的参考价值。
参考技术A 1、基本数据类型的auto局部变量
在block中无法修改基本数据类型的auto局部变量,修改时会报Variable is not assignable (missing __block type specifier)错误。block对基本数据类型的auto局部变量是值捕获,无法去修改外部的变量。
2、static修饰的基本数据类型局部变量
3、全局变量
在block中可以修改全局变量,因为全局变量在任何作用域都可以调用,block不会对其进行捕获。
4、对象局部变量
block对对象局部变量是指针捕获,会对其强引用,在内部可以修改变量而不报错
1、__block可以用于解决block内部无法修改auto变量值的问题
2、__block不能修饰全局变量、静态变量(static)
3、编译器会将__block变量包装成一个对象
2-3加了__block
2-4没加__block
从2-3中可以看到,加了__block后,内部age是一个__Block_byref_age_0对象
__Block_byref_age_0中包含了isa指针,所以变量为对象。
__forwarding是指向自己的指针
age是变量的值
block通过__forwarding指针去修改age的值
这里个Block里输出的age为20,因为加了__block修饰符后,编译器会将__block变量包装成一个对象,block对其是指针捕获,可以通知指针去修改值。
不加__block时,block是直接强引用的Person对象,但是现在是对__Block_byref_person_0进行了强引用。
__Block_byref_person_0内部才是对Person对象强引用,比没有__block多了__Block_byref_person_0这一步。__Block_byref_person_0内部还有一个__Block_byref_id_object_copy和__Block_byref_id_object_dispose,其作用跟block内部的copy和dispose一样,可以滑到上面看block的copy和dispose的讲解。
Block变量捕获详解(一)
Block的三种类型(二)
Block对象变量捕获(三)
Block循环引用(五)
Block存储域探析
接《Block截获自动变量实现与__block修饰符内部实现》我们继续探讨Block
留下的问题
- 1,
__Block_byref_i_0 *__forwarding
;这个指向自身的指针是什么鬼,有什么作用,什么时候用? - 2,Desc_0结构体中多出来的
void (*copy) void (*dispose)
这两个方法有什么作用,什么时候用?
Block 和 __block变量的实质
名称 | 实质 |
---|---|
Block | 栈上Block的结构体实例 |
__block变量 | 栈上__block变量的结构体实例 |
Block类型
struct __ViewController__viewDidLoad_block_impl_0 {
struct __block_impl impl;
struct __ViewController__viewDidLoad_block_desc_0* Desc;
__Block_byref_i_0 *i; // by ref
__ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
我们都知道isa指针是指向其class的指针,所以, impl.isa = &_NSConcreteStackBlock;
也就表示这个Block为_NSConcreteStackBlock
类型。即栈上的Block。
当然还有
类 | 设置对象的存储域 |
---|---|
_NSConcreteStackBlock | 栈 |
_NSConcreteGlobalBlock | 程序的数据区域(.data区) |
_NSConcreteMallocBlock | 堆 |
什么情况下Block是 _NSConcreteGlobalBlock 类型的
根据名称,global(全局)我们创建一个全局的Block,看一下他的编译结果
#import "ViewController.h"
//全局的Block
void (^blk)(void) = ^{NSLog(@"123");};
typedef void(^WxsBlock) ();
@interface ViewController ()
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
blk();
}
@end
编译后
struct __blk_block_impl_0 {
struct __block_impl impl;
struct __blk_block_desc_0* Desc;
__blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteGlobalBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
NSLog((NSString *)&__NSConstantStringImpl__var_folders_t9_g3xrsv653kz2gr7tmgwfbfvh0000gn_T_ViewController_fbc035_mi_0);}
static struct __blk_block_desc_0 {
size_t reserved;
size_t Block_size;
} __blk_block_desc_0_DATA = { 0, sizeof(struct __blk_block_impl_0)};
static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);
void (*blk)(void) = ((void (*)())&__global_blk_block_impl_0)
我们发现impl.isa = &_NSConcreteGlobalBlock;
我们再从block作为普通局部变量、函数返回值、函数参数时来看它的类型
有自动变量的截取
@property(nonatomic, assign) blk_t blockAssgin;
@property(nonatomic, strong) blk_t blockStrong;
@property(nonatomic, copy) blk_t blockCopy;
blk_t getBlk_t(int i) {
blk_t block4 = ^(int count){return count*i;};
NSLog(@"作为函数返回值,有自动变量截取---%@",block4);
return block4;
}
{
//作为普通变量, 有自动变量截取
blk_t block1 = ^(int count) {
return count * value;
};
NSLog(@"作为普通变量, 有自动变量截取---%@",block1);
//作为assign类型成员变量 ,有自动变量截取
self.blockAssgin = ^(int count) {
return count * value;
};
NSLog(@"作为assign类型成员变量 ,有自动变量截取---%@",self.blockAssgin);
//作为strong类型成员变量 ,有自动变量截取
self.blockStrong = ^(int count) {
return count * value;
};
NSLog(@"作为strong类型成员变量 ,有自动变量截取---%@",self.blockStrong);
//作为copy类型成员变量 , 有自动变量截取
self.blockCopy = ^(int count) {
return count * value;
};
NSLog(@"作为copy类型成员变量 , 有自动变量截取---%@",self.blockCopy);
//作为函数返回值,有自动变量截取
getBlk_t(value);
//作为函数参数,有自动变量截取
[self actionBlock:^int(int i) {
return i*value;
}];
}
- (void)actionBlock:(blk_t)block {
//作为函数参数,无自动变量截取
NSLog(@"作为函数参数,有自动变量截取---%@",block);
block(1);
}
NSLog 日志:
Block[47786:5956467] 作为普通变量,有自动变量截取---<__NSMallocBlock__: 0x60000024c9c0>
Block[47786:5956467] 作为assign类型成员变量,有自动变量截取---<__NSStackBlock__: 0x7fff5fbc39e0>
Block[47786:5956467] 作为strong类型成员变量,有自动变量截取---<__NSMallocBlock__: 0x608000056110>
Block[47786:5956467] 作为copy类型成员变量,有自动变量截取---<__NSMallocBlock__: 0x608000056620>
Block[47786:5956467] 作为函数返回值,有自动变量截取---<__NSMallocBlock__: 0x6080000565c0>
Block[47786:5956467] 作为函数参数,有自动变量截取---<__NSStackBlock__: 0x7fff5fbc3968>
但是通过clang编译出来的结果,他们全部同样全部都是impl.isa = &_NSConcreteStackBlock;
惊不惊喜?意不意外?
无自动变量的截取
{
//作为返回值,无自动变量截取
blk_t blockt = getBlk_t();
NSLog(@"作为返回值,无自动变量截取blockt---%@",blockt);
//作为普通变量,无自动变量截取,此处尝试了@property(strong,assgin,copy)这三种情况,是相同的结果
self.blocktest = ^(int count){return count;};
NSLog(@"作为普通变量,无自动变量截取----%@",self.blocktest);
//作为函数参数,无自动变量截取
[self actionBlock:^int(int i) {
NSLog(@"%d",i);
return i;
}];
}
- (void)actionBlock:(blk_t)block {
//作为函数参数,无自动变量截取
NSLog(@"作为函数参数,无自动变量截取---%@",block);
block(1);
}
打印结果
Block[47535:5919554] 作为返回值,无自动变量截取blockt---<__NSGlobalBlock__: 0x1039fd0f0>
Block[47535:5919554] 作为普通变量,无自动变量截取----<__NSGlobalBlock__: 0x1039fd150>
Block[47535:5919554] 作为函数参数,无自动变量截取---<__NSGlobalBlock__: 0x1039fd190>
全部是GlobalBlock
但是通过clang编译出来的结果,他们全部同样全部都是impl.isa = &_NSConcreteStackBlock;
惊不惊喜?意不意外?
通过clang的编译在ARC下只能看到静态情况下(静态代码声明)的Block类型,感觉只能作为参考,毕竟还有运行时。
所以我们可以根据NSLog的打印的结果简单总结一下:
Block类型 | 有无自动变量截取 | 使用情况 |
---|---|---|
_NSConcreteGlobalBlock | 有 | 当作全局变量声明并初始化 |
无 | 全部情况 | |
NSStackBlock | 有 | assgin类型的成员变量 |
作为函数参数 | ||
无 | 无 | |
_NSConcreteMallocBlock | 有 | 作为普通的局部变量 |
作为copy类型的成员变量 | ||
作为strong类型的成员变量 | ||
作为函数返回值 | ||
无 | 无 |
为什么_NSConcreteGlobalBlock类型的Block要设置在数据区域?
因为在使用全局变量的地方不会截获自动变量。因为它在一开始就已经定义好了。不存在”变量“,所以它的执行在任何时候都是固定的,不会依赖运行时的环境,这种类型的操作全局只有一个就可以,既节省空间又好管理。所以放在全局静态区的数据区域最好不过了。
通过clang编译和NSLog的对比的总结
通过clang编译的结果可知,除了在全局变量声明并创建的时候block被编译成_NSConcreteGlobalBlock
其他时候全部都是_NSConcreteMallocBlock
类型,我们不难看出,Block的初始状态只有这两种,所以Malloc状态的Block是从Stack状态转变过去的,这个猜想在看《Objective-C高级编程》的时候也和作者不谋而合。
那么我们就来探讨一下它是如何从栈被搞到堆中去的。
根据《Objective-C高级编程》的描述:
它是通过objc_retainBlock()
方法来把Block从栈搞到堆中的,
通过objc4运行库的runtime/objc-arr.mm 可知:
objc_retainBlock()
= _Block_copy
当一个Block被作为函数返回值时为什么会被搞到堆中,它被做了什么?
blk_t func(int rate) {
/*
通过Block语法生成Block,配置在栈上的结构体实例,
tmp被赋值
*/
blk_t tmp = &__func_block_impl_0(...);
/*
通过_Block_copy将其复制到堆上
复制后将堆上的地址作为指针复制给变量tmp
*/
tmp = _Block_copy(tmp);
/*
将堆上的Block作为Objective—C对象
注册到autoreleasepool中,返回该对象
*/
return objc_autoreleaseReturnValue(tmp);
}
在ARC中Block被复制的操作是编译器自己搞定的,但是编译器也不是万能的
当作为返回值的时候,编译器显然是可以搞定的
但是再作为函数参数的时候就不太好使了,这个时候需要我们手动的去copy Block,这一点通过“作为函数参数,有自动变量截取”验证实例得到了验证,它都是stack类型的
这里肯定会被问,为什么非得copy到堆上??在栈上就不行?
俩字:安全!
复制到堆上的__block变量和Block变量在变量作用域结束时不受影响
什么时候需要手动Copy
向方法或者函数的参数重传递Block时
但是如果在方法或者函数中适当的复制了传递来的参数,那么就不必在调用该方法或者函数前手动复制了,如一下方法:
1,Cocoa框架的方法且方法名字中有usingBlock等时
2,GCD的API
以上是关于__block修饰符(四)的主要内容,如果未能解决你的问题,请参考以下文章
阶段1 语言基础+高级_1-3-Java语言高级_02-继承与多态_第6节 权限修饰符_6_四种权限修饰符