一、block定义
- 概念
block是将函数及其上下文封装起来的对象。
block也是一个指针,保存的是一段代码块在内存中的空间 (栈内存)
//blcok定义:后者return_type可省,参数为void参数可省略
return_type (^blockName)(var_type) = ^return_type (var_type varName) {
// statements
};
blockName(var);
//利用typedef简化Block的声明:
typedef return_type (^BlockTypeName)(var_type);
- block的使用场景
- 把block保存到对象中,恰当时机的时候才去调用
- (void)block1
{
Person *p = [[Person alloc] init];
void(^block)() = ^() {
NSLog(@"执行对象中block");
};
p.operation = ^(){
NSLog(@"执行对象中block");
};
p.operation = block;
_p = p;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
_p.operation();
}
// block:ARC使用strong,非ARC使用copy
// block类型:void(^)()
@property (nonatomic, strong) void(^operation)();
- 把block当做方法的参数使用,外界不调用,都是方法内部去调用,Block实现交给外界决定.
- (void)block2
{
Person *p = [[Person alloc] init];
// 传入block给参数的Block赋值
[p eat:^{
NSLog(@"吃东西");
}];
}
//Person中eat方法
- (void)eat:(void (^)())block
{
block();
}
- 把block当做方法的返回值,目的就是为了代替方法.block交给内部实现,外界不需要知道Block怎么实现,只管调用
- (void (^)(int))run
{
return ^(int meter){
NSLog(@"跑了%d米",meter);
};
}
- (void)block3
{
Person *p = [[Person alloc] init];
p.run(2);
// void(^run)() = p.run;
//run();
}
二、block捕获变量
1. block使用外部变量,会将外界变量拷贝一份到堆内存(在给block块分配内存空间的时候),调用block之前修改变量,不影响block内的该变量取值。该block是不允许更改该变量的
- (void)test01
{
int a = 10;
void(^block)(int x) = ^(int x){
NSLog(@"== %d", a);//10, a的地址和外面a不同
//variable is not assignable (missing __block type specifier )不允许修改a的值
};
a = 20;
block(a);
}
2. __block的作用
- 基本类型
局部变量 : 在声明前加__block,可以直接读写该变量,是地址传递,会影响外界值。会否拷贝该值分为ARC、MRC二种情况
静态变量、全局变量:不会对原来的值进行copy,直接读写,地址不变
- (void)test01
{
//int sum = 5; //( variable is not assignable (missing __block type specifier ))
__block int sum = 5;
NSLog(@"1---%p --- %d", &sum, sum);
sum = 10;
void (^sumBlock) (int m, int n) = ^(int m, int n) {
sum = m + n;
NSLog(@"2---%p --- %d", &sum, sum);
};
sum = 15;
sumBlock(1, 2);
NSLog(@"3---%p --- %d", &sum, sum);
__block NSString *name = @"小明";
NSLog(@"1---%@---%p", name, &name);
void (^stringBlock) (void) = ^(void) {
name = @"小丽";
NSLog(@"2---%@---%p", name, &name);
};
name = @"ls";
stringBlock();
NSLog(@"3---%@---%p", name, &name);
}
//ARC
//1---0x7ffee12688a8 --- 5
// 2---0x60000118ced8 --- 3
// 3---0x60000118ced8 --- 3
// 1---小明---0x7ffee1268848
// 2---小丽---0x600001f8da68
// 3---小丽---0x600001f8da68
//MRC
//1---0x7ffee0d888a8 --- 5
//2---0x7ffee0d888a8 --- 3
//3---0x7ffee0d888a8 --- 3
//1---小明---0x7ffee0d88848
//2---小丽---0x7ffee0d88848
//3---小丽---0x7ffee0d88848
如果想在block内修改某局部变量需加__block. MRC 环境下block在使用过程中不会对原来值进行copy,可以直接修改该变量 ,ARC环境下会对原值进行copy,内存地址发生变化。
block可以直接修改全局和静态变量 ,不会copy该变量的值
- 指针类型的变量
- (void)test02
{
Person *people = nil;
people = [[Person alloc] init];
people.name = @"zhangsan";
NSLog(@"1---%@---%p--%@", people, &people, people.name);
void (^peopleBlock) (void) = ^(void) {
NSLog(@"2---%@---%p--%@", people, &people, people.name);
people.name = @"wangwu";
/*
people = [[Person alloc] init];
people.name = @"zhaoliu";
*/
};
people.name = @"lisi";
peopleBlock();
NSLog(@"3---%@---%p--%@", people, &people, people.name);
}
ARC、MRC不使用__block
//1---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--zhangsan
//2---<Person: 0x6000021ab3b0>---0x600002de3500--lisi
//3---<Person: 0x6000021ab3b0>---0x7ffeef9c28a8--wangwu
MRC使用__block
//1---<Person: 0x6000027ec760>---0x7ffeeec238a8--zhangsan
//2---<Person: 0x6000027ec760>---0x7ffeeec238a8--lisi
//3---<Person: 0x6000027ec760>---0x7ffeeec238a8--wangwu
ARC使用__block
//1---<Person: 0x6000034d8390>---0x7ffee03568a8--zhangsan
//2---<Person: 0x6000034d8390>---0x600003881018--lisi
//3---<Person: 0x6000034d8390>---0x600003881018--wangwu
不加__block:
MRC 和 ARC block内都是对(原来指针的copy),也就是有两个不同的指针,指向同一个对象。在block内可以更改对象的属性值,但是不可以更改对象
使用__block:
MRC环境block中不会对原来的指针进行copy,所以可以更改属性,也可以更改对象本身 。
ARC环境则是新增指针地址并赋值给原指针(对原对象的copy?内存地址也发生变化?)。
指针类型全局和静态变量block内可以直接修改,不会copy值和指针。
三、关键字__weak, __strong,copy
- __weak
- (void)test03
{
Person *p = [[Person alloc]init];
p.name = @"myObject";
NSLog(@"1---%@---%p--%@", p, &p,p.name);
__weak Person *weakObj = p;
NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
void(^testBlock)(void) = ^(){
NSLog(@"3---%@---%p--%@", weakObj, &weakObj,weakObj.name);
};
testBlock();
p = nil; // 这边值nil 用来判断block是否复制了对象
testBlock();
}
不使用__weak(另MRC 是没有__weak关键字的)
//1---<Person: 0x600000c20d50>---0x7ffee5e148a8--myObject
//2---<Person: 0x600000c20d50>---0x7ffee5e148a0--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject
//3---<Person: 0x600000c20d50>---0x60000002e240--myObject
使用__weak
//1---<Person: 0x6000037f8d90>---0x7ffeee15d8a8--myObject
//2---<Person: 0x6000037f8d90>---0x7ffeee15d8a0--myObject
//3---<Person: 0x6000037f8d90>---0x600003becb30--myObject
//3---(null)---0x600003becb30--(null)
不使用 __weak, p = nil 后block块内打印出的对象仍不为空,说明block中新增了强指针引用
使用 __weak p = nil 后person对象为nil ,说明block内新增了弱指针引用,对象释放后 weakObj 也不在持有, 并会被置nil 防止野指针报错。
//Capturing 'self' strongly in this block is likely to lead to a retain cycle
- (void)test04
{
__weak typeof(self)weakSelf = self;
self.block = ^(NSString *name){
NSLog(@"arr:%@", weakSelf);
// [self weakTest];
// p.name = @"haha";
};
self.block(@"123");
}
block在copy时都会对block内部用到的对象进行强引用(ARC)或者retainCount增1(非ARC)。在ARC与非ARC环境下对block使用不当都会引起循环引用问题 。
一般表现为,某个类将block作为自己的属性变量,然后该类在block的方法体里面又使用了该类本身,
简单说就是self.block = ^(Type var){
[self dosomething];
或者 self.otherVar = XXX;
或者 _otherVar = ...
};
block的这种循环引用会被编译器捕捉到并及时提醒
解决方法 __weak typeof(self)weakSelf = self;
- __strong
__weak 可能产生的问题: weakSelf 指针是没有对象持有权的,那么外部对象被提前释放了怎么办?block内部的执行岂不是会出错 ?这个问题又当如何解决呢? 神奇的 strong 关键字来了, 先看代码
- (void)test04
{
Person* p = [[Person alloc]init];
p.name = @"myObject";
NSLog(@"1---%@---%p--%@", p, &p,p.name);
__weak Person *weakObj = p;
NSLog(@"2---%@---%p--%@", weakObj, &weakObj,weakObj.name);
void(^testBlock)(void) = ^(){
__strong Person *strongObj = weakObj;
NSLog(@"3---%@---%p--%@", strongObj, &strongObj,strongObj.name);
NSLog(@"w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
};
testBlock();
p = nil;
testBlock();
}
//1---<Person: 0x600001002fb0>---0x7ffeee9d28a8--myObject
//2---<Person: 0x600001002fb0>---0x7ffeee9d28a0--myObject
//3---<Person: 0x600001002fb0>---0x7ffeee9d27c8--myObject
//w---<Person: 0x600001002fb0>---0x600001c48260--myObject
//3---(null)---0x7ffeee9d27c8--(null)
//w---(null)---0x600001c48260--(null)
发现 __strong 修饰的对象仍被置nil 了 怎么回事呢 ?? 接着看.....
- (void)test05
{
Person* p = [[Person alloc]init];
p.name = @"myObject";
NSLog(@"0p---%@---%p--%@", p, &p, p.name);
__weak Person *weakObj = p;
NSLog(@"0w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong Person *strongObj = weakObj;
NSLog(@"0s---%@---%p--%@", strongObj, &strongObj,strongObj.name); //先打印这行
sleep(3); // 睡眠三秒确保 p 被置 nil 后执行接下来的代码
NSLog(@"2w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
NSLog(@"2s---%@---%p--%@", strongObj, &strongObj,strongObj.name);
});
sleep(1); //睡眠1秒让异步线程block块执行
p = nil; //执行过程中将 p 对象置 nil
NSLog(@"1p---%@---%p--%@", p, &p, p.name);
NSLog(@"1w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
sleep(4); //异步线程结束后 再打印出 person 对象
NSLog(@"3w---%@---%p--%@", weakObj, &weakObj,weakObj.name);
}
//0p---<Person: 0x6000031ac320>---0x7ffee1aa68a8--myObject
//0w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//0s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//1p---(null)---0x7ffee1aa68a8--(null)
//1w---<Person: 0x6000031ac320>---0x7ffee1aa68a0--myObject
//2w---<Person: 0x6000031ac320>---0x600003de98e0--myObject
//2s---<Person: 0x6000031ac320>---0x70000f8fce08--myObject
//3w---(null)---0x7ffee1aa68a0--(null)
p = nil 后由__strong 修饰的对象仍然存在。在block执行过程中,如果对象用 __strong 修饰 block内部依然会继续强引用它 。__weak修饰的只要有强指针指向,会一直存在。上面的例子是因为下面的代码是后续执行的,所以打印出结果为 nil 。
block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。既避免了循环引用的问题,又可以在 block 内部持有该变量
我们平时在使用时,常常先判断 strongObj 是否为空,然后再执行后续代码,如下方式
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong Person *strongObj = weakObj;
if (strongObj) {
//
}
});
- copy:block变量定义时为什么用copy关键字
默认情况下,block是存档在栈中,可能被随时回收,故需要copy操作。这也就是我们在定义block的时候用得时copy (arc 下也可以用strong), 而不是weak等
block默认存储于栈区,访问外界对象,不会对对象retain。copy会转移至堆,访问外界对象,会对对象retain,使用__block修饰,则不会对对象retain。
使用copy修饰block是将block转移至堆,而不是copy一份,使用copy保住外界对象,避免使用时对象已释放。copy操作后需要在对象的dealloc下对block进行block_release.