美文网首页
Block的基础使用

Block的基础使用

作者: E术家 | 来源:发表于2020-05-20 17:53 被阅读0次

概述

  • 闭包 = 一个函数「或指向函数的指针」 + 该函数执行的外部的上下文变量「自由变量」
  • Block 是 Objective-C 对于闭包的实现

Block的特性

  • 可以嵌套定义,定义Block方法和定义函数的方法相似
  • Block可以定在方法内部或外部
  • 只有调用Block的时候,才会执行{}内的代码
  • 本质是对象,使代码高聚合

Block的定义和使用

  • 无参数,无返回值
    void(^Myblock1)(void) = ^{
        NSLog(@"无参数,无返回值");
    };
    Myblock1();
  • 有参数,无返回值
    void(^Myblock2)(int a) = ^(int a){
        NSLog(@"%d是我传入的参数,无返回值",a);
    };
    Myblock2(100);
  • 有参数,有返回值
    int(^Myblock3)(int,int) = ^(int a,int b) {
        NSLog(@"%d 和 %d 是我传入的参数,有返回值",a,b);
        return a + b;
    };
    Myblock3(11,12);
  • 无参数,有返回值
    int(^Myblock4)(void) = ^ {
        NSLog(@"无参数,有返回值");
        return 233;
    };
    Myblock4();
  • 开发中用 typedef 定义 block
typedef int (^SnowBlock) (int,int);

这时 SnowBlock 就成为了一种Block类型
定义属性可以这样

@property (nonatomic, copy) SnowBlock myBlock;

使用时

    self.myBlock = ^int(int a, int b) {
        //TODO
        return a + b;
    };

Block与外界变量

接活自动变量(局部变量)值
  • 默认情况
    对于 block 外的变量引用,block默认是将其复制到其数据结构中来实现访问的。也就是说block的自动变量截获只针对block内部使用的自动变量,不使用则不截获,因为截获的自动变量会存储于block的结构体内,会导致block体积变大。特别要注意的是默认情况下block只能方位不能修改局部变量的值。


    int age = 27;
    void(^AgeBlock)(void) = ^{
        NSLog(@"age = %d",age);
    };
    age = 18
    AgeBlock();

输出结果


  • __block 修饰的外部变量
    对于用 __block 修饰的外部变量引用,block是复制其引用地址来实现访问的。block可以修改 __block 修饰的外部变量的值。


    __block int age2 = 27;
    void(^AgeBlock2)(void) = ^{
        NSLog(@"age2 = %d",age2);
    };
    age2 = 18;
    AgeBlock2();

输出结果


Block的copy操作

  • Block的存储域及copy操作
    先来看看一个由C/C++/OBJC编译的程序占用内存分部的结构



    其实,block有3种类型
    1.全局块 —— NSConcreteGlobalBlock
    2.栈块 —— NSConcreteStackBlock
    3.堆块 —— NSConcreteMallocBlock
    这三种block各自的存储域如下



    全局块存在于全局内存中,相当于单例
    栈块存在于栈内存中,超出其作用于则马上被销毁
    堆块存在于堆内存中,是一个带引用计数的对象,需要自行管理其内存
简而言之,存储在栈中的Block就是栈块、存储在堆中的就是堆块、既不在栈中也不在堆中的块就是全局块
遇到一个Block,我们怎么确定这个Block的存储位置 ?

(1)Block不访问外界变量(包括栈中和堆中的变量)
Block既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。
(2)Block访问外界变量
MRC环境下:访问外界变量的Block默认存在栈中。
ARC环境下:访问外界变量的Block默认存在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。

ARC下,访问外界变量的Block为什么要自动从栈区拷贝到堆区?

栈上的Block,如果其所属的变量作用域结束,该Block就被废弃,如同一般的自动变量。当然,Block中的__block变量也同时废弃。



为了解决栈块在其变量作用域结束之后被废弃(释放)的问题,我们需要把Block复制到堆中,延长其生命周期。在开启ARC时,大多数情况下编译器会恰当地进行判断是否需要将Block从栈复制到堆,如果有,自动生成将Block从栈复制到堆的代码。

Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。

在非ARC情况下需要开发者调用copy方法手动复制,由于开发中几乎都是ARC模式,所以手动复制内容不再过多研究。

将Block从栈上复制到堆上相当消耗CPU,所以当Block设置在栈上也能够使用时,就不要复制了,因为此时的复制是在浪费CPU资源。

Block的复制操作执行的是copy实例方法。不同类型的Block使用copy方法如下


根据表得知,Block在堆中copy会造成引用计数增加,这与其他Objectice-C对象是一样的。虽然Block在栈中也是以对象的身份存在,但是栈块没有引用计数,因为不需要,栈区的内存编译器自动分配释放。

不管Block存储域在何处,用copy方法复制都不会引起任何问题。在不确定时调用copy方法即可。

在ARC有效是,多次调用copy方法完全没有问题

  • __block 变量 与 __forwarding
    在copy操作之后,既然__block变量也被copy到堆上去了,那么访问该变量是访问栈上的还是堆上的呢?
__forwarding

通过__forwarding,无论是在block中还是block外访问的__block变量,也不管该变量在栈上或堆上,都能顺利地访问同一个__block变量

防止 Block 循环引用

Block 循环引用的情况
某个类将block作为自己的属性变量,然后再block的方法体里又使用了该类本身

    self.myBlock = ^int(int a, int b) {
        [self dosomething];
        return a + b;
    };

解决办法

  • ARC下:使用 __weak
    __weak typeof(self) weakSelf = self;
    self.myBlock = ^int(int a, int b) {
        [weakSelf dosomething];
        return a + b;
    };
  • MRC下:使用 __block
    __block typeof(self) blockSelf = self;
    self.myBlock = ^int(int a, int b) {
        [blockSelf dosomething];
        return a + b;
    };

值得注意的是,在ARC下,使用 __block 也有可能带来循环引用。

Block 使用示例

  • Block 作为变量
    int (^sum) (int,int); //定义一个 Block 变量 sum
    //给 Block 变量赋值
    //一般 返回值省略
    sum = ^int (int a,int b) {
        return a + b;
    };
    int n = sum(10,12);// 调用
  • Block 作为属性
    // 给 Calculate 类型 sum2变量 赋值
    typedef int (^Calculate)(int,int); //Calculate 就是类型名
    Calculate sum2 = ^(int a,int b) {
        return a + b;
    };
    int a = sum2(10,20);//调用 sum2 变量
typedef int (^SnowBlock) (int,int);
//作为对象的属性声明,copy后block会转移到堆中和对象一起
@property (nonatomic, copy) SnowBlock myBlock; //使用 typedef
@property (nonatomic, copy) int (^sumNumber)(int,int); //不使用 typedef
    self.sumNumber = ^int(int a, int b) {
        return a + b;
    };
  • 作为OC中的方法参数
    无参数传递的 block
- (CGFloat)testTimeConsume:(void(^)(void))middleBlock {
    // 执行前记录下当前的时间
    CFTimeInterval startTime = CACurrentMediaTime();
    middleBlock();
    // 执行后记录下当前时间
    CFTimeInterval endTime = CACurrentMediaTime();
    return endTime - startTime;
}

调用

    [self testTimeConsume:^{
       // 放入 block 中的代码
    }];

有参数传递的 block

- (CGFloat)testTimeConsume2:(void(^)(NSString *name))middleBlock {
    // 执行前记录下当前的时间
    CFTimeInterval startTime = CACurrentMediaTime();
    NSString *name = @"有参数";
    middleBlock(name);
    // 执行后记录下当前时间
    CFTimeInterval endTime = CACurrentMediaTime();
    return endTime - startTime;
}

调用

    [self testTimeConsume2:^(NSString *name) {
        // 放入 block 中的代码 可以使用参数 name
        // 参数 name 是实现代码中传入的 在调用时只能使用 不能传值
    }];
  • Block回调
    Block回调是关于Block最常用的内容,比如网络下载,我们可以用Block实现下载成功与失败的反馈。开发者在block没发布前,实现回调基本都是通过代理delegate进行的,比如负责网络请求的原生类NSURLConnetion类,通过多个协议方法实现请求中的事件处理。而在最新的环境下,使用的NSURLSession已经采用block的方式处理任务请求了。各种第三方网络请求框架也都在使用block进行回调处理。这种转变很大一部分原因在于block使用简单,逻辑清晰,灵活。
    比如有A、B两个界面。A界面中有个buttonA和一个label,B界面有一个UITextField和一个buttonB。A界面点击buttonA跳转到B界面,B界面的UITextField中输入内容后点击buttonB跳回到A界面,并将B界面中UITextField中的值传到A界面并显示在A界面的label上。

核心代码
A界面中 .m button点击触发函数

- (void)gotoB {
    BlockExampleB *vcB = [BlockExampleB new];
    [self.navigationController pushViewController:vcB animated:YES];
    
    __weak typeof(self) weakSelf = self;
    [vcB setCVBlock:^(NSString * string) {
        weakSelf.textLabel.text = string;
    }];
}

B界面中 .h

typedef void(^getVCBstringBlock) (NSString *string);
@property (nonatomic, copy) getVCBstringBlock CVBlock;

.m 中

- (void)back {
    [self.navigationController popViewControllerAnimated:YES];
    self.CVBlock(self.textField.text);
}

相关调试demo:https://github.com/Snowxls/BlockKnowledge

相关文章

  • Block相关(二)

    前言,在上篇文章Block相关(一),中介绍了Block相关的基础知识,包括Block介绍、基础语法、使用情况等。...

  • Block的基础使用

    概述 闭包 = 一个函数「或指向函数的指针」 + 该函数执行的外部的上下文变量「自由变量」 Block 是 Obj...

  • i0S-UIView动画(基础动画+Block动画)

    UIView基础动画 UIView基础动画Api介绍: 使用例子1: 使用例子2: UIView-Block动画 ...

  • Block原理

    原文链接Block原理 Block的实质 我们先写一个最基础的Block 接着我们使用clang将这段代码翻译,在...

  • Block 的实现原理 (一)

    本篇文章主要是使用 clang 重写 dispatch_block_t 类型对象. 最基础的 Block 这个语法...

  • iOS:加强

    基础问题1、使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?2...

  • block-基础概念和使用

    block主要准备分为3个文章记录。第一章:基础概念和使用第二章:捕获变量第三章:持有变量 一.基础 block的...

  • iOS:Block的本质

    我们项目中经常使用block来进行回调传值,之前我对block的认识也就仅仅的停留在基础的层面,包括简单的使用和一...

  • css权威教程

    基础知识 文本使用闭合标签(标签,段落,文本) 引用文本使用自闭和标签 css display属性1:block(...

  • ios block使用

    iOS Block的使用一 .最简单的block使用******使用block的三个步骤:1.定义block变量 ...

网友评论

      本文标题:Block的基础使用

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