zoukankan      html  css  js  c++  java
  • 代码块(Block)回调一般阐述

    本章教程主要对代码块回调模式进行讲解,已经分析其他回调的各种优缺点和适合的使用场景。

    • 代码块机制
    • Block变量类型
    • Block代码封装及调用
    • Block变量对普通变量作用域的影响
    • Block回调接口使用
    • 0、Block简介

      Block块是封装工作单元的对象,是可以在任何时间执行的代码段。其本质上是可移植的匿名函数,可以作为方法和函数的参数传入,可以从方法和函数中返回。—(翻译自官方文档)

      块是对C语言的一种扩展,它并未作为标准的ANSI C所定义的部分,而是有苹果公司添加到语言中的。块看起来更像是函数,可以给块传递参数,块也可以具有返回值。

      1,代码块机制

      苹果公司在iOS4 SDK中首次支持代码块机制,随后代码块机制被广泛应用于各种编码场景,最常见的为回调机制,也成为Block回调。

      代码块也称Block。是封装代码的一种机制,也可以称为匿名函数。

      使用这种机制可以将一段代码放入一个Block变量中进行存储,该变量可以作为参数进行传递,也可以通过该变量调用其存储的代码。

      2,Block变量类型

      在OC语法中,创建一个变量首先要明确其类型。Block作为一个可以储存代码的变量,其类型相对特殊。

      确定block变量的类型有两个因素:

      • 储存代码的返回值类型
      • 储存代码的参数列表

        只要这两个因素一样,我们就可以说是相同的block类型。

        现在我们举一个简单的例子,创建一个储存没有返回值,没有输入参数的代码的block类型。

        1
        2
        <code>void (^ varBlock)(void);
        </code>

        上面的代码声明了一个block变量,变量名为varBlock,其储存代码类型为没有返回值,没有输入参数。

        如果想要储存有返回值,有输入参数的代码,同样可以声明响应的block变量进行使用。

        1
        2
        <code>int (^ varBlock1)(int a,int b);
        </code>

        上面的代码声明了一个block变量,变量名为varBlock1,其储存代码类型为int型返回值,有两个int型参数。

        Block变量类型较为复杂,如果直接用这种方式进行声明变量十分容易储存。通常我们用typedef关键字将Block类型重命名,然后用相对简单的类型名进行声明变量的工作。

        1
        2
        3
        4
        <code>typedef void (^ BlockType1)(void);
         
        BlockType1 var1;//var1与varBlock1为同一类型
        </code>

        3,Block代码封装及调用

        有了Block变量,下面我们就要给变量赋值。

        1
        2
        3
        4
        5
        6
        <code>typedef void (^ BlockType1)(void);
         
        BlockType1 var1;
         
        var1 = ^(){NSLog(@"test")};
        </code>

        通过上述语法格式将代码封装在大括号内,并用var1变量进行储存。封装代码的过程中要注意一下几点:

        • ^符号开始为Block封装过程。
        • ^后面的小括号中写这段代码需要的参数。该参数有调用者进行赋值。
        • 小括号后面的大括号中写要封装的代码,且代码可以使用小括号中的参数。

          下面举一个求两个数的和的代码封装过程。

          1
          2
          3
          4
          5
          6
          <code>typedef int (^BlockType)(int a,int b);
           
          BlockType varBlock;
           
          varBlock = ^(int a,int b){return a+b;};
          </code>

          将代码存入varBlock变量中后,便可以使用该变量调用代码。

          1
          2
          3
          4
          5
          <code>int a = 4;
          int b = 6;
          int sum = varBlock(a,b);
          NSLog(@"sum = %d",sum);//输出结果为10
          </code>

          Block变量也可以给同类型的变量赋值

          1
          2
          3
          4
          5
          <code>BlockType varBlockTemp;
          varBlockTemp = varBlock;
          int sum = varBlockTemp(1,2);
          NSLog(@"sum = %d",sum);//输出结果为3
          </code>

          将一段代码当做一个变量进行传递,Block这样的特性极大的方便了我们之后的编码工作

          3,Block变量对普通变量作用域的影响

          通过Block对象将代码进行封装的同时,有一个非常关键的问题我们需要明确讨论,即Block变量对普通变量作用域的影响。

          通过一个简单案例来因此这个问题。见如下代码:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          <code>main()
          {
              {
                  int a = 1;
                  {
                      a = 2;
                  }
                  //此处输出a的值为2
              }
              //此处已经超出变量a的作用域,讨论a的值无意义。
          }
          </code>

          这段代码很简单,通过大扩展来表示变量的作用域范围。再看下面代码:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          <code>typedef void (^ BlockType)(void);
           
          BlockType var;
           
          void fun1()
          {
              int a = 10;
              var = ^(){NSLog(@"a = %d",a)};
          }
           
          void fun2()
          {
              var();
          }
           
          main()
          {
              fun1();
              fun2();
          }
          </code>

          在fun2函数中调用var变量时,运行的是fun1中存入var变量的代码,且代码中的使用的变量a也是fun1中的局部变量。

          正常状态下,变量a的作用域在fun1函数体大括号内。在函数体大括号外面使用a是没有意义的。

          但此处情况特殊,变量a被block变量var所使用,所以var变量将a进行了一个复制操作,也就是我们在var的代码里面使用的a其实是a的副本。

          我们看下面的代码:

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          <code>typedef void (^ BlockType)(void);
           
          BlockType var;
           
          void fun1()
          {
              int a = 10;
              var = ^(){NSLog(@"a = %d",a)};
              a = 20;
          }
           
          void fun2()
          {
              var();
          }
           
          main()
          {
              fun1();
              fun2();
          }
          </code>

          这段代码的输出和上一段代码一样,不会因为fun1函数中a的值发生变化而导致block里面的a的值发生变化。原因是Block变量在使用局部变量是,会对局部变量进行一个复制操作,block变量中储存的代码使用的时局部变量的副本。

          但是在某些特殊场合,我们需要改变局部变量可以引起block变量中代码的变化。这时候我们需要使用block全景变量。

          block全景变量通过:__block来声明。

          1
          2
          3
          4
          5
          6
          7
          8
          9
          10
          11
          12
          13
          14
          15
          16
          17
          18
          19
          20
          21
          22
          <code>typedef void (^ BlockType)(void);
           
          BlockType var;
           
          void fun1()
          {
              __block int a = 10;
              var = ^(){NSLog(@"a = %d",a)};
              a = 20;
          }
           
          void fun2()
          {
              var();
          }
           
          main()
          {
              fun1();
              fun2();
          }
          </code>

          上文代码中,fun1中的变量a被block全景变量标识符所修饰,即变量a成为一个block全景变量。

          其作用是,此时block封装代码时使用a变量,不会进行复制操作,也就表示,局部变量a与block代码中的a为同一个变量。所以,当前代码的运行结果为 a = 20。

          4,Block回调接口使用

          回调的本质为控件反馈自身信息,在面向对象编程中,控件需要调用方法反馈自身信息,而方法必须从属某个对象。所以之前的回调接口必须设置两个参数,一个反馈对象,一个反馈方法。

          • 在目标动作中,反馈对象为target,反馈方法为action,一个SEL类型的变量。
          • 在委托回调中,反馈对象为delegate,反馈方法为组件协议中声明的方法。

            在Block回调中,因Block机制可以直接将代码封装如一个变量中,而且这个变量可以当做参数进行传递。利用这个机制,组件可以保存这段代码,在触发事件的时候直接调用此段代码,不需要设置反馈对象和反馈方法。

            这里仍然用之前的开关最为例子:

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            <code>typedef enum : NSUInteger {
                SwitchStateOff,//default
                SwitchStateOn,
            } SwitchState;
             
            typedef void(^SBlockType)(SwitchState state);
             
            @interface SwitchB : NSObject
             
            @property(nonatomic,assign,readonly)SwitchState currentState;
             
            @property(nonatomic,strong)SBlockType changeStateBlockHandle;
             
            @end
            </code>

            声明中的changeStateBlockHandle属性就是保存回调代码。设置回调,只需要将此属性赋值就可。

            1
            2
            3
            4
            5
            6
            7
            8
            9
            10
            11
            12
            13
            14
            15
            16
            17
            18
            19
            20
            21
            22
            23
            24
            25
            26
            27
            28
            29
            30
            31
            32
            33
            <code>@interface Room : NSObject
            @property (strong, nonatomic) Light *lightA;
            @property (strong, nonatomic) SwitchB *s;
             
            @end
             
             
            @implementation Room
            - (instancetype)init
            {
                self = [super init];
                if (self) {
                    self.lightA = [[Light alloc] init];
                    self.s = [[SwitchB alloc] init];
             
                    __weak __block Room * copy_self = self;//打破强引用循环,后续章节会展开讲解
             
                    self.s.changeStateBlockHandle = ^(SwitchState state)
                    {
                        if (state == SwitchStateOff)
                        {
                            [self.lightA turnOff];
                        }
                        else
                        {
                            [self.lightA turnOn];
                        }
                    };
                }
                return self;
            }
            @end
            </code>

            当开关的状态发生改变时,开关需要将自身状态反馈给使用者。当使用Block回调接口的组件时,需要将回调代码直接封装,赋值给组件响应的Block类型的属性即可。当状态改变时,封装的代码便被组件调用。

  • 相关阅读:
    centos7.x网卡bond配置
    twemproxy源码解析系列三Twemproxy配置文件解析及相关组件初始化过程
    twemproxy源码解析系列二关键数据结构分析
    Nginx 变量漫谈(一)(转)
    twemproxy源码解析系列一特性及启动流程分析
    理解 Linux 的处理器负载均值
    man命令使用
    awk 查找文件中数字 字符串 email
    非阻塞socket调用connect, epoll和select检查连接情况示例
    Mysql日期类型大小比较拉取给定时间段的记录
  • 原文地址:https://www.cnblogs.com/Jenaral/p/6164488.html
Copyright © 2011-2022 走看看