经过上一篇文章对block本质的分析,咱们能够了解到,block的本质就是一个OC对象,拥有一个isa
指针,那么block就确定有本身的类型。上一篇文章中经过C++代码咱们也看到了,isa
指向一个&_NSConcreteStackBlock
,本文就继续分析block的类型,继承链等知识点。bash
block
有三种类型,能够经过isa
或者调用class
方法获取,最终都继承自NSObject
. 下面咱们经过class获取到block的类型函数
int main(int argc, const char * argv[]) {
@autoreleasepool {
// insert code here...
void(^myBlock)(void) = ^{
NSLog(@"this is a block ");
};
NSLog(@"%@",[myBlock class]);
NSLog(@"%@",[[myBlock class] superclass]);
NSLog(@"%@",[[[myBlock class] superclass] superclass]);
NSLog(@"%@",[[[[myBlock class] superclass] superclass] superclass]);
}
return 0;
}
复制代码
结果以下,也更加印证了block是一个OC对象
。post
block[12750:370967] __NSGlobalBlock__
block[12750:370967] __NSGlobalBlock
block[12750:370967] NSBlock
block[12750:370967] NSObject
复制代码
上面咱们分析出了block
都继承自NSBlock
,最终继承自NSObject
,下面继续看看block
的三种类型学习
void (^block1)(void) = ^{
NSLog(@"this is a block");
};
int age = 10;
void (^block2)(void) = ^{
NSLog(@"this is a block %d",age);
};
NSLog(@"%@ --- %@ --- %@",[block1 class],[block2 class],[^{
NSLog(@"this is a block %d",age);
} class]);
复制代码
输出: __NSGlobalBlock__ --- __NSMallocBlock__ --- __NSStackBlock__
可是咱们仍是须要编辑成C++代码再看一下isa指向的类型,就不贴代码了,直接说结果,都是_NSConcreteStackBlock;
测试
这里要说一下一切以运行时
的结果类型为准,由于clang这个命令编译的结果可能跟咱们最初写的代码有些出入,只能做为学习的参考。ui
一图胜前言 this
编写的代码都放在程序区域spa
全局变量通常放在数据区域3d
程序区域和数据区域 都是编译器自动处理的区域指针
堆:动态分配内存 好比[NSObject alloc] malloc(20) 须要开发者申请内存页须要本身管理内存
栈:局部变量 参数 系统自动分配内存,自动释放内存
NSGlobalBlock就放在数据区域,NSMallocBlock放在堆区 ,NSStackBlock放在栈区
那么咱们开发中写的不少block,咱们怎么知道他是什么类型的block呢?
下面总结几点判断的规则:
为何上面要强调一下copy操做呢,由于在栈上的block调用copy以后会从栈赋值到堆上,在堆上的block再copy,则会引用计数加1,而在数据区的NSGlobalBlock调用block则不会有任何操做。
上面内存分部中说过NSStackBlock作了copy操做就是NSMallocBlock
,在ARC下,系统会根据状况自动把栈上的block copy到堆上,那是什么状况系统会作这件事呢?下面举例说一说
typedef void (^MyBlock)(void);
MyBlock returnBlock() {
int age = 20;
//自动调用copy
return ^{
NSLog(@"----- %d",age);
};
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
MyBlock block = returnBlock();
block();
NSLog(@"%@",[block class]);//NSMallocBlock
}
return 0;
}
复制代码
根据咱们上面分析的结果,调用了auto变量,是一个NSStackBlock
,可是最后打印的结果是一个NSMallocBlock
,说明编译器帮咱们作了一次copy
操做,固然在ARC下咱们也不须要关心release
,在做用域结束,编译器也帮咱们作了release
操做。
int main(int argc, const char * argv[]) {
@autoreleasepool {
int age = 20;
MyBlock block = ^{
NSLog(@"----- %d",age);
};
block();
NSLog(@"%@",[^{
NSLog(@"----- %d",age);
} class]);//NSStackBlock
NSLog(@"%@",[block class]); //NSMallocBlock
}
return 0;
}
复制代码
NSArray *array = [NSArray array];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
复制代码
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
<#code to be executed once#>
});
复制代码
总结一下:经过表面看本质,栈区
的block是不会对外面的变量执行retain
操做的(MRC),或者说不会对外面变量发生强引用
(ARC),只有堆区
的block才有可能持有变量。
说到__block
这个修饰符,你们开发中必定是用过不少次了,我们先从为何要用开始提及,再说__block
加了这个修饰词以后到底发生了什么。
为何要用__block
呢?最多见的一个使用就是在block里面
想修改block外部
的局部变量,前面咱们也已经分析过了,auto变量是经过值传递
,被记录在block的结构体中的,并且是跨函数访问,咱们如何能在block执行的函数中,修改另一个函数中的auto变量呢,固然是改不了的。而后咱们又知道了只要用__block
修饰这个想改的auto变量
,咱们就能够在block中修改他的值了,那么这里就能够想想__block
这个修饰词究竟是作了什么呢?也许他就是把以前的值传递
改为了地址传递
呢?
想要修改auto变量,咱们先说一种不是使用__block
的方法
static修饰
前面咱们也分析过static修饰的变量被block捕获是地址传递,只要是地址传递咱们能拿到地址,就能够根据地址修改对应内存中的内容,能够确定,static修饰,是能够修改auto变量的全局变量
就更不用说了,谁均可以改,都不用经过block去捕获__block
除了上面两种方法(可能会有内存问题,咱们通常不用),__block才是咱们最优的一种解决方法仍是经过clang编译成C++代码区分析__block
的原理,先写一段测试代码
int main(int argc, const char * argv[]) {
@autoreleasepool {
__block int age = 20;
MyBlock block = ^{
age = 33;
NSLog(@"----- %d",age);
};
block();
}
return 0;
}
复制代码
__block int age = 20;
最终被编译成了
__attribute__((__blocks__(byref))) __Block_byref_age_0 age = {(void*)0,(__Block_byref_age_0 *)&age, 0, sizeof(__Block_byref_age_0), 20};
复制代码
其实这里能够理解为,添加了__block修饰以后的age,被编译成了一个__Block_byref_age_0
对象
struct __Block_byref_age_0 {
void *__isa;
__Block_byref_age_0 *__forwarding;
int __flags;
int __size;
int age;
};
复制代码
其中__forwarding被赋值了&age,也就是__Block_byref_age_0
对象自己的地址,age的值(20)也被存储在了这个对象中。而后就是block中修改age的值,并打印age,使用age->__forwarding->age
取到age的值
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
__Block_byref_age_0 *age = __cself->age; // bound by ref
(age->__forwarding->age) = 33;
NSLog((NSString *)&__NSConstantStringImpl__var_folders_86_ljphgvys0hzg8hfxdtv5q5gm0000gn_T_main_76267f_mi_0,(age->__forwarding->age));
}
复制代码
到这里咱们应该已经清楚了,为何__block修饰的变量在block中能够被修改,其实说白了,仍是变相的实现了地址的传递,只有拿到地址才能够改变相应地址指向的内存区域中的数据。
下面再说一个block中使用array
的小问题:
addObject
编译不会报错,可是给
array
赋值成
nil
就报错,这是为何呢?
先说赋值nil
报错,根据上面的分析,由于咱们并无用__block
修饰array,block中不能修改auto对象,因此报错了。
再说addObject
为何不会报错,由于addObject其实并非去改变array的值,只是去操做array
,而block只是不能修改auto变量,并非不能操做变量,因此不会报错。