1.block的语法
1.1 标准声明和定义
返回类型(^block名称)(参数类型) = ^返回类型(变量类型 变量名称){实现}
直接定义block时,可以省略定义时的返回类型,即
返回类型(^block名称)(参数类型) = ^(变量类型 变量名称){实现}
若参数类型为void,可省略写成
返回类型(^block名称)(void) = ^{实现}
匿名block:block定义时,等号右边的即为匿名block
1.2 typedef简化block的声明
typedef 返回类型(^block名称)(参数类型);
2. block中变量的访问
2.1 block访问局部变量
int num = 10;
void (^myBlock)(void) = ^{
NSLog(@"num的值为%d", num);
};
num = 15;
myBlock();
输出结果为10,若在block中修改num的值,则会报错。
把上述代码转为c++代码,转换步骤:
在终端cd当文件所在目录,在终端输入命令 clang -rewrite-objc 文件名
转换前的代码
@implementation WYYBlock
- (void)test{
int num = 10;
void (^myBlock)(void) = ^{
NSLog(@"num的值为%d", num);
};
num = 15;
myBlock();
}
@end
转换后的代码与block相关的如下:
struct __WYYBlock__test_block_impl_0 {
struct __block_impl impl;
struct __WYYBlock__test_block_desc_0* Desc;
int num;
__WYYBlock__test_block_impl_0(void *fp, struct __WYYBlock__test_block_desc_0 *desc, int _num, int flags=0) : num(_num) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __WYYBlock__test_block_func_0(struct __WYYBlock__test_block_impl_0 *__cself) {
int num = __cself->num; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bh_3br_11pn5hxckj2qxzhfb1c80000gp_T_WYYBlock_00083b_mi_0, num);
}
static struct __WYYBlock__test_block_desc_0 {
size_t reserved;
size_t Block_size;
} __WYYBlock__test_block_desc_0_DATA = { 0, sizeof(struct __WYYBlock__test_block_impl_0)};
static void _I_WYYBlock_test(WYYBlock * self, SEL _cmd) {
int num = 10;
//声明定义block
void (*myBlock)(void) = ((void (*)())&__WYYBlock__test_block_impl_0((void *)__WYYBlock__test_block_func_0, &__WYYBlock__test_block_desc_0_DATA, num));
num = 15;
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
}
定义声明block转换后的代码是下面的内容
void (*myBlock)(void) = ((void (*)())&__WYYBlock__test_block_impl_0(
(void*)__WYYBlock__test_block_func_0, &__WYYBlock__test_block_desc_0_DATA, num)
);
可以看出来,block变量实际上是一个指向结构体__WYYBlock__test_block_impl_0的指针,而结构体的第三个元素是局部变量num的值
__WYYBlock__test_block_impl_0中,WYYBlock是该文件的类名,test是block所在方法的名称。
结构体的第一个变量是(void*)__WYYBlock__test_block_func_0,在该方法中可以看到,在方法内部有一个参数num接受了外面的num
基本数据类型会直接把值传递过来,所以num的值在block定义完时就已经固定,在定义之后无法在进行修改。
2.2block中访问__block修饰的局部变量
把OC代码修改为
- (void)test{
__block int num = 10;
void (^myBlock)(void) = ^{
NSLog(@"num的值为%d", num);
};
num = 15;
myBlock();
}
可以看到转换后,block的声明和定义变为
void (*myBlock)(void) = ((void (*)())&__WYYBlock__test_block_impl_0(
(void *)__WYYBlock__test_block_func_0, &__WYYBlock__test_block_desc_0_DATA, (__Block_byref_num_0 *)&num, 570425344)
);
可以看到原来的第三个参数num,变成了*(__Block_byref_num_0 )&num,传入的是num的引用,所以此时定义block后改变num的值,调用block时,num的值会相应改变,也可以在block中改变num的值。
同时,在结构体__WYYBlock__test_block_impl_0中第三个变量也由原来的 int num;变成了 __Block_byref_num_0 *num;
总结:block在定义后,局部变量的值改变不会改变block中局部变量的值,并且在block中局部变量的值无法修改。因为在Block定义时便是将 "局部变量的值" 传给Block变量所指向的结构体,因此在调用Block之前对局部变量进行修改并不会影响Block内部的值。如果要在block中修改局部变量的值,或者局部变量的值会随时修改,局部变量需要用__block修饰
2.3block内访问全局变量
把OC代码修改为
int num = 10;
- (void)test{
void (^myBlock)(void) = ^{
NSLog(@"num的值为%d", num);
};
num = 15;
myBlock();
}
运行结果为15,同时在block中修改num的值也不会报错
把当前代码编译成C++文件后可以发现,声明定义block时并没有再传入num的值或者指针
void (*myBlock)(void) = ((void (*)())&__WYYBlock__test_block_impl_0(
(void *)__WYYBlock__test_block_func_0, &__WYYBlock__test_block_desc_0_DATA) );
__WYYBlock__test_block_impl_0结构体中也没有了num,所以说明全局变量不会再传入block中,当调用block时需要使用全局变量时是从类的空间中获取的,所以调用block时获取到的值是num最后的值,在block中也能修改num的值。
全局变量所占用的内存只有一份,供所有函数共同调用,在Block定义时并未将全局变量的值或者指针传给Block变量所指向的结构体。
2.4 block内访问静态变量
把OC代码修改为
- (void)test{
static int num = 10;
void (^myBlock)(void) = ^{
NSLog(@"num的值为%d", num);
};
num = 15;
myBlock();
}
运行结果为15,同时在block中修改num的值也不会报错
把当前代码编译成C++文件后可以发现,声明定义block时传入的是num的指针
void (*myBlock)(void) = ((void (*)())&__WYYBlock__test_block_impl_0(
(void *)__WYYBlock__test_block_func_0, &__WYYBlock__test_block_desc_0_DATA, &num));
2.5block内访问类型为对象的局部变量
有一个WYYPerson类,类中有一个int类型的age属性,OC代码如下
WYYPerson *person = [[WYYPerson alloc] init];
person.age = 10;
void (^myBlock)(void) = ^{
NSLog(@"%d", person.age);
};
person.age = 18;
myBlock();
运行结果为18,因为局部对象的类型为对象时,对象传入的是引用
编译成C++后的文件内容
//block中的代码
static void __WYYBlock__test_block_func_0(struct __WYYBlock__test_block_impl_0 *__cself) {
WYYPerson *person = __cself->person; // bound by copy
NSLog((NSString *)&__NSConstantStringImpl__var_folders_bh_3br_11pn5hxckj2qxzhfb1c80000gp_T_WYYBlock_90d3f3_mi_0, ((int (*)(id, SEL))(void *)objc_msgSend)((id)person, sel_registerName("age")));
}
//block的定义和实现
void (*myBlock)(void) = ((void (*)())&__WYYBlock__test_block_impl_0(
(void *)__WYYBlock__test_block_func_0, &__WYYBlock__test_block_desc_0_DATA, person, 570425344));
从block的定义和实现可以知道,传入的是person对象的指针,从block中的代码可以知道,当获取person的值是通过给person对象发送消息获取age的值的,所以在block中也可以修改person的值
把一个类改为在MRC模式下的步骤:
Build phases -> Compile Sources -> 双击需要改成MRC模式的类会弹出输入框 -> 把-fno-objc-arc复制粘贴进去
3.block的三种类型
1.NSGlobalBlock:全局的静态 block,在block中不访问外部局部变量,可以访问外部全局变量和静态变量。此时为NSGlobalBlock。
2.NSStackBlock :保存在栈中的 block,当函数返回时会被销毁。
3._NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0 时会被销毁。
3.1 在MRC和ARC两种情况下,block内部不使用局部变量,在block中使用全局变量和静态变量,block是NSGlobalBlock
在block中不访问外部局部变量,代码如下
void (^myBlock)(void) = ^{
NSLog(@"不访问外部局部变量");
};
myBlock();
NSLog(@"%@", myBlock);
打印结果为
不访问外部局部变量
<NSGlobalBlock: 0x100f182c0>
在MRC模式下打印结果相同
在MRC模式下在block内部访问静态变量,block也是NSGlobalBlock。
代码
static int num = 10;
void (^myBlock)(void) = ^{
NSLog(@"%d", num);
};
myBlock();
NSLog(@"%@", myBlock);
结果:<NSGlobalBlock: 0x1006142c0>
在MRC模式下在block内部访问全局变量,block也是NSGlobalBlock。
代码:
int num = 10;
- (void)test{
void (^myBlock)(void) = ^{
NSLog(@"%d", num);
};
myBlock();
NSLog(@"%@", myBlock);
}
结果:<NSGlobalBlock: 0x1045742c0>
总结:
在ARC下,block内部不使用局部变量,在block中使用全局变量和静态变量,block的类型都是NSGlobalBlock。
在MRC下,block内部不使用局部变量,在block中使用全局变量和静态变量,block的类型都是NSGlobalBlock。
3.2 在MRC情况下会存在栈block
在ARC模式下没有NSStackBlock,必须在MRC下才能看到, 代码如下:
int num = 10;
void (^myBlock)(void) = ^{
NSLog(@"%d", num);
};
myBlock();
NSLog(@"%@", myBlock);
打印结果为:<NSStackBlock: 0x16ba85328>
3.3 在MRC下分别用assign,retain,strong,weak,copy修饰block
如果是全局block的话,不管用什么修饰词,仍然是全局block,此处不再做代码验证,我自己已经打印过结果,下面只验证在MRC下使用局部变量,本来是栈block
用assign关键字修饰block,完整代码:
@interface WYYBlock ()
@property (nonatomic, assign) void (^myBlock)(void);
@end
@implementation WYYBlock
- (void)test{
int num = 10;
self.myBlock = ^{
NSLog(@"%d", num);
};
self.myBlock();
NSLog(@"%@", self.myBlock);
}
- (void)dealloc{
NSLog(@"WYYBlock的dealloc方法被调用");
[super dealloc];
}
- (void)test2{
NSLog(@"%@", self.myBlock);
}
@end
在外面先调用test方法,再调用test2方法,在执行test2中的代码NSLog(@"%@", self.myBlock);,崩溃。说明在栈中的block出栈之后即会被销毁,此时block为栈block。
用retain关键字修饰block
@property (nonatomic, retain) void (^myBlock)(void);
结果与用assign修饰时相同,仍然为栈block,在执行test2中的代码*NSLog(@"%@", self.myBlock);*,崩溃
用weak关键字修饰block
@property (nonatomic, weak) void (^myBlock)(void);
结果与用assign修饰时相同,仍然为栈block,在获取myBlock时崩溃,造成了野指针
用strong关键字修饰block
@property (nonatomic, strong) void (^myBlock)(void);
打印结果为
<__NSMallocBlock__: 0x280588a20>
<__NSMallocBlock__: 0x280588a20>
此时block变成了堆block,不再随方法结束而销毁,所以可以第二次进行调用
用copy关键字修饰block
@property (nonatomic, copy) void (^myBlock)(void);
结果与用strong修饰时相同,block变成了堆block
由以上结果可以得到结果,在MRC模式下,当用copy和strong修饰时,block会被存放在堆中,不会因为所在方法结束而销毁block。
3.4 在ARC下分别用assign,retain,strong,weak,copy修饰block
如果是全局block的话,不管用什么修饰词,仍然是全局block,此处不再做代码验证,我自己已经打印过结果,下面只验证在ARC下使用局部变量,本来是堆block
用assign关键字修饰block
@property (nonatomic, assign) void (^myBlock)(void);
此时会变成栈block,当在test2,再次调用block时,会崩溃,因为block已经被销毁
用weak修饰时也会变成栈block,并且编译器会提示使用过后会销毁
用retain,strong,copy修饰时都仍然是堆block。
用retain修饰时会提示警告
Retain'ed block property does not copy the block - use copy attribute instead
提示,retain不会赋值block,推荐使用copy代替
总结:
1.block内部没有调用外部局部变量,或者调用了全局变量或者静态变量时,在MRC和ARC时都在全局区。
2.在ARC下,block调用了外部局部变量,系统默认会对block进行copy操作,也在堆区。在MRC下block调用了外部局部变量,会在栈区,所以此时修饰block要使用strong或者copy,把block放在堆区,这样block才不会随时被销毁,造成调用的时候已经被销毁的情况。
3.block造成循环引用的原因:block内部是一个结构体,会捕获外部传入的局部变量,保存在block的结构体中,如果在block中使用self,block又被self持有,此时会造成循环引用
4.block用weakSelf时,为什么在block内部又要声明一个strongSelf?
如果只用weakSelf打破循环引用的话,block是没有持有控制器的,当控制器被pop掉的时候,控制器就已经被销毁了,可能此时block被调用,可是控制器已经被销毁了,会引起崩溃,所以需要在block中在强引用self
5.strongSelf对weakSelf强引用,weakSelf对self弱引用,最终不也是对self进行了强引用,会导致循环引用吗不会的,因为strongSelf是在block里面声明的一个指针,当block执行完毕后,strongSelf会释放,这个时候将不再强引用weakSelf,所以self会正确的释放。
__weak __typeof__(self) weakSelf = self;
__strong __typeof(self) strongSelf = weakSelf;