block

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 显然是一个循环引用
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容