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

相关文章

  • iOS开发之Block原理探究

    Block概述 Block本质 Block调用 Block分类 Block循环引用 Block原理探究 Block...

  • block的使用

    定义block 返回类型 (^block名称)(参数) = ^(){block内容}; 调用block block...

  • Block 02 - __block

    Block 02 - __block __block 的作用 __block 可以解决 Block 内部无法修改 ...

  • iOS面试之Block大全

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS面试之Block模块

    Block Block内容如下: 关于Block 截获变量 __block修饰符 Block的内存管理 Block...

  • iOS Block

    Block的分类 Block有三种类型:全局Block,堆区Block,栈区Block 全局Block 当Bloc...

  • iOS block 为什么官方文档建议用 copy 修饰

    一、block 的三种类型block 三种类型:全局 block,堆 block、栈 block。全局 block...

  • iOS开发block是用copy修饰还是strong

    Block分为全局Block、堆Block和栈Block1、在定义block没有引用外部变量的时候,block为全...

  • block 初探

    全局block, 栈block, 堆block

  • Block

    一、Block本质 二、 BlocK截获变量 三、__block 修饰变量 四、Block内存管理 五、Block...

网友评论

      本文标题:block

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