• 【Objective -- C】—— block


    Block简介

    block是一个带有自动变量值的匿名函数,它也是一个数据类型,跟int double float一样都是数据类型.所以我们是可以创建一个block类型的变量。

    总结来说:block类似于一个方法。而每一个方法都是在被调用的时候从硬盘到内存,然后去执行,执行完就消失,所以,方法的内存不需要我们管理,也就是说,方法是在内存的栈区。 所以,block不像OC中的类对象(在堆区),他也是在栈区的。如果我们使用block作为一个对象的属性,我们会使用关键字copy修饰他,因为他在栈区,我们没办法控制他的消亡,当我们用copy修饰的时候,系统会把该 block的实现拷贝一份到堆区,这样我们对应的属性,就拥有的该block的所有权。就可以保证block代码块不会提前消亡。

    语法

    初始化和声明

    在这里插入图片描述

     返回值类型 (^block变量的名称)(参数列表)
    
    
    • 1
    • 2
    void (^block1)()//无返回值,无参数
    int (^block2)(int num1,int num2)//int类型返回值i,两个int类型参数
    
    
    • 1
    • 2
    • 3

    Block类型变量

    Block类型变量与一般C语言函数变量完全相同,可作为以下用于使用:

    • 自动变量
    • 函数参数
    • 静态全局变量
    • 全局变量

    使用Block语法,将Block赋值为Block变量。

    int (^blk)(int) = ^(int count){return count + 1};
    
    
    • 1
    • 2

    因为与通常的变量相同,所以当然也可以有Block类型变量赋值给Block类型变量。

    int (^bilk1)(int) = blk;
    
    
    • 1
    • 2
    - (void)blk3 {
        int (^blk)(int) = ^(int count){
            return count + 1;
        };
        int (^blk1)(int) = blk;
        int (^blk2)(int);
        blk2 = blk1;
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    typedef

    函数参数中使用Block类型变量可以向函数传递Block。但是在参数和函数返回值中使用Block类型变量极为复杂。这时,我们可以使用typedef来解决该问题。

    typedef int (^blk_t)(int);
    
    
    • 1
    • 2

    截获自动变量

    blk的带有自动变量的匿名函数,什么是带有自动变量?
    带有自动变量值在Block中表现为“截取自动变量值 ”。

    NSString *c = @"ff";
    NSString* (^addBlock)(NSString *a, NSString *b) = ^(NSString *a, NSString *b) {
        return [NSString stringWithFormat:@"%@%@%@", a, b, c];
    };
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    __block说明符

    自动变量值截获只能保存执行Block语法瞬间的值。
    以下面的代码为例:

    {
    	int val = 10;
        const char* fmt = "val = %d\n";
        //自动变量值截获只能保存执行Block语法瞬间的变量的值。
        //执行瞬间fmt = "val = %d\n",val = 10;
        void (^blk)(void) = ^ {
            printf(fmt, val);
        };
        val = 2;
        fmt = "These values were changed. val = %d\n";
        blk();
            
        return 0;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    输出的结果为:
    在这里插入图片描述
    不允许块属性,仅允许在局部变量上 __block attribute not allowed, only allowed on local variables。
    保存后就不能改写该值。若想在Block语法的表达式中将值赋给Block语法外声明的自动变量,需要在该自动变量上附加 __block说明符 。我们称这种变量为__block变量。

    截获的自动变量

    对于块来说截获Objective-C对象,调用变更该对象的方法不会产生编译错误。

    - (void)blk4 {
        id tstArray = @[@"123", @"234", @"345"];
        id array = [[NSMutableArray alloc] init];
    
        void (^blk)(void) = ^{
            id obj = [[NSObject alloc] init];
            [array addObject:obj];
        };
    
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    而向截获的变量array赋值则会产生编译错误。

    - (void)blk4 {
        id tstArray = @[@"123", @"234", @"345"];
        id array = [[NSMutableArray alloc] init];
    
        void (^blk)(void) = ^{
            id obj = [[NSObject alloc] init];
            array = tstArray;
        };
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    block的实现

    Block的存储域

    Block 和 ——block变量的实质
    在这里插入图片描述
    block既然是OC的对象,那么也存在许多的类似的类。
    在这里插入图片描述
    三种类对应了三种不同的区域:
    在这里插入图片描述

    NSGlobalBlock

    如果一个 block 没有访问外部局部变量,或者访问的是全局变量,或者静态局部变量,此时的 block 就是一个全局 block ,并且数据存储在全局区。

    //block1没有引用到局部变量
        int a = 10;
        void (^block1)(void) = ^{
             NSLog(@"hello world");
        };
        NSLog(@"block1:%@", block1);
    
        //    block2中引入的是静态变量
        static int a1 = 20;
        void (^block2)(void) = ^{
            NSLog(@"hello - %d",a1);
        };
        NSLog(@"block2:%@", block2);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    运行结果:
    在这里插入图片描述
    在Block内部不使用外部变量(不使用应截获的自动变量),或者只使用静态变量和全局变量。

    NSStackBlock

        int b = 10;
        NSLog(@"%@", ^{
            NSLog(@"hello - %d",b);
        });
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    运行结果:
    在这里插入图片描述
    可以发现,这边显示的不是NSStackBlock,原因是:在ARC环境下,系统会自动将block进行拷贝操作。只要改成MRC就行了。
    与MallocBlock一样,可以在内部使用局部变量或者OC属性。但是不能赋值给强引用或者Copy修饰的变量

    NSMallocBlock

    什么时候栈上的 Block 会复制到堆呢?

    • 调用Block的copy实例方法时
    • Block作为函数返回值返回时
    • 将Block 赋值给附有__strong修饰符id类型的类或Block类型成员变量时
    • 在方法名中含有usingBlock的Cocoa框架方法或 Grand Central Dispatch的 API 中传递 Block 时
        int a = 10;
        void (^block1)(void) = ^{
            NSLog(@"%d",a);
        };
        NSLog(@"block1:%@", [block1 copy]);
    
        __block int b = 10;
        void (^block2)(void) = ^{
            NSLog(@"%d",b);
        };
        NSLog(@"block2:%@", [block2 copy]);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    运行结果:
    在这里插入图片描述
    那么三种储存方式有什么区别呢?
    简单来说,没有捕获自动变量的就是数据区,捕获了自动变量但是没有进行copy操作就是栈区,copy之后就变成了堆区。

    深入理解Block的存储域

    为什么Block超出变量作用区域可以存在?
    配置在全局变量上的 Block,从变量作用域外也可以通过指针安全地使用。 但放置在栈上的Block,如果其所属的变量作用城结束,该Blook 就被废弃由于__block 交量也配置在栈上,同样地,如果其所属的变量作用域结束,则该_block 变量也会被废弃。
    在这里插入图片描述
    Blocks提供了把Block和__block复制到堆上的方法,防止block被废弃
    在这里插入图片描述
    复制到堆上的block将NSMallocBlock类对象写入Block用结构体实例的成员变量指针。

    Blocks如何实现复制到堆上

    对于OC来说,当ARC处于有效情况的时候,大多数情况下编译器会自己进行判断,自动生成把block从栈复制到堆上的代码。
    以返回block的函数为例:

    blk_t func(int rate) {
        return ^(int count) {
            return rate * count;
        };
    };
    - (void)testBlk {
        func(4);
        NSLog(@"%@", func(4));
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述
    该代码本应该是返回配置在栈上的block函数当程序结束的时候block应该被废弃,但实际打印出来的结果是mallocblock。
    上面的代码翻译成源码为:
    在这里插入图片描述
    在ARC处于有效状态下,blk_t tmp 和blk_t strong tmp是一样的效果。
    源码的 objc_retainBlock(tmp)其实就是Block_copy函数。

    tmp = Block_copy(tmp)
    return objc_autoReleaseReturnValue(tmp)
    
    • 1
    • 2

    翻译源码的注释看实现栈到堆的过程发生了什么?
    在这里插入图片描述
    也就当Block作为函数的返回值返回的时候,编译器会自动生成复制到堆上的代码。

    大多数情况下编译器会自主的进行判断拷贝,但是如下情况需要手动copy:

    • 向方法或者函数里传递Block时。
      但是在方法或者函数里面适当的复制了传递的参数时,那么就不需要手动复制。
    • GCD的API不需要。
    • cocoa框架方法且方法名包含usingBlock。

    对于Block语法和Block类型变量都可以直接调用copy方法。

    blkCopy = [blk copy];
    
    • 1

    对于配置在栈上的Block我们知道调用copy方法会讲Block从栈复制到堆上,那么存储在数据域和堆上Block调用copy会发生如下情况:
    在这里插入图片描述
    在不确定的情况下调用copy方法不会出现问题,即使是引用计数也有ARC帮我们自动管理。多次调用copy也不会出现问题。

     int (^blk)(int) = ^(int count){
            return count + 1;
        };
        blk = [[[[blk copy] copy] copy] copy];
    
    
    • 1
    • 2
    • 3
    • 4
    • 5

    多次的copy就是一个持有,废弃,持有,废弃。。。的重复过程,对于ARC我们有自动管理,所以不会出现问题。
    在这里插入图片描述

    __block变量的存储域

    __block变量的存储域和引用计数的模式类似,因为__block对象是在Block里被引用或者调用的,当Block被从栈复制到堆时,__block也有一样的变化。
    在这里插入图片描述
    在一个Block使用__block对象的时候,若Block从栈复制到堆,__block对象也从栈复制到堆,此时Block持有__block。
    在这里插入图片描述
    多个Block使用__block对象的时候,当一个Block从栈复制到堆,此时和一个Block情况一样,当其余Block从栈复制到堆的时候,Block会持有__block对象,此时__block对象引用计数加1;
    在这里插入图片描述
    当Block被废弃,那么它持有的__block对象也会被释放,这个是一一对应的关系,__block的思考方式和OC的引用计数内存管理方式完全相同。
    在这里插入图片描述
    通过Block的复制,可以同时访问堆和栈的__block变量。因为此时__block也从栈复制到堆,这与__block结构体使forward ing指针变量有关。

    当栈上的__block被复制到堆上的时候,栈上的__block变量用结构体实例在__block变量从栈复制到堆上的时候,_forwarding成员变量的值从指向栈上的自身替换为目标堆上__block变量用结构体实例的的地址。
    在这里插入图片描述

    截获对象

    在 ARC 中,捕获了外部变量的 block 的类会是 NSMallocBlock 或者 NSStackBlock,如果 block 被赋值给了某个变量,在这个过程中会执行 __Block__copy 将原有的 NSStakeBlock 变成 NSMallocBlock ;但是如果 block没有赋值给某个变量,那他的类型就是 NSStakeBlock ;没有捕获外部变量的 block 的类会是 NSGlobalBlock 既不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。

    在非 ARC 中,捕获了外部变量的 block 的类会是 NSStackBlock ,放在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同。

    书上展示了如下的代码:

    typedef void (^blk_t1) (id);
    //  截获对象
    - (void)blkObj {
        id array = [[NSMutableArray alloc] init];
        blk_t1 blk;
        blk = [^(id obj) {
            [array addObject:obj];
            NSLog(@"array count = %ld", [array count]);
        } copy];
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        
        NSLog(@"%@", blk);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    代码首先生成并持有NSMutableArray对象,由于附有__strong目标变量的作用域立即结束,那么该对象应该被废弃,其强引用失效并释放,但是代码打印结果如图:
    在这里插入图片描述
    结果表示可变数组的类对象在Block的执行过程超出了变量的作用域而存在。
    在这里插入图片描述
    源码理解:
    被赋值的自动变量array对象发现其在Block结构体中是带有__strong关键字的成员变量, 书上的解释是OC库能够很准确的把握从栈复制到堆,以及堆上被废弃的Block 因此即使结构体的成员变量含有__strong或者__weak关键字修饰的变量,编译器也可以适当的初始化和废弃。

    Block源码里出现了 _main_block_copy_0_main_block_depose_0 两个关键函数。

    _main_block_copy_0

    因为带有_-strong修饰符,所以 _main_block_copy_0函数调用 _Block_object_assgin函数来持有该array对象
    在这里插入图片描述
    _Block_object_assgin函数 相当于retain实例方法,将对象赋值在对象类型的结构体成员变量当中。

    _main_block_depose_0

    _main_block_depose_0函数调用 _Block_object_dispose,释放赋值在Block用结构体成员变量array中的对象。
    在这里插入图片描述
    _Block_object_dispose函数 相当于release实例方法,释放在对象类型的结构体成员变量中的对象。

    调用时机

    在这里插入图片描述
    Block复制到堆上的情况:

    • 调用 Block 的 copy 实例方法时。
    • Block 作为函数返回值返回时。
    • 将 Block 赋值给附有strong 修饰符 id 类型的类或 Block 类型成员变量时。
    • 在方法名中含有usingBlock 的 Cocoa 框架方法或 Grand Central Dispatch 的 API 中传递
      Block 时。

    其表面看来是从栈栈复制到堆,本质是当调用到了_Block_copy函数的时候从栈复制到堆
    相对的就是释放的时候,对于谁都不持有的对象会调用_Block_object_dispose函数,相当于dealloc方法,所以解释了上述截获对象array为什么可以超出其变量作用域而存在。

    __block变量和对象

    __block说明符可以指定任何类型的自动变量。

    __block id obj = [[NSObject alloc] init];
    
    • 1

    该代码等同于:

    __block id __strong obj1 = [[NSObject alloc] init];
    
    • 1

    ARC有效的时候 id类型以及对象类型变量必定附加所有权修饰符。

    __block

    该代码通过clang翻译源码如下:
    在这里插入图片描述
    上述出现了 _Block_object_assgin函数和 _Block_object_dispose函数。

    **当在Block使用附有strong修饰符修饰的id类型或对象类型的自动变量时,**当Block从栈复制到堆的时候,使用_Block_object_assgin函数,持有被捕获对象,当堆上的Block被废弃的时候使用_Block_object_dispose函数,释放截获对象。

    对于__block变量会发生相同的事情。所以即使对象赋值到堆上的附有strong修饰符的对象类型的__block变量中,只要__block变量在堆上继续存在,那么该对象就会继续处于被持有的状态。

    __weak

    对于之前的array代码,我们加入__weak修饰符试试.
    这里的代码和书上的有所不同,按书上的我们需要重新初始化array释放,达到效果。

     array = [[NSMutableArray alloc] init];
    
    • 1
    - (void)blkWeak {
        void(^blk)(id);
        id array = [[NSMutableArray alloc] init];
        id __weak weakArray = array;
        array = [[NSMutableArray alloc] init];
        blk = [^(id obj) {
            [weakArray addObject:obj];
            NSLog(@"array count = %ld", [weakArray count]);
        } copy];
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
            
        NSLog(@"%@", blk);
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    在这里插入图片描述
    同时加入__block试一试。
    在这里插入图片描述
    这是因为即使附加了__block说明符,附有__strong修饰符的变量array也会在该变量作用域结束的同时被释放被废弃,nil被赋值给附有__weak修饰符的变量的weakArray中。

  • 相关阅读:
    表结构及索引设计
    OLED之U8g2中文库使用
    大一学生Web课程设计 美食主题网页制作(HTML+CSS+JavaScript)-咖啡 6页
    qt6 多媒体开发代码分析(四、视频播放)
    修改vscode的相对路径计算逻辑
    Python编程题每日一练day2(附答案)
    不可变集合、Lambda表达式、Stream流
    【office办公-pdf篇】pdf合并与拆分让我们摆脱付费软件的功能限制好不好
    Shell脚本:三剑客(AWK)
    对于工作中复杂的业务,使用mabatis-plus加分页插件的话,有时候得不到自己想要的
  • 原文地址:https://blog.csdn.net/qq_72437394/article/details/136762939