美文网首页
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的基础使用

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