OC - Block(一) - 基本认识

2018-07-05  本文已影响35人  KongPro

前言

对于很多iOS开发者来说,有关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

我们发现:

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

这里主要说明三个区域:

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

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

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

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

2. Block访问外界变量

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

栈block.png
那么栈block也是如此。为了保证栈block不会随着作用域而释放,导致后续无法使用的情况,系统采用这种copy机制,由栈区拷贝到堆区,让程序员来决定何时释放。如图: 堆block.png
特别要说明一点,当使用@property来定义block的时候,这样:
    @property (nonatomic,copy) DemoBlock myBlock;

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

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

上一篇 下一篇

猜你喜欢

热点阅读