block

作者: iChuck | 来源:发表于2018-03-15 13:52 被阅读1次

    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 显然是一个循环引用
    

    相关文章

      网友评论

          本文标题:block

          本文链接:https://www.haomeiwen.com/subject/askhqftx.html