美文网首页
block(OC)和closure(Swift)详解

block(OC)和closure(Swift)详解

作者: sayHellooX | 来源:发表于2018-09-14 22:15 被阅读286次

    Block

    语法定义

    ^(参数列){ 表达式 }
    从 ^ 开始到 {} 结束 就是块语法;

       //声明没有返回值,有一个Int类型参数的块对象 b1
        void (^b1)(int) = ^(int a) { printf("%d", a); };
       //声明没有返回值为int类型,没有参数的块对象 b2
        int (^b2)(void) = ^(void) { return 10; };
        // 调用
        b1(3);   //print 3
        b2();
    

    似一般的类型

    Block同int等普通类型一样,也可以看成一种特别的类型,如可以声明一个int型的变量,同样也可以声明一个Block型的变量,只不过 int 代表的是一个整形的数字,而Block代表的是一个,能捕获状态的,有很多特性的代码块(这里后面详细介绍)

        //声明
        int i;
        //赋值
        i = 10;
    
        //声明
        void (^b1)(void);
        //赋值
        b1 = ^{ printf("没有返回值及参数的Block"); };
    

    类型声明,为了方便简写,可以通过typedef简化声明;

        //定义MyBlocks类型
        typedef int (^MyBlocks) (int);
        //声明  MyBlocks的 Block
        MyBlocks block = ^(int a){ return 2 * a; };
    
    • 应用方式
      既然上面说到 Block就像 int一样也可以看做一种类型,那么它也可以像普通类型一样,作为方法的参数及方法的返回值;
    typedef int (^MyBlocks) (int);
    
    - (MyBlocks)currentBlock {
        int (^block)(int) = ^(int a) {
            return 2 * a;
        };
        return block;
    }
    
    - (void)setBlock: (MyBlocks) blcok {
        blcok(9);
    }
    

    捕获(capture)

    Block 一个很重的特性就是,它可以捕获它声明处的一些状态,比如它可以捕获变量,这也是为什么前文提到,Block是包含一些状态的代码块;

    //全局静态变量
    static int glob = 1000;
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        void (^b)(void);
        
        //局部静态变量
        static int s = 20;
        
        //局部变量
        int a = 20;
        
        //1 1000 20 20
        b = ^{ NSLog(@"gloab: %d, s: %d, a: %d", glob, s, a); };
        [self myPrintFunc:1 block:b];
        
        s = 0;
        a = 0;
        glob = 5000;
    
        //2   5000 0 20
        [self myPrintFunc:2 block:b];
        
        //3   5000 0 0
        b = ^{ NSLog(@"gloab: %d, s: %d, a: %d", glob, s, a); };
        [self myPrintFunc:3 block:b];
        
    }
    
    - (void)myPrintFunc: (int) m block: (void(^)(void)) b {
        //打印
        NSLog(@"m: %d", m);
        //调用块
        b();
    }
    

    打印结果:

     m: 1
    gloab: 1000, s: 20, a: 20
     m: 2
    gloab: 5000, s: 0, a: 20
    m: 3
    gloab: 5000, s: 0, a: 0
    

    1: 这里声明了一个Block,同时赋值给b,在这个声明处Block可以捕获 全局静态变量 global、局部静态变量s、局部变量a,然后将b作为参数传递给方法 myPrintFunc(:)后,被调用;
    2: 改变三种变量的值后,重新调用了myPrintFunc(:)方法,也就是重新执行了Block b,这里可以看出,三种变量在Block内的被捕获状态是不同的,局部变量没有因为原有的值改变而改变,说明Block内部捕获的是copy的一份;
    3.重新声明一个新的Block给b ,并且重新调用,这个Block 捕获重新捕获三个变量;
    同时我们改变一下Block b,在下面的代码中会报错

    b = ^{
            glob += 100;
            s += 10;
            //error
            a += 10;
        };
    

    总结:

    • 在Block块内可以捕获:局部变量、形参外、还包括声明当前Block处可以访问的外部变量
    • 从Block内部,可以直接更改访问到的,外部变量及静态变量(static变量)的值。
    • 在Block内可以访问的局部变量(栈内变量)中,局部变量的值会自动被保存起来(copy一份新的)然后在访问(变量a的状况), 所以即使这些自动变量的最初的值发生了改变,块对象在使用时也不知道。Block copy的这些局部变量的值可以被读取,但是不能被改变,相当于用const进行修饰的变量,改变时会报错(解决办法后面介绍)。

    生命周期

    有如下代码

    typedef int (^myBlock)(void);
    
    @interface ViewController ()
    
    /**
     * 1
     * 声明一个block属性
     */
    @property  myBlock block;
    
    @end
    
    @implementation ViewController
    
    - (void)viewDidLoad {
        [super viewDidLoad];
        
        self.block = ^int{
            return 100;
        };
        
        [self printFunc:self.block];
        [self func1:5];
        [self func2:10];
        //5
        [self printFunc:self.block];
    }
    
    - (void)printFunc: (myBlock)b {
        NSLog(@"%d", b());
    }
    
    - (void)func1: (int) n {
        //2
        int (^b1)(void) = ^{ return n; };
        [self printFunc: b1];
        //3 给属性block赋值
        self.block = b1 ;
    }
    
    - (void)func2: (int) n {
        int a = 20;
        int (^b2)(void) = ^{ return n * a; };
        //4
        [self printFunc: b2];
    }
    

    上面的输出结果:

    100    
    5    
    200    
    5
    

    上面的代码是在ARC 自动内存管理的情况下运行的,如果在手动内存管理的情况下运行代码,可能会在 5 的时候报错。
    原因:
    在2、4处,为在函数内部声明的Block b1、b2,这里声明的Block和在函数内部的局部变量相同,同样在栈内存中分配空间,也就是说,函数运行完成后,为该函数分配的栈内存会被释放,同样的分配给b1、b2的内存也会被释放,但是在3处将函数内部的b1 赋值给外部self.block,当执行5的时候,由于原来的b1 已经被释放,所以可能会出错,这里我们需要在赋值的时候进行copy操作,即:
    如果在手动内存管理的时候

    self.block = [b1 copy];
    

    在ARC情况下,系统自动给我们进行了copy操作,所以不用担心,但是在声明Block的属性时,我们应该做copy的属性标记,来表示赋值的copy特性,这也是需要属性声明的时候需要标记copy的原因。

    @property (copy) myBlock block;
    

    官方文档说明:
    Note: You should specify copy as the property attribute, because a block needs to be copied to keep track of its captured state outside of the original scope. This isn’t something you need to worry about when using Automatic Reference Counting, as it will happen automatically, but it’s best practice for the property attribute to show the resultant behavior. For more information, see Blocks Programming Topics.

    _ _block 变量

    前面提到Block会捕获他可以访问的局部变量,并创建副本,但是只能读取,不能修改,但是可以读取并更改全局/局部静态变量的值。如果我们想要像对静态变量那样,对捕获的局部变量进行同样的操作,我们需要用_ _block 关键字来修饰该局部变量。

    #include <stdio.h>
    #include <Block.h>
    
    void (^g)(void) = NULL;
    int c = 0;
    
    void func(int n){
        __block int sh = 0;
        void (^b1)(void) = ^{
            sh += 1;
            printf("%d: b1, n = %d, sh = %d\n", ++c, n, sh);};
        void (^b2)(void) = ^{
            sh += 20;
            printf("%d: b2, n = %d, sh = %d\n", ++c, n, sh);};
        b1();                                  //1, 5
        b2();                                  //2, 6
        g = b1;
        sh += n*1000;
        n = 999;
        b2();                                 //3, 7
        
        printf("----\n");
    }
    
    int main(void)
    {
        void (^myBlock)(void);
        func(1);
        myBlock = g;
        myBlock();                            //4
        func(2);
        myBlock();                            //8
        return 0;
    }
    
    • 输出如下
    **1: b1, n = 1, sh = 1**
    **2: b2, n = 1, sh = 21**
    **3: b2, n = 1, sh = 1041**
    **----**
    **4: b1, n = 1, sh = 1042**
    **5: b1, n = 2, sh = 1**
    **6: b2, n = 2, sh = 21**
    **7: b2, n = 2, sh = 2041**
    **----**
    **8: b1, n = 1, sh = 1043**
    

    总结_ _block 如下:

    • _ _block修饰的变量的作用如下:
    • 函数内Block语句引用的 _ _ block变量是Block对象可以读取的变量,同一个作用域内多个Block对象访问时,他们之间可以共享_ _block变量的值。
    • _ _ block变量不是静态变量,它在Block句法每次执行时获取变量的内存区域,也就是说同一个变量作用域内的Block对象及他们之间共享的_ _ block变量是在执行时动态生成的。
    • 访问_ _ block变量的Block对象在被赋值后,新生成的Block对象也能共享_ _block变量的值
    • 多个对象访问同一个_ _block变量时,只要有一个Block对象存在着, _ _block变量就会存在着,如果访问该变量的对象不存在了,该变量也就会随之消失。

    ARC中使用块对象的注意点-避免循环引用

    前面我们提到在Block语句内有关外部变量及局部变量的一些行为,下面我们说下在Block内部引用对象时的行为,代码如下:

    - (void)func1: (int) n {
        int a = 2 * n;
        SomeClass *objc = [SomeClass new];
        void (^b1)(void) = ^{ [objc method: a]; };
        outBlock = b1;
    }
    

    在上面的代码中,b1同局部变量 a 一样,在栈内分配空间,也相当于局部变量,在b1中调用了对象objc及a,在b1中就多了a及objc的副本,但是这里多了的objc的副本也仅仅是对objc的引用,而不会使得objc的引用计数增加,相当于一个 weak 类型的引用,但是当将b1赋值给outBlock,由于会执行copy操作,这时候就在堆上生成了一份新的block,在这个新的block内也同样有着对objc的引用,这时objc的引用计数会+1,因为函数func1执行结束后,b1就会被释放,所以被赋值的Block会继续持有objc;
    如果上面的代码,objc 为包含当前方法的类对象的实例变量的话,即objc为self的实例变量的话,那么b1被copy后self的引用计数也会+1,objc为整数时也是一样的效果;

    根据上面的特性,在ARC情况下,使用Block语句时,要注意防止产生循环引用。下面介绍更详细的例子:

    @interface A ()
    
    @property  Handel* handel;
    @property  Logger* logger;
    
    @end
    
    -(void)func {
          handel.cleanUp = ^{
              [self.logger  writeLog];
          };
    }
    

    在上面的代码中,类A有 属性 handel,logger。handel拥有Block cleanUp,如果在A的对象方法中,调用了cleanUp,同时在 Block cleanUp 中调用了A的属性,根据Block的特性,就会产生 A -> handel -> block -> A 这样的循环。
    解决办法:

    • 1.先声明自身的若引用被Block捕获,在Block内用强引用保存对象,然后在进行调用。
    __weak A * weakSelf =  self;
    handel.cleanUp = ^{
        A * strongSelf =  weakSelf;
        [strongSelf.logger writeLog];
    };
    
    • 2.通过__block解决循环引用


      image.png
    image.png

    执行execBlock后


    image.png

    这种办法的缺点是,为了避免循环引用,必须要执行这个block,如果不执行,则会引起循环引用
    未完待续.....

    相关文章

      网友评论

          本文标题:block(OC)和closure(Swift)详解

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