block(块)的本质是什么?
在本质上是一个指向结构体的指针,能将函数作为变量、参数来传递。
带block参数的函数
typedef void (^ testBlock) ( NSString *name1,NSString *name2);
返回值类型 (^变量名) (参数列表)
block作为函数的参数
-(void)testBlock: (void (^) (NString * message)) arguBlock;
有返回值的block
int (^testBlock)(int) = ^(int n) {
return n * 2;
};
int a = testBlock();
block通俗点儿讲就是是一个代码块、能够读取其它函数内部变量的函数。viewDidLoad
函数中的myBlock函数
- (void)viewDidLoad {
[super viewDidLoad];
__block int a =10;
self.myBlock = ^{
a = 90;
NSLog(@"%d",a );
};
NSLog(@"%d",a );
}
block多用于参数传递, 代替代理方法, (有多个参数需要传递或者多个代理方法需要实现还是推荐使用代理方法), 少时用于当做返回值传递.
你定义完block之后,其实是创建了一个函数,在创建结构体的时候把函数的指针一起传给了block,所以之后可以拿出来调用。
block中那些不需要使用 weakSelf
- 在block不是作为一个property的时候,可以在block里面直接使用self,以下几种block是不会造成循环引用的
1、比如UIView的animation动画block。
[UIView animateWithDuration:1.0 animations:^{
//这里只有block对self进行了一次强引用,属于单向的强引用,没有形成循环引用
[self doSomething];
}];
2、大部分GCD方法
dispatch_async(dispatch_get_main_queue(), ^{
[self doSomething];
});
//因为self并没有对GCD的block进行持有,没有形成循环引用。目前我还没碰到使用GCD导致循环引用的场景,
//如果某种场景self对GCD的block进行了持有,则才有可能造成循环引用。
3、block并不是对象的属性 / 变量,而是方法的参数 / 临时变量
- (void)viewDidLoad {
[super viewDidLoad];
//self调用testWithBlock方法
[self testWithBlock:^{
//block内调用self的doSomeThing方法
[self doSomeThing];
}];
}
//block作为方法的参数
- (void) testWithBlock:(void(^)())block {
block();
}
-(void)doSomeThing{
NSLog(@"调用");
}
//这里因为block只是一个临时变量,self并没有对其持有,所以没有造成循环引用
block 中 __weak与__strong使用
1、 当block被声明为一个property的时候,在 block 内如果需要访问 self 的方法、变量,需要在block里面使用weakSelf,这是因为在block中调用 self 会引起循环引用(在block执行完后,将block置nil,这样也可以打破循环引用,这样做的缺点是,block只会执行一次,因为block被置nil了,要再次使用的话,需要重新赋值,不然就crash
) 。
weakSelf其原理就是__weak 修饰的变量,当对象销毁的时候,指针变量会自动置为Nil,这样就不会一直循环引用,导致不能释放。如果在内部对象需要强用,可以再用__strong 修饰。
//声明一个block
@property (nonatomic,copy) void(^myBlock)(void);//用assign 会crash
- (void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self;
self.myBlock = ^{
[weakSelf doSomeThing];
//会发生循环引用
// [self doSomeThing]; 会给出警告的
};
//调用block
self.myBlock ();
}
-(void)doSomeThing{
NSLog(@"调用");
}
2、当在并发执行的时候,我们在block内又要使用__strong 来修饰这个weakSelf。在block中需要对weakSelf进行__strong修饰,是因为要保证代码在执行到block中时,self不会被释放,当block执行完后,会自动释放该strongSelf。
举个栗子🌰:
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
[weakSelf doSomething];
[weakSelf doOtherThing];
});
上面的代码在 doSomething 中,weakSelf 不会变成 nil,不过在 doSomething 执行完成,调用第二个方法 doOtherThing 的时候,weakSelf 有可能被释放,于是,strongSelf 就派上用场了:
__weak __typeof__(self) weakSelf = self;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong __typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doOtherThing];
.........
.........
.........
});
注意: 在 Block 内需要多次 访问 self,则需要使用 strongSelf,如果没有 strongSelf 的那行代码,self 可能被释放掉了,这样很可能造成逻辑异常。如果这个 block 执行到一半时 self 释放,那么多半情况下会 Crash.
__block修饰外部变量
- block内不允许修改外部变量的值,这里所说的外部变量的值,指的是栈上指针的内存地址。__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中,这样在block内部也可以修改外部变量的值。
Apple这样设计,应该是考虑到了block的特殊性,block也属于“函数”的范畴,变量进入block,实际就是已经改变了作用域。在几个作用域之间进行切换时,如果不加上这样的限制,变量的可维护性将大大降低。又比如我想在block内声明了一个与外部同名的变量,此时是允许呢还是不允许呢?只有加上了这样的限制,这样的情景才能实现。
1、Block表达式可截获所使用的外部变量的值,保存外部变量的瞬间值,因为是“瞬间值”,所以声明Block之后,即便在Block外修改外部变量的值,也不会对Block内截获的外部变量值产生影响。
int a = 10;
void (^myBlock)(void) = ^{
NSLog(@"block内部, a = %d", a);
};
a = 20;
myBlock();//打印: block内部, a = 10
NSLog(@"a = %d", a);//打印:a = 20
2、外部变量截获的值为Block声明时刻的瞬间值,保存后就不能改写该值,如需对外部变量进行重新赋值,需要在变量声明前附加__block说明符,这时该变量称为__block变量。
__block int a = 10;//a为__block变量,可在block中重新赋值
void (^myBlock)(void) = ^{
NSLog(@"block内部, a = %d", a);
};
a = 20;
myBlock();//打印: block内部 a = 20
NSLog(@"a = %d", a);//打印:a = 20
3、当外部变量值为一个对象情况,且没有使用__block修饰时,虽然不可以在Block内对该变量进行重新赋值,但可以修改该对象的属性。
如果该对象是个Mutable的对象,例如NSMutableArray,则还可以在Block内对NSMutableArray进行元素的增删:
NSMutableArray *array = [[NSMutableArray alloc] initWithObjects:@"one", @"two",nil ];
void (^myBlock)(void) = ^{
[array addObject:@"three"];//修改
//array = [NSNSMutableArray array];//没有__block修饰,报错
};
myBlock();
NSLog(@"修改后的array:%@", array);
*打印结果*
修改后的array:(
one,
two,
three
)
为什么用了__block修饰就可以在block内修改外部变量呢?
如果在block中直接修改变量的值,它实质上会转化成,新变量 -> __forwarding —> 原变量同类型变量
。 所以最终修改的其实是结构体中原变量同类型变量,而这个变量明显已经不属于block的外部变量了,所以是在block中是可以修改的。
此时,分析到这里,还是有两个疑问:这个新变量也是非静态局部变量,block执行的时候,新变量可能已经被栈回收,如果block执行时,新变量也已经被释放的话,程序是会crash的,其实就算用了__block也不能解决这个问题,或者说__block 和这种情况似乎也没有什么联系吧!日常开发中,好像很少遇到这种crash啊?因为实际开发中遇到的block大多数都已经copy到了堆上面,block在copy的时候,也会触发这个__block变量的copy,会将变量从栈空间copy 到堆空间,所以block在执行的时候,使用的是堆空间上相应的变量,因而不会产生crash。
__forwarding的作用是啥?
在使用__block变量时经转换后,其实都是通过其__forwarding来访问的从现象结果来看,如果在block中修改了__block变量,block外修改亦有效,其实这也是__forwarding的功效。详细看:
《黑幕背后的__block修饰符》
《深入研究Block捕获外部变量和__block实现原理》
block声明使用copy说明
block声明使用copy,是在MRC中延续下来的,在MRC下,方法内部的block是存放在栈区,使用copy会将block拷贝到堆区。在ARC下编译器会自动对block进行copy,因此我们使用copy或者strong的效果是一样的。但是我们在ARC下继续使用copy可以提醒我们编译器会自动帮我们实现copy的操作。
官方文档指出,复制到堆区的主要目的就是保存block的状态,延长其生命周期。因为block如果在栈上的话,其所属的变量作用域结束,该block就被释放掉,block中的__block变量也同时被释放掉。为了解决栈块在其变量作用域结束之后被释放掉的问题,我们就需要把block复制到堆中。
__block和__weak修饰符的区别?
1、__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,也可以修饰基本数据类型
2、__weak只能在ARC模式下使用,只能修饰对象(NSString),不能修饰基本数据类型
3、__block修饰的对象可以在block中被重新赋值,__weak修饰的对象不可以
网友评论