《Objective-C 高级编程》学习笔记2-Blocks

857 阅读7分钟

狮子头镇楼

《Objective-C 高级编程》学习笔记1-内存管理

《Objective-C 高级编程》学习笔记3-GCD

参考资料:

《Objective-C高级编程》Blocks

《Objective-C 高级编程》干货三部曲(二):Blocks篇

俗话说好记性不如烂笔头,参考了大佬的智慧,看完狮子头后总结的学习笔记。如有侵权请友情告知。


1、什么是Blocks

Blocks是C语言的扩充功能:带有局部变量的匿名函数;

Blocks中将带有局部变量的匿名函数部分称为“Block literal”,或简称“Block”;

“带有局部变量的匿名函数”这一概念并不仅指Blocks,它还存在于其他程序语言中,在计算机科学中,此概念也称为必包。

2、Blocks模式

Block语法:


Block例子:

//1、可用 typedef 给Block定义别名
typedef int (^blk_t)(int);

int blockDemo() {
    blk_t blk = ^(int count) {
        return count + 1;
    };
    
    int a = blk(6);
    printf("a = %d\n", a);
    
    //2、Block会截获所使用的局部变量值,即保存该局部变量的瞬间值;
    int b = 111;
    //3、使用附有 __block 说明符的局部变量可在Block中赋值,该变量称为 __block 变量
    __block int c = 222;
    
    void (^blk_b)(void) = ^ {
        printf("b1 = %d\n", b);
        printf("c1 = %d\n", c);
        c += b;
    };
    
    b = 222;
    blk_b();
    
    printf("b2 = %d\n", b);
    printf("c2 = %d\n", c);
    
    return 0;
}

输出结果是:


3、Blocks的实现

3.0   OC转C的方法,可通过 clang 指令转换源代码


转换后可以在该文件夹得到一个:block.cpp,转换得到的C++代码就在其中。

3.1   Block语法转换

OC代码:

int mainDemo() {
    void (^blk)(void) = ^{
        printf("Block\n");
    };
  
    blk();
    return 0;
}

转换成C++后:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};

struct __mainDemo_block_impl_0 {
  struct __block_impl impl;
  struct __mainDemo_block_desc_0* Desc;
  __mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

//通过 Blocks 使用的匿名函数实际上被作为简单的C语言函数来处理
//参数 __cself 为指向 Block 值的变量
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    printf("Block\n");
}

static struct __mainDemo_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __mainDemo_block_desc_0_DATA = {
    0,
    sizeof(struct __mainDemo_block_impl_0)
};

int mainDemo() {
    void (*blk)(void) = ((void (*)())&__mainDemo_block_impl_0((void *)__mainDemo_block_func_0,
                                                              &__mainDemo_block_desc_0_DATA));

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

    // 去掉转换部分:
    // struct __mainDemo_block_impl_0 tmp = __mainDemo_block_impl_0(__mainDemo_block_func_0, &__mainDemo_block_desc_0_DATA);
    // struct __mainDemo_block_impl_0 *blk = &tmp;
    
    return 0;
}

3.2   Objective-C类和对象的实质

//“id”这一变量类型用于存储OC对象,它的声明如下:
typedef struct objc_object {
    Class isa;
} *id;
//id 为 objc_objc 结构体的指针类型

//Class 为 objc_calss 结构体的指针类型
typedef struct objc_class *Class;
//objc_class 的声明如下:
struct objc_class {
    Class isa;  
};

//objc_object 结构体和 objc_class 结构体是在各个对象和类的实现中使用的最基本的结构体。

举个例子,定义一个OC类:MyObject

@interface MyObject : NSObject {
    int val0;
    int val1;
}
@end

基于 objc_object 结构体,MyObject类的对象结构体如下:

struct MyObject {
    Class isa;//通过成员变量 isa 保持该类的结构体实例指针
    int val0;
    int val1;
}

那么MyObject的实质:


各类的结构体就是基于 objc_class 结构体的 class_t 结构体。class_t 结构体的声明如下:

struct class_t {
    struct class_t *isa;
    struct class_t *superclass;
    Cache cache;
    IMP *vtable;
    uintptr_t data_NEVER_USE;
};
//该结构体实例会持有声明的成员变量、方法的名称、方法的实现(即函数指针)、属性以及父类的指针

3.3   Block的实质:为Objective-C对象

//Block结构体
struct __mainDemo_block_impl_0 {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
    struct __mainDemo_block_desc_0* Desc;
};
// __mainDemo_block_impl_0 结构体相当于基于 objc_object 结构体的OC类对象的结构体。

// 对于其中成员变量 isa 进行初始化:
isa = &_NSConcreteStackBlock;

// 即 _NSConcreteStackBlock 相当于 class_t 结构体实例。
// 在将 Block 作为 OC 的对象处理时,关于该类的信息放置于 _NSConcreteStackBlock 中。

4、截获局部变量

 “截获局部变量值”意味着在执行 Block 语法时,Block 语法表达式所使用的局部变量值被保存到 Block 的结构体实例中。

OC代码:

int mainDemo() {
    int dmy = 256;
    int val = 10;
    const char *fmt = "var = %d\n";
    
    void (^blk)(void) = ^{
        printf(fmt, val);
    };
    
    val = 2;
    fmt = "These values were changed. var = %d\n";
    
    blk();
    return 0;
}

转换后C++代码核心部分:

struct __mainDemo_block_impl_0 {
    struct __block_impl impl;
    struct __mainDemo_block_desc_0* Desc;
    
    //3、block中未使用的局部变量不会被截获
    //4、block中使用的局部变量,截获的是自动变量值
    const char *fmt;
    int val;
};

static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    // “截获局部变量值”意味着在执行 Block 语法时
    // Block 语法表达式所使用的局部变量值被保存到 Block 的结构体实例中
    const char *fmt = __cself->fmt;
    int val = __cself->val;
    printf(fmt, val);
}

int mainDemo() {
    int dmy = 256;
    int val = 10;
    const char *fmt = "var = %d\n";
    
    struct __mainDemo_block_impl_0 tmp = __mainDemo_block_impl_0(__mainDemo_block_func_0, 
&__mainDemo_block_desc_0_DATA, fmt, val);
    struct __mainDemo_block_impl_0 *blk = &tmp;
    
    val = 2;
    fmt = "These values were changed. var = %d\n";

    blk->FuncPtr(blk);    
    return 0;
}   

5、解决 Block 中不能保存值的问题

C语言的函数中可能使用的变量:

  • 局部变量
  • 函数的参数
  • 静态变量
  • 静态全局变量
  • 全局变量

其中,在函数的多次调用之间能够传递值的变量有:

  • 静态变量
  • 静态全局变量
  • 全局变量

5.1   在函数的多次调用之间能够传递值的变量,在 Block 中也可以改写值

  • 对 静态全局变量 和 全局变量 的访问于转换前完全相同
  • 对 静态变量 是通过把静态变量的指针传递给Block结构体保存后,进行访问

OC代码:

int global_val = 1;
static int static_global_val = 2;

int mainDemo() {
    static int static_val = 3;

    void (^blk)(void) = ^{
        global_val *= 1;
        static_global_val *= 2;
        static_val *= 3;
    };
    
    blk();
    return 0;
}

转换成C++后:

int global_val = 1;
static int static_global_val = 2;

struct __mainDemo_block_impl_0 {
    struct __block_impl impl;
    struct __mainDemo_block_desc_0* Desc;

    int *static_val; 

    __mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc, 
                            *_static_val, int flags=0) : static_val(_static_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

//block函数部分
static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    int *static_val = __cself->static_val;
    global_val *= 1;
    static_global_val *= 3;
    (*static_val) *= 3;
}

static struct __mainDemo_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __mainDemo_block_desc_0_DATA = {
    0,
    sizeof(struct __mainDemo_block_impl_0),
};

int mainDemo() {
    static int static_val = 3;

    blk = &__mainDemo_block_impl_0(__mainDemo_block_func_0, 
        &__mainDemo_block_desc_0_DATA, &static_val);

    return 0;
}

5.2   __block 存储域类说明符

OC代码:

int mainDemo() {
    __block int block_val = 10;
    
    void (^blk)(void) = ^{
        block_val = 100;
    };

    blk();
    return 0;
}

转换成C++后:

// __block类型结构体
struct __Block_byref_block_val_0 {
    void *__isa;
    __Block_byref_block_val_0 *__forwarding;//指向自己的指针
    int __flags;
    int __size;
    int block_val;//通过__forwarding进行访问
};

struct __mainDemo_block_impl_0 {
    struct __block_impl impl;
    struct __mainDemo_block_desc_0* Desc;

    __Block_byref_block_val_0 *block_val;    

    __mainDemo_block_impl_0(void *fp, struct __mainDemo_block_desc_0 *desc, 
                             __Block_byref_block_val_0 *_block_val, int flags=0) 
                            : block_val(_block_val->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};

static void __mainDemo_block_func_0(struct __mainDemo_block_impl_0 *__cself) {
    __Block_byref_block_val_0 *block_val = __cself->block_val;
    (block_val->__forwarding->block_val) = 100;
}

static void __mainDemo_block_copy_0(struct __mainDemo_block_impl_0*dst, struct __mainDemo_block_impl_0*src) {
    _Block_object_assign((void*)&dst->block_val, (void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static void __mainDemo_block_dispose_0(struct __mainDemo_block_impl_0*src) {
    _Block_object_dispose((void*)src->block_val, 8/*BLOCK_FIELD_IS_BYREF*/);
}

static struct __mainDemo_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __mainDemo_block_impl_0*, struct __mainDemo_block_impl_0*);
    void (*dispose)(struct __mainDemo_block_impl_0*);
} __mainDemo_block_desc_0_DATA = {
    0,
    sizeof(struct __mainDemo_block_impl_0),
    __mainDemo_block_copy_0,
    __mainDemo_block_dispose_0
};

int mainDemo() {
    __Block_byref_block_val_0 block_val = {
        0,
        &block_val, 
        0, 
        __Block_byref_block_val_0, 
        10
    };

    blk = __mainDemo_block_impl_0(
    __mainDemo_block_func_0, &__mainDemo_block_desc_0_DATA, &val, 0x22000000);
    
    return 0;
}

  • __block 变量会变成栈上生成的 __Block_byref_block_val_0 结构体实例
  • 该结构体持有原局部变量的成员变量
  • __Block_byref_block_val_0 结构体实例的成员变量 __forwarding 持有指向该实例自身的指针
  • 通过 __forwarding 成员变量访问成员变量val
  • __block 变量的 __Block_byref_block_val_0 结构体并不在 Block 用 __mainDemo_block_impl_0 结构体中,这样做是为了在多个 Block 中使用 __block 变量


6、Block 存储域

  • Block 会转换为 Block 的结构体类型的自动变量
  • __block 变量转换为 __block 变量的结构体类型的自动变量

将 Block 当作 OC对象 来看时,Block 有如下种类:

  • _NSConcreteStackBlock
  • _NSConcreteGlobalBlock
  • _NSConcreteMallocBlock

1、Block 何时为 _NSConcreteGlobalBlock  类对象:

  1. 记述全局变量的地方使用 Block 语法时
  2. Block 语法的表达式中不实用应截获的自动变量时

2、设置在栈上的Block,如果其所属的变量作用域结束,该 Block 就被废弃,__block 变量也会被废弃:


3、 Blocks 提供了将 Block 和 __block 变量从栈上复制到堆上的方法来解决 


4、各种 Block 调用 copy 方法的效果:


5、栈上 Block 复制到堆上的时机:

  • 调用 Block 的 copy 实例方法时
  • Block 作为函数返回值返回时
  • 将 Block 赋值给附有 __strong 修饰符 id 类型的类或 Block 类型成员变量时
  • 在 GCD 的API中传递Block时

7、__block 变量存储域


  1. 在任何一个 Block 从栈复制到堆时,__block 变量也会一并从栈复制到堆并被该 Block 所持有。当剩下的 Block 从栈复制到堆时,被复制的 Block 持有 __block 变量,并增加 __block 变量的引用计数。
  2. 如果配置在堆上的 Block 被废弃,那么它所使用的 __block 变量也就被释放。此思考方式与 OC 的引用计数式内存管理相同。
  3. __block 变量用结构体成员变量 __forwarding 的原因:


通过该功能,无论在 Block 语法位置,还是 __block 变量配置在栈上或堆上,都可以顺利访问同一个 __block 变量。