美文网首页iOS底层
iOS开发之基础篇(14)—— Block

iOS开发之基础篇(14)—— Block

作者: 看影成痴 | 来源:发表于2017-11-09 10:23 被阅读28次

    版本

    Xcode 9.1

    block简介

    block是一个OC对象,于iOS4开始引入。其本身封装了一段代码,可被当作变量、当作参数或作为返回值。block常用于GCD、动画、排序及各类回调传值中。

    block代码结构图

    注:图片来自这里

    示例1

        // 创建一个block
        int(^myBlock)(int) = ^(int num) {
            return num * 3;
        };
        
        // 调用block
        NSLog(@"%d",myBlock(3));        // 结果输出9
    

    示例2
    如果需要重复声明多个相同参数和返回值的block,我们可以用typedef来定义block类型。

        // 声明一个block
        typedef int(^myBlock)(int);
        
        // 实例化第一个block
        myBlock block1 = ^(int num) {
            return num * 3;
        };
        
        // 实例化第二个block
        myBlock block2 = ^(int num) {
            return num * 4;
        };
        
        // 调用block
        NSLog(@"%d, %d",block1(3),block2(3));       // 结果为9, 12
    

    block分类

    按照存储区域可分为三类:

    • NSGlobalBlock
    • NSStackBlock
    • NSMallocBlock

    1. NSGlobalBlock

    全局block——没有用到外界变量,或者只用到全局变量、静态(static)变量。生命周期从创建到应用程序结束。

    示例:

        /* 情况1:不使用外部变量 */
        // 声明一个没有用到外部变量的block
        void (^globalBlock1)(void) = ^{
        };
        // 调用block
        globalBlock1();
        // 查看block类型
        NSLog(@"globalBlock1:%@",globalBlock1);
        
        /* 情况2:使用全局变量和静态变量 */
        // 新建静态变量
        static NSString *staticStr = @"我是静态变量";
        // 声明一个使用全局变量和静态变量的block
        void (^globalBlock2)(void) = ^{
            NSLog(@"%@, %@",globalStr,staticStr);
        };
        // 调用block
        globalBlock2();
        // 查看block类型
        NSLog(@"globalBlock2:%@",globalBlock2);
    

    结果如下:

    2. NSStackBlock

    栈block——用到局部变量、成员属性/变量,且没有强指针引用。生命周期由系统管理,超出作用域(函数返回时)马上被销毁。

        /* 情况1:用到局部变量 */
        // 新建一个局部变量
        NSString *localStr = @"我是局部变量";
        // 查看block类型, 直接在NSLog里面调用使用了局部变量的block,这样就没有强指针引用了
        NSLog(@"stackBlock1:%@",^{NSLog(@"%@",localStr);});
        
        /* 情况2:用到成员属性/变量 */
        // 给成员变量、成员属性赋值
        _variateStr = @"我是成员变量";
        self.propertyStr = @"我是成员属性";
        // 查看block类型, 直接在NSLog里面调用使用了成员属性/变量的block,这样就没有强指针引用了
        NSLog(@"stackBlock2:%@",^{NSLog(@"%@, %@",_variateStr,self.propertyStr);});
    

    结果:

    3. NSMallocBlock

    堆block——有强指针引用或使用有copy修饰的成员属性/变量。没有强指针引用即销毁,生命周期需手动管理。

        /* 情况1:用到强指针引用 */
        // 新建一个局部变量
        NSString *localStr = @"我是局部变量";
        // 声明一个使用强指针引用的block
        void (^mallocBlock1)(void) = ^{
            NSLog(@"%@",localStr);
        };
        // 调用block
        mallocBlock1();
        // 查看block类型
        NSLog(@"mallocBlock1:%@",mallocBlock1);
        
        /* 情况2:用到有copy修饰的成员属性/变量 */
        // 给成员变量、成员属性赋值
        _variateStr = @"我是成员变量";
        self.propertyStr = @"我是成员属性";
        // 声明一个使用有copy修饰的成员属性的block
        void (^mallocBlock2)(void) = ^{
            NSLog(@"%@, %@",_variateStr, self.propertyStr);
        };
        // 调用block
        mallocBlock2();
        // 查看block类型
        NSLog(@"mallocBlock2:%@",mallocBlock2);
    

    结果:

    block访问外部变量

    1. 访问局部变量

    在block中可以访问局部变量,但不能直接修改局部变量。
    示例1:

        // 声明局部变量local
        int local = 100;
        // 声明block
        void(^myBlock)(void) = ^{
            // local++;    // 修改local值会报错
            NSLog(@"local1 = %d", local);
        };
        // 在调用block之前改变local的值
        local = 101;
        // 调用block
        myBlock();
        // 查看local改变后的值
        NSLog(@"local2 = %d", local);
    

    打印结果:

    原理分析:

    在block定义时是将局部变量的值传给block变量所指向的结构体,因此在调用block之前对局部变量进行修改并不会影响block内部的值。同时传进来的内部值也是不可修改的。

    但是,我们有时候又想在block内部修改block外的局部变量,应该怎么办呢?

    使用__block修饰的局部变量,在block中可以直接修改
    示例2:

        // 声明局部变量local (用__block修饰)
        __block int local = 100;
        // 声明block
        void(^myBlock)(void) = ^{
            NSLog(@"修改前local1 = %d", local);
            local++;    // 不会报错
            NSLog(@"修改后local1 = %d", local);
        };
        // 在调用block前修改local值
        local = 200;
        // 调用block
        myBlock();
        // 查看local改变后的值
        NSLog(@"local2 = %d", local);
    

    打印结果:

    原理分析:

    在局部变量前使用__block修饰,block定义时是将局部变量的指针传给block变量所指向的结构体,因此在调用block之前对局部变量进行修改会影响block内部的值。同时内部的值也是可以修改的。

    2. 访问静态变量

    在block中可以访问静态变量,也可以直接修改静态变量。

    示例:

        // 声明静态变量staticInt
        static int staticInt = 100;
        // 声明block
        void(^myBlock)(void) = ^{
            NSLog(@"修改前staticInt1 = %d", staticInt);
            staticInt++;    // 不会报错
            NSLog(@"修改后staticInt1 = %d", staticInt);
        };
        // 在调用block前修改staticInt值
        staticInt = 200;
        // 调用block
        myBlock();
        // 查看staticInt改变后的值
        NSLog(@"staticInt2 = %d", staticInt);
    

    打印结果:

    原理分析:

    block定义时是将静态变量的指针传给Block变量所指向的结构体,因此在调用block之前对静态变量进行修改会影响block内部的值。同时内部的值也是可以修改的。

    3. 访问全局变量

    在block中可以访问全局变量,也可以直接修改全局变量。
    示例:

        // 先在@implementation前定义一个全局变量globalInt
        
        // 声明block
        void(^myBlock)(void) = ^{
            NSLog(@"修改前globalInt1 = %d", globalInt);
            globalInt++;    // 不会报错
            NSLog(@"修改后globalInt1 = %d", globalInt);
        };
        // 在调用block前修改globalInt值
        globalInt = 200;
        // 调用block
        myBlock();
        // 查看globalInt改变后的值
        NSLog(@"globalInt2 = %d", globalInt);
    

    打印结果:

    原理分析:

    全局变量所占用的内存只有一份,供所有函数(方法)共同调用,在block定义时并未将全局变量的值或者指针传给block变量所指向的结构体,因此在调用Block之前对局部变量进行修改会影响block内部的值。同时内部的值也是可以修改的。

    block在ARC下的内存管理

    如果对象内部有一个block属性,而在block内部又访问了该对象,那么会造成循环引用
    错误示例:

    // 声明block成员属性
    @property (nonatomic, copy) void(^myBlock)(void);
    
        // 定义block
        self.myBlock = ^{
            NSLog(@"%@", self);    // 此句造成循环引用,编译报错
        };
        
        // 执行block
        self.myBlock();
    

    解决办法:使用一个弱引用的指针指向该对象,然后在Block内部使用该弱引用指针来进行操作。
    正确示例:

    // 声明block成员属性
    @property (nonatomic, copy) void(^myBlock)(void);
    
        // 声明一个弱引用指针对象
        __weak typeof(self) weakSelf = self;
        
        // 定义block
        self.myBlock = ^{
            NSLog(@"%@", weakSelf);        // 使用弱引用指针对象
        };
        
        // 执行block
        self.myBlock();
    

    如果担心在调用Block之前引用的对象已经被释放,那么我们需要在Block内部再定义一个强指针来指向该对象
    官方示例:

    // 声明block成员属性
    @property (nonatomic, copy) void(^myBlock)(void);
    
        // 声明一个弱引用指针对象
        __weak typeof(self) weakSelf = self;
        
        // 定义block
        self.myBlock = ^{
            typeof(self) strongSelf = weakSelf;     // 声明一个强引用指针对象
            NSLog(@"%@", strongSelf);               // 使用强引用指针对象
        };
        
        // 执行block
        self.myBlock();
    

    block的传值应用

    先看看效果:

    从界面1加载界面2,然后在界面2返回时回传textField里的text。方法步骤:

    • 界面1(接收方):
      1、定义(实现)一个block并传给界面2(的block属性)
      2、在block中处理传递过来的值
    • 界面2(传值方):
      1、声明一个block属性,其具体实现由接收方来完成
      2、在需要传值的地方调用block

    界面1 .m文件:

    // 跳转到界面2
    - (IBAction)btnShowViewController2:(UIButton *)sender {
        
        // 从storyboard实例化界面2
        ViewController2 *vc2 = [self.storyboard instantiateViewControllerWithIdentifier:@"ViewController2"];
        
        // 定义(实现)一个block并传给界面2(的block属性)
        vc2.textBlock = ^(NSString *str) {
            self.label.text = str;      // 处理传回来的值
        };
        
        // 加载界面2
        [self presentViewController:vc2 animated:YES completion:nil];
    }
    

    界面2 .h文件

    @interface ViewController2 : UIViewController
    
    // 声明block属性
    @property (nonatomic, copy) void(^textBlock)(NSString *);
    
    @end
    

    界面2 .m文件:

    // 返回界面1
    - (IBAction)btnBack:(UIButton *)sender {
        
        // 先判断block是否为空,为空则报错
        if (self.textBlock) {
            // 返回textField里的text
            self.textBlock(self.textField.text);
        }
        
        // 退出界面2
        [self dismissViewControllerAnimated:YES completion:nil];
    }
    

    相关文章

      网友评论

        本文标题:iOS开发之基础篇(14)—— Block

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