OC - Block(一) - 基本认识

前言

对于很多iOS开发者来说,有关block(块)的问题,无论是实际工作,还是面试,被问到的知识点始终都会围绕着以下几点:

  • block的定义和使用
  • block引用外部变量 (__block 、__week)
  • block的内存管理 (堆,栈,全局)
  • 另一篇文章主要介绍block的循环引用 (诱因及解决)

下面就会围绕着这几点,进行逐步的分析说明

一、 block的定义和使用

  • block的定义
    返回值类型(^Block名字)(参数列表);
  • block的形式分为一下几种:
1. 无参、无返回值(这里有两种写法,通常使用第二种)
   // 返回值类型 (^block名字) (参数列表) = ^(参数列表) {代码块};
    void (^DemoBlock)(void) = ^(){
        NSLog(@"我是无参数、无返回值的block");
    };
    DemoBlock();  //  执行block

    // 无参数的情况下,`= ^`后面的`()`可以省略 - (常用写法)
    void (^DemoBlock)(void) = ^{
        NSLog(@"我是无参数、无返回值的block");
    };
    DemoBlock();
2. 有参、无返回值的block
    // 返回值类型 (^block名字) (参数列表) = ^(参数列表) {代码块};
    void (^DemoBlock)(int, int) = ^(int a, int b){
        NSLog(@"a + b = %d",a + b);
    };
    DemoBlock(10,10);  //  执行block
3. 有参、有返回值
    int (^DemoBlock1)(int, int) = ^(int a, int b){
        return a + b;
    };
    int result = DemoBlock1(10,10);
    NSLog(@"block返回的结果是 -- %d", result);
4. 无参、有返回值(不常用)
    void (^DemoBlock1)(int, int) = ^(int a, int b){
         NSLog(@"a + b = %d",a + b);
    };
    DemoBlock1(10,10);
5. block通常使用typedef来取别名,例:
   // 定义一个带有两个参数的,返回值是int类型的block
   typedef int (^DemoBlock)(int , int);

   // 这时,DemoBlock就成为了一种类型,可以用这个类型来定义block变量,例:
   @property (nonatomic,copy) DemoBlock myBlock;
   // 或者
   DemoBlock myBlock = ^(int a, int b){
       return a + b;
   };

可能你想问,为什么@property后面的修饰符要用copy,后续再说到block的内存管理时,会特别讲到。

说明:以上重要是为了让大家熟悉block的定义写法,有参数无参数,具体应用具体参考即可

二、 block使用外部变量

如果对上述的block的定义格式很熟悉了,请继续阅读,否则还请先熟练地手写block的定义和使用,会更有所帮助呢...
先来看几个例子,请确认输出结果是什么:

About Block.png

答案可以看过下面的文章之后,然后自己写代码加深印象,会更有助于对block引用外部变量的理解 ^-^


1. 截获自动变量(局部变量)值

block对于外部变量的引用,默认是将变量复制到block的内部,特别要注意的是默认情况下block只能访问不能修改局部变量的值。默认情况下,block引用外部变量,是将变量以const的形式,copy到block内部,因此不允许对const进行修改

    int count = 10;
    void (^DemoBlock)(void) = ^{
        NSLog(@"block内部:count = %d, address = %p",count , &count);
    };
    count = 20;
    NSLog(@"block外部:count = %d, address = %p",count , &count);
    DemoBlock();

输出结果:

    block外部:count = 20, address = 0x7ffee4093a6c
    block内部:count = 10, address = 0x604000254f60

我们发现:

  • block内部引用了外部的变量之后,count变量的地址发生了变化,说明是将count变量重新copy了一份在自己的block区域内(由栈区copy到堆区(
  • 即使在block外部如何对count变量进行修改,都不会影响block内部的变量的值(因为两个地址不同,block外的变量仍然是在栈区)
  • 如果在block内部对count变量进行修改,则会报如下错误(在block内,强制将外部的count值修改为30)
    block内部修改外部变量.png
    接下来,__block终于要出场了
2. __block 修饰的外部变量

对于使用__block修饰的外部变量,block是copy变量的地址到block内部,从而达到可以修改的目的

    __block int count = 10;
    void (^DemoBlock)(void) = ^{
        count = 30; // 这里会报错,要求我们用`__block`来修改外部的count变量
        NSLog(@"block内部:count = %d, address = %p",count , &count);
    };
    NSLog(@"block外部:count = %d, address = %p",count , &count);
    DemoBlock();

输出结果:

    block外部:count = 10, address = 0x60400003eed8
    block内部:count = 30, address = 0x60400003eed8

我们惊奇的发现,一旦使用了__block修饰的外部变量,不论是在block内不还是外部,变量地址相同,也就从而达到了可以修改的目的。(注意:一旦使用了__block修饰外部变量,这个变量就始终在堆区,而不是在栈区)

三、 block的内存管理

想要了解block内存相关的知识,先要知道程序内存的分配情况,你需要知道:
内存分区.jpeg

这里主要说明三个区域:

  • 全局/静态区:在程序中定义的全局变量、常量存储于此(不能被修改、随着程序结束才被释放)
  • 栈区:在程序中定义的一些局部变量,由系统自动分配和回收,不需要程序员管理变量出了函数作用域会被系统自动回收并释放内存空间
  • 堆区:由程序员主动申请释放,例如alloc、new操作堆区的内容,是由程序员来控制申请和释放。在程序结束后,系统也会统一回收
  • 注⚠️:本文主要讲解关于block的内容,更多关于内存的知识点,有更多的大牛详细介绍,本文只需要了解堆内存栈内存特性即可

那么相应的,block也分为三种

  • 全局block(块)(_NSConcreteGlobalBlock):存在于全局内存中, 相当于单例.程序结束的时候才被释放
  • 栈block(块) (_NSConcreteStackBlock):存在于栈内存中, 超出其作用域则马上被销毁
  • 堆block(块)(_NSConcreteMallocBlock):存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存

那么问题来了,遇到一个Block,我们怎么这个Block的存储位置呢?

1. Block没有访问外界变量(包括栈中和堆中的变量)

Block 既不在栈又不在堆中,在代码段中,ARC和MRC下都是如此。此时为全局块。

2. Block访问外界变量
  • MRC 环境下:访问外界变量的 Block 默认存储栈中。
  • ARC 环境下:访问外界变量的 Block 默认存储在堆中(实际是放在栈区,然后ARC情况下自动又拷贝到堆区),自动释放。

那么你或许想问:在ARC下,访问外界变量的Block为什么要自动从栈区拷贝到堆区呢?(也是算是面试题)
答:这正是由于ARC的特性,结合栈区的特点,如同一般的变量一样,出了作用域后,会自动释放掉,如下图

栈block.png

那么栈block也是如此。为了保证栈block不会随着作用域而释放,导致后续无法使用的情况,系统采用这种copy机制,由栈区拷贝到堆区,让程序员来决定何时释放。如图:
堆block.png

特别要说明一点,当使用@property来定义block的时候,这样:

    @property (nonatomic,copy) DemoBlock myBlock;

使用的是cpoy关键字来修饰,原因就是上述所说,DemoBlock的对象myblock作为变量,防止在栈区被释放,需要用copy,从栈区copy到堆区

PS:本人写了个菜单功能,动态菜单、任意位置弹出。喜欢的点颗小星星、Github怼我

你可能感兴趣的:(OC - Block(一) - 基本认识)