block 的底层实现
- 首先看四个函数
void test1() {
int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); // 10
}
void test2() {
__block int a = 10;
void (^block)() = ^{
NSLog(@"a is %d", a);
};
a = 20;
block(); // 20
}
void test3() {
static int a = 10;
void (^block) () = ^{
NSLog(@"a is %d", a);
};
a = 20;
block; // 20
}
int a = 10;
void test4() {
void (^block) () = ^{
NSLog(@"a is %d", a);
};
a = 20;
block; // 20
}
- 造成这样的原因是:传值和传址。
block 的定义
<#returnType#>(^<#blockName#>)(<#parameterTypes#>) = ^(<#parameters#>) {
<#statements#>
};
block 内存管理
- 无论当前是 ARC 还是 MRC,只要 block 没有访问外部变量,block 始终在全局区
- MRC 情况下
- block 如果访问外部变量,block 在栈里
- 不能对 block 使用retain,否则不能保存在堆里
- 只有使用 copy,才能放在堆里
- ARC 情况下
- block 如果访问外部变量,block 在堆里
- block 可以使用 copy 和 strong,并且 block 是一个对象
block 的循环引用
- 如果要在 block 直接使用外部强指针会发生错误。使用以下代码可以解决 __weak typeof(self) weakSelf = self;
- 但是如果在 block 内部使用延时操作还使用弱指针的话会取不到该弱指针,需要在 block 内部再讲弱指针强引用以下 __strong typeof(self) strongSelf = weakSelf;
retain cycle 例子
block 中循环引用:一个 viewcontroller
@property (nonatomic, strong) HttpRequestHandler *handler;
@property (nonatomic, strong) NSData *data;
_handler = [HttpRequestHandler sharedManager];
[_handler downloadData:^(id responseData){
_data = responseData;
}];
self 拥有 handler,handler 拥有 block,block 拥有 self(因为使用了 self 的_data 属性,block 会 copy 一份 self)
解决方法:
__weak typeof(self) weakSelf = self;
[_handler downloadData:^(id responseData){
weakSelf.data = responseData;
}];
block 中的 weakSelf,是任何时候都需要加的吗?
- 不是任何时候都需要加的。不过任何时候都添加总是好的。只要出现 self->block->self.property/self->_ivar 这样的结构链的时,才会出现循环引用。
通过 block 来传值
1. 创建一个 ViewController,在.h 文件里声明一个 block 属性
@interface BlockViewController : ViewController
@property (nonatomic, copy) void (^ valueBlock) (NSString *str);
@end
2. 在.m 文件中实现方法
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
if (self.valueBlock) {
_valueBlock(@"abc");
}
}
3. 在需要的时候调用方法
BlockViewController *block = [[BlockViewController alloc] init];
block.valueBlock = ^(NSString *str) {
NSLog(@"blockViewController %@", str);
};
[self presentViewController:block animated:YES completion:nil];
block 作为一个参数使用
1. 在.h 中声明方法
- (void)setText:(void (^)(NSString *str))block;
2. 实现该方法
- (void)setText:(void (^)(NSString *))block {
block(@"123");
}
3. 调用方法
BlockViewController *block = [[BlockViewController alloc] init];
[self presentViewController:block animated:YES completion:nil];
[block setText:^(NSString *str) {
NSLog(@"blockViewController setText %@",str);
}];
block 作为返回值
- masonry 框架中我们就可以看到用法 make.top.equalTo(superview.mas_top).with.offset(padding.top); 这个方法就是将 block 作为返回值使用
- 分析代码可以看出:make.top, make.equalTo, makeWith, make.offset,所以可以得出一个结论就是 make.top 返回了一个 make,才能实现 make.top.equalTo
1. 在.h 中声明方法
- (BlockViewController * (^)(int))add;
2. 实现该方法
- (BlockViewController *(^)(int))add {
return ^(int a){
_result += a;
return self;
};
}
3. 调用方法
BlockViewController *block = [[BlockViewController alloc] init];
[self presentViewController:block animated:YES completion:nil];
block.add(10).add(20).add(30);
tip:当一个函数没有参数的,可以吧这个函数看做 get 方法使用点语法来调用
block 变量传递
- 如果 block 访问的外部变量时局部变量,那么就是值传递,外界改变了,不会影响里面
- 如果 block 访问的外部变量时__block 或者 static 修饰,或者是全局变量,那么就是指针传递,block 里的值和外界的同一个变量,外界改变,里面也会改变。
使用 block 有什么好处?使用 NSTimer 写出一个使用 block 显示(在 UILabel 上)秒表的代码。
- block 的好处,最直接的代码紧凑,传值、回调都很方便,省去了写代理的很多代码。
// YYKit 的一段 timer 封装成 block 回调的代码
+ (void)_yy_ExecBlock:(NSTimer *)timer {
if ([timer userInfo]) {
void (^block)(NSTimer *timer) = (void (^)(NSTimer *timer))[timer userInfo];
block(timer);
}
}
+ (NSTimer *)scheduledTimerWithTimeInterval:(NSTimeInterval)seconds block:(void (^)(NSTimer *timer))block repeats:(BOOL)repeats {
return [NSTimer scheduledTimerWithTimeInterval:seconds target:self selector:@selector(_yy_ExecBlock:) userInfo:[block copy] repeats:repeats];
}
// 使用
NSTimer *timer = [NSTimer scheduledTimerWithTimeInterval:1.0 block:^() {
weakSelf.secondsLabel.text = ...
} repeats:YES];
[[NSRunLoop currentRunLoop] addTimer:timer forMode:NSRunLoopCommonModes];
block 和函数很像
- 可以保存代码
- 有返回值
- 有形参
- 调用方式是一样的
使用系统的 block api(如 UIView的 block 动画)是否考虑循环引用的问题?
- 系统的某些 block api中,UIView 的 block版本动画时不需要考虑,但是也有一些 API 需要考虑。所谓的“引用循环”就是指双向的强引用,所以那些“单向的强引用”(block 强引用 self)没有问题。例如:
[UIView animateWithDuration:1 animations:^{
[self.view layoutIfNeeded];
}];
NSOperationQueue *queue = [NSOperationQueue mainQueue];
[queue addOperationWithBlock:^{
self.view = [[UIView alloc] init];
}];
[[NSNotificationCenter defaultCenter] addObserverForName:@"NSNotificationName" object:nil queue:queue usingBlock:^(NSNotification * _Nonnull note) {
self.view = [[UIView alloc] init];
}];
- 如果你使用一些参数中可能包含成员变量的 API 的时候,如 GCD、NSNotificationCenter 就要小心一点。如果 GCD 内部引用了 self,而且 GCD 的其他参数是成员变量,则需要考虑循环引用的问题。
__weak typeof(self) weakSelf = self;
dispatch_group_async(group, queue, ^{
__weak typeof(self) strongSelf = weakSelf;
[strongSelf doSomething];
});
__weak typeof(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"NSNotificationName" object:nil queue:queue usingBlock:^(NSNotification * _Nonnull note) {
__weak typeof(self) strongSelf = weakSelf;
[strongSelf dismissViewControllerAnimated:YES completion:nil];
}];
self->_observer->block->self 显然是一个循环引用
网友评论