iOS – Block

1. Block

1. Block

1.1 什么是Block

  从前都以对block的简要实用,那里再一次驾驭下。

  代码块Block是苹果在iOS四起来引进的对C语言的扩充,完毕匿名函数的特征,Block是1种特殊的数据类型,其能够健康定义变量、作为参数、作为重回值,特殊的,block还能保存一段代码,在急需的时候调用,如今Block广泛的使用iOS开发中,常用于GCD、动画、排序及各项回调。

  注:Block的扬言与赋值只是保存了1段代码段,必须调用才能实施内部的代码。  

1.1 什么是Block

  此前都以对block的简约实用,那里再一次精晓下。

  代码块Block是苹果在iOS4从头引进的对C语言的扩大,完毕匿名函数的表征,Block是一种奇特的数据类型,其能够正常定义变量、作为参数、作为重回值,特殊的,block还足以保留一段代码,在须要的时候调用,近期Block广泛的应用iOS开发中,常用来GCD、动画、排序及各种回调。

  注:Block的证明与赋值只是保存了1段代码段,必须调用才能举办内部的代码。  

一.2 Block简单的接纳

Block的声明:

Block变量的声明格式为: 返回值类型(^Block名字)(参数列表);

// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);

// 形参变量名称可以省略,只留有变量类型即可
void(^aBlock)(NSString *, NSString *);

 Block的赋值:

Block变量的赋值格式为: Block变量 = ^(参数列表){函数体};

aBlock = ^(NSString *x, NSString *y){
    NSLog(@"%@ love %@", x, y);
};

Block申明并赋值:

int(^myBlock)(int) = ^(int num){
    return num * 7;
};

// 如果没有参数列表,在赋值时参数列表可以省略
void(^aVoidBlock)() = ^{
    NSLog(@"I am a aVoidBlock");
};

Block 变量的调用;

// 调用后控制台输出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");

// 调用后控制台输出"result = 63"
NSLog(@"result = %d", myBlock(9));

// 调用后控制台输出"I am a aVoidBlock"
aVoidBlock();

壹.二 Block不难的利用

Block的声明:

Block变量的声明格式为: 返回值类型(^Block名字)(参数列表);

// 声明一个无返回值,参数为两个字符串对象,叫做aBlock的Block
void(^aBlock)(NSString *x, NSString *y);

// 形参变量名称可以省略,只留有变量类型即可
void(^aBlock)(NSString *, NSString *);

 Block的赋值:

Block变量的赋值格式为: Block变量 = ^(参数列表){函数体};

aBlock = ^(NSString *x, NSString *y){
    NSLog(@"%@ love %@", x, y);
};

Block声明并赋值:

int(^myBlock)(int) = ^(int num){
    return num * 7;
};

// 如果没有参数列表,在赋值时参数列表可以省略
void(^aVoidBlock)() = ^{
    NSLog(@"I am a aVoidBlock");
};

Block 变量的调用;

// 调用后控制台输出"Li Lei love Han Meimei"
aBlock(@"Li Lei",@"Han Meimei");

// 调用后控制台输出"result = 63"
NSLog(@"result = %d", myBlock(9));

// 调用后控制台输出"I am a aVoidBlock"
aVoidBlock();

二. Block 数据结构

二. Block 数据结构

二.1 Block 数据结构不难认识

block的数据结构定义如下:

图片 1

对应的构造体定义如下:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

 通过上边我们能够领略,叁个block实例实际上由6片段组成:

  1. isa 指针,全部指标都有该指针,用于落到实处指标相关的效果。
  2. flags,用于按bit位表示1些block的叠加消息,本文后边介绍 block copy
    的贯彻代码能够见见对该变量的选取
  3. reserved 保留变量
  4. invoke 函数指针,指向具体的block 达成的函数调用地址
  5. descriptor 代表该block的增大描述音讯,首假如size大小,以及 copy 和
    dispose 函数的指针。
  6. variables , capture
    过来的变量,block能够访问它表面包车型客车壹部分变量,就是因为将那么些变量(或变量的地方)复制到了结构体中。

在 OC 语言中,一共有 三 种档次的 block:

  1. _NSConcreteGlobalBlock 全局的静态 block,不会访问任何外部变量。
  2. _NSConcreteStackBlock 保存在栈中的 block,当函数重回时会被灭绝
  3. _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0
    时会被灭绝。

赶上3个Block,我们怎么显著那一个Block的囤积地点吗?

a。Block不访问外界变量(包涵栈仲春堆中的变量)

Block既不在栈又不在堆中,在代码段中,AENVISIONC和M索罗德C都以那般,此时为大局块。

b。Block访问外界变量

MEnclaveC 环境下:访问外界变量的Block暗中认可存储在栈中。

ATucsonC
环境下:访问外界变量的Block暗许存款和储蓄在堆中(实际是身处栈区,然后A奥迪Q5C情形下活动又拷贝到堆区),自动释放。

2.1 Block 数据结构简单认识

block的数据结构定义如下:

图片 2

对应的协会体定义如下:

struct Block_descriptor {
    unsigned long int reserved;
    unsigned long int size;
    void (*copy)(void *dst, void *src);
    void (*dispose)(void *);
};
struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

 通过上面大家得以理解,三个block实例实际上由陆有的构成:

  1. isa 指针,全体目的都有该指针,用于落到实处目的相关的成效。
  2. flags,用于按bit位表示壹些block的叠加新闻,本文前边介绍 block copy
    的贯彻代码可以见到对该变量的选取
  3. reserved 保留变量
  4. invoke 函数指针,指向具体的block 完成的函数调用地址
  5. descriptor 代表该block的附加描述新闻,首要是size大小,以及 copy 和
    dispose 函数的指针。
  6. variables , capture
    过来的变量,block能够访问它外表的一对变量,正是因为将那些变量(或变量的地址)复制到了结构体中。

在 OC 语言中,壹共有 三 种档次的 block:

  1. _NSConcreteGlobalBlock 全局的静态 block,不会造访任何外部变量。
  2. _NSConcreteStackBlock 保存在栈中的 block,当函数重临时会被灭绝
  3. _NSConcreteMallocBlock 保存在堆中的 block,当引用计数为 0
    时会被灭绝。

相遇一个Block,我们怎么分明这些Block的储存地方吗?

a。Block不访问外界变量(蕴涵栈花潮堆中的变量)

Block既不在栈又不在堆中,在代码段中,A帕杰罗C和M大切诺基C都以这么,此时为大局块。

b。Block访问外界变量

MPAJEROC 环境下:访问外界变量的Block暗许存储在栈中。

AQX56C
环境下:访问外界变量的Block默许存款和储蓄在堆中(实际是置身栈区,然后ASportageC意况下活动又拷贝到堆区),自动释放。

2.2 NSConcreteGlobalBlock 类型的 block 的实现

我们能够新建八个block壹.c文书:

#include <stdio.h>
int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

 在顶峰输入 clang -rewrite-objc block1.c ,就足以在目录中来看 clang
输出了贰个 block1.cpp 的文件,那一个文件正是 block 在 C 语言的贯彻:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("Hello, World!\n");
}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
int main()
{
    (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
    return 0;
}
  1.   一个block实际正是1个目的,它最首要由二个 isa 和二个 impl 和多少个descriptor 组成。
  2. 那里我们看出 isa 指向的依然 _NSConcreteStackBlock,但在 LLVM
    的完结中,开启 A奇骏C 时,block 应该是 _NSConcreteGlobalBlock
    类型。感觉是当三个 block 被声称的时候,它都以1个
    _NSConcreteStackBlock类的对象。
  3. impl 是实在的函数指针,本例中,它指向 _main_block_func_0。那里的
    impl 也正是事先提到的 invoke 变量,只是 clang
    编译器对变量的命名不等同。
  4. descriptor 是用以描述当前这一个 block
    的附加音讯的,包罗结构体的尺寸,须要 捕获 和 处理
    的变量列表等。结构体大小须要保留是因为,每一种 block 因为会 捕获
    1些变量,那个变量会加到 __main_block_impl_0
    这几个结构体中,让其容量变大。前面会看出有关代码。

2.2 NSConcreteGlobalBlock 类型的 block 的实现

咱俩得以新建二个block1.c文书:

#include <stdio.h>
int main()
{
    ^{ printf("Hello, World!\n"); } ();
    return 0;
}

 在极端输入 clang -rewrite-objc block一.c ,就足以在目录中看看 clang
输出了三个 block一.cpp 的文件,这一个文件正是 block 在 C 语言的兑现:

struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void *FuncPtr;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    printf("Hello, World!\n");
}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0) };
int main()
{
    (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA) ();
    return 0;
}
  1.   2个block实际便是一个指标,它根本由多个 isa 和一个 impl 和一个descriptor 组成。
  2. 此间大家看出 isa 指向的照旧 _NSConcreteStackBlock,但在 LLVM
    的落到实处中,开启 A途观C 时,block 应该是 _NSConcreteGlobalBlock
    类型。感觉是当二个 block 被声称的时候,它都以叁个
    _NSConcreteStackBlock类的指标。
  3. impl 是实际的函数指针,本例中,它指向 _main_block_func_0。那里的
    impl 也便是事先涉嫌的 invoke 变量,只是 clang
    编写翻译器对变量的命名不雷同。
  4. descriptor 是用以描述当前以此 block
    的附加新闻的,包蕴结构体的轻重缓急,必要 捕获 和 处理
    的变量列表等。结构体大小须求保留是因为,各个 block 因为会 捕获
    一些变量,这个变量会加到 __main_block_impl_0
    那一个结构体中,让其体量变大。后边会看出有关代码。

2.3 NSConcreteStackBlock 类型的 block 的实现

我们其余新建八个名称叫 block二.c 的文书,输入一下剧情:

#include <stdio.h>
int main() {
    int a = 100;
    void (^block2)(void) = ^{
        printf("%d\n", a);
    };
    block2();
    return 0;
}

 再一次利用 clang 工具,转换后的严重性代码如下:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    printf("%d\n", a);
}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
    int a = 100;
    void (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
    return 0;
}

  在本例中,大家得以看出:

  1. 本例中,isa 指向
    _NSConcreteStackBlock,表达那是叁个分配在栈上的实例。
  2. main_block_impl_0
    中追加了一个变量a,在block中援引的变量a实际上是在注明block时,被复制到
    main_block_impl_0
    结构体中的这些变量a。y因为那样,我们就能通晓,在block内部修改变量a的剧情,不会潜移默化外部的莫过于变量a。
  3. main_block_impl_0
    中由于扩张了叁个变量a,所以结构体的大大小小变了,该结构体大小被写在了
    main_block_desc_0 中。

咱俩修改下面的代码,在变量后面扩张 __block 关键字:

#include <stdio.h>
int main()
{
    __block int i = 1024;
    void (^block1)(void) = ^{
        printf("%d\n", i);
        i = 1023;
    };
    block1();
    return 0;
}

  生成的机要代码如下,能够看看,差距十分大:

struct __Block_byref_i_0 {
    void *__isa;
    __Block_byref_i_0 *__forwarding;
    int __flags;
    int __size;
    int i;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_i_0 *i; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_i_0 *i = __cself->i; // bound by ref
    printf("%d\n", (i->__forwarding->i));
    (i->__forwarding->i) = 1023;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
    void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344);
    ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    return 0;
}

  从代码中大家可以见到:

  1. 源码中追加多个名叫 __block_byref_i_0 的结构体,用来保存大家要
    捕获 并且修改的变量 i。
  2. main_block_impl_0 引用的是 Block_byref_i_0
    的结构体指针,那样就能够直达修改外部变量的效益。
  3. __Block_byref_i_0 结构体中涵盖 isa,表达它也是3个指标。
  4. 大家要求承担 Block_byref_i_0 结构体相关的内部存款和储蓄器管理,所以
    main_block_desc_0 中追加了 copy 和 dispose
    函数指针,对于在调用前后修改响应变量的引用计数。

干什么采纳__block 修饰的表面变量的值就能够被block修改呢?

作者们发现二个有的变量加上 __block
修饰符后竟是跟block1样成为了一个__Block_byref_i_0结构体类型的机关变量实例。此时大家在block内部访问
i 变量则供给经过二个叫 __forwarding 的积极分子变量来直接待上访问 i 变量。

__block 变量和 __forwarding

在copy操作之后,既然__block变量也被copy到堆上去了,那么访问该变量是访问栈上依旧堆上的呢?

图片 3

通过__forwarding, 无论是在block中照旧 block外访问__block变量,
也不管该变量在栈上或堆上, 都能顺遂地走访同叁个__block变量。

2.3 NSConcreteStackBlock 类型的 block 的实现

咱俩别的新建一个名称为 block二.c 的公文,输入一下内容:

#include <stdio.h>
int main() {
    int a = 100;
    void (^block2)(void) = ^{
        printf("%d\n", a);
    };
    block2();
    return 0;
}

 再次行使 clang 工具,转换后的显要代码如下:

struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    int a;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    printf("%d\n", a);
}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
int main()
{
    int a = 100;
    void (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
    return 0;
}

  在本例中,大家能够看出:

  1. 本例中,isa 指向
    _NSConcreteStackBlock,表达那是四个分红在栈上的实例。
  2. main_block_impl_0
    中加进了一个变量a,在block中引用的变量a实际上是在表明block时,被复制到
    main_block_impl_0
    结构体中的那1个变量a。y因为如此,我们就能领略,在block内部修改变量a的内容,不会影响外部的其实变量a。
  3. main_block_impl_0
    中由于增加了二个变量a,所以结构体的轻重变了,该结构体大小被写在了
    main_block_desc_0 中。

咱俩修改下面的代码,在变量后边扩充 __block 关键字:

#include <stdio.h>
int main()
{
    __block int i = 1024;
    void (^block1)(void) = ^{
        printf("%d\n", i);
        i = 1023;
    };
    block1();
    return 0;
}

  生成的最首要代码如下,能够看看,差距十分大:

struct __Block_byref_i_0 {
    void *__isa;
    __Block_byref_i_0 *__forwarding;
    int __flags;
    int __size;
    int i;
};
struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0* Desc;
    __Block_byref_i_0 *i; // by ref
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
    }
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    __Block_byref_i_0 *i = __cself->i; // bound by ref
    printf("%d\n", (i->__forwarding->i));
    (i->__forwarding->i) = 1023;
}
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
static struct __main_block_desc_0 {
    size_t reserved;
    size_t Block_size;
    void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
    void (*dispose)(struct __main_block_impl_0*);
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
int main()
{
    __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
    void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344);
    ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
    return 0;
}

  从代码中大家得以见到:

  1. 源码中加进三个名称叫 __block_byref_i_0 的结构体,用来保存大家要
    捕获 并且修改的变量 i。
  2. main_block_impl_0 引用的是 Block_byref_i_0
    的结构体指针,那样就足以达到修改外部变量的效劳。
  3. __Block_byref_i_0 结构体中含有 isa,表达它也是一个指标。
  4. 大家须要承担 Block_byref_i_0 结构体相关的内部存款和储蓄器管理,所以
    main_block_desc_0 中加进了 copy 和 dispose
    函数指针,对于在调用前后修改响应变量的引用计数。

何以采用__block 修饰的外部变量的值就足以被block修改呢?

我们发现3个片段变量加上 __block
修饰符后甚至跟block一样成为了叁个__Block_byref_i_0结构体类型的自动变量实例。此时大家在block内部访问
i 变量则要求通过多个叫 __forwarding 的成员变量来直接待上访问 i 变量。

__block 变量和 __forwarding

在copy操作之后,既然__block变量也被copy到堆上去了,那么访问该变量是访问栈上依旧堆上的吧?

图片 4

通过__forwarding, 无论是在block中照旧 block外访问__block变量,
也不管该变量在栈上或堆上, 都能胜利地走访同1个__block变量。

2.3 NSConcreteMallocBlock 类型的 block 的实现

NSConcreteMallocBlock 类型的 block
经常不会在源码中一向出现,因为默许它是当一个 block 被 copy
的时候,才会将以此 block 赋值到堆中。以下是八个 block 被copy
时的示范代码,能够见到,在第7步,指标的 block 类型被改动为
_NSConcreteMallocBlock。

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    // 1
    if (!arg) return NULL;
    // 2
    aBlock = (struct Block_layout *)arg;
    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    // 8
    result->isa = _NSConcreteMallocBlock;
    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

2.3 NSConcreteMallocBlock 类型的 block 的实现

NSConcreteMallocBlock 类型的 block
平日不会在源码中央直机关接出现,因为默许它是当三个 block 被 copy
的时候,才会将以此 block 赋值到堆中。以下是3个 block 被copy
时的言传身教代码,能够看看,在第拾步,目的的 block 类型被修改为
_NSConcreteMallocBlock。

static void *_Block_copy_internal(const void *arg, const int flags) {
    struct Block_layout *aBlock;
    const bool wantsOne = (WANTS_ONE & flags) == WANTS_ONE;
    // 1
    if (!arg) return NULL;
    // 2
    aBlock = (struct Block_layout *)arg;
    // 3
    if (aBlock->flags & BLOCK_NEEDS_FREE) {
        // latches on high
        latching_incr_int(&aBlock->flags);
        return aBlock;
    }
    // 4
    else if (aBlock->flags & BLOCK_IS_GLOBAL) {
        return aBlock;
    }
    // 5
    struct Block_layout *result = malloc(aBlock->descriptor->size);
    if (!result) return (void *)0;
    // 6
    memmove(result, aBlock, aBlock->descriptor->size); // bitcopy first
    // 7
    result->flags &= ~(BLOCK_REFCOUNT_MASK);    // XXX not needed
    result->flags |= BLOCK_NEEDS_FREE | 1;
    // 8
    result->isa = _NSConcreteMallocBlock;
    // 9
    if (result->flags & BLOCK_HAS_COPY_DISPOSE) {
        (*aBlock->descriptor->copy)(result, aBlock); // do fixup
    }
    return result;
}

3. 变量的复制

对于 block
外的变量引用,block暗中认可是将其复制到其数据结构中来促成访问的,相当于说block的电动变量只针对block内部选择的机关变量,不使用则不截获,因为截获的自行变量会蕴藏于block的结构体内部,会促成block容积变大,暗许情形下
block 只可以访问不能够修改部分变量的值,如下图所示:

图片 5

对于 __block 修饰的表面变量引用,block
是复制其引用地址来贯彻访问的,block能够修改__block
修饰的外部变量的值,如下图所示:

图片 6

 

3. 变量的复制

对于 block
外的变量引用,block私下认可是将其复制到其数据结构中来兑现访问的,也正是说block的自行变量只针对block内部使用的全自动变量,不利用则不截获,因为截获的自动变量会储存于block的结构体内部,会招致block体量变大,默许情形下
block 只可以访问无法修改部分变量的值,如下图所示:

图片 7

对于 __block 修饰的外表变量引用,block
是复制其引用地址来兑现访问的,block能够修改__block
修饰的表面变量的值,如下图所示:

图片 8

 

四. A本田UR-VC 对 block 类型的影响

在 ARAV4C 开启的动静下,将只会有 NSConcreteGlobalBlock 和
NSConcreteMallocBlock 类型的 block。

本来的 NSConcreteStackBlock 的 block 会被 NSConcreteMallocBlock 类型的
block替代。评释情势是以下代码再 XCode 中,会输出
<__NSMallocBlock__: 0x100109960>。

在苹果的合法文书档案中也提到,当把栈中的block再次回到时,不需求调用 copy
方法了。

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        int i = 1024;
        void (^block1)(void) = ^{
            printf("%d\n", i);
        };
        block1();
        NSLog(@"%@", block1);
    }
    return 0;
}

 A揽胜C下,访问外界变量的 Block 为何要从栈区拷贝到堆区呢?

栈上的Block,若是其所属的变量成效域甘休,该Block就被放任,就像是1般的自动变量。当然,Block中的__block变量也同时被抛弃:

图片 9

为了消除栈块在其变量功能域停止之后被吐弃(释放)的标题,大家供给把Block复制到堆中,延长其生命周期。开启ALacrosseC时,大部分情形下编写翻译器会恰本地举行判断是不是有亟待将Block从栈复制到堆,借使有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作实施的是copy实例方法。Block只要调用了copy方法,栈块就会成为堆块。

如下图:

图片 10

四. ATucsonC 对 block 类型的熏陶

在 AMuranoC 开启的境况下,将只会有 NSConcreteGlobalBlock 和
NSConcreteMallocBlock 类型的 block。

原来的 NSConcreteStackBlock 的 block 会被 NSConcreteMallocBlock 类型的
block替代。评释格局是以下代码再 XCode 中,会输出
<__NSMallocBlock__: 0x100109960>。

在苹果的合法文书档案中也波及,当把栈中的block重返时,不要求调用 copy
方法了。

#import <Foundation/Foundation.h>
int main(int argc, const char * argv[])
{
    @autoreleasepool {
        int i = 1024;
        void (^block1)(void) = ^{
            printf("%d\n", i);
        };
        block1();
        NSLog(@"%@", block1);
    }
    return 0;
}

 A陆风X8C下,访问外界变量的 Block 为啥要从栈区拷贝到堆区呢?

栈上的Block,借使其所属的变量作用域甘休,该Block就被撤消,就像是1般的活动变量。当然,Block中的__block变量也还要被屏弃:

图片 11

为了消除栈块在其变量效用域甘休之后被放任(释放)的难题,咱们需求把Block复制到堆中,延长其生命周期。开启AHavalC时,超越3/6动静下编写翻译器会恰本地举行判断是还是不是有亟待将Block从栈复制到堆,假诺有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作实施的是copy实例方法。Block只要调用了copy方法,栈块就会化为堆块。

如下图:

图片 12

伍. 链式语法的实现

  类似于第3方自动布局 Masonry 的代码:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top);
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

5. 链式语法的兑现

  类似于第二方自动布局 Masonry 的代码:

[view1 mas_makeConstraints:^(MASConstraintMaker *make) {
    make.top.equalTo(superview.mas_top).with.offset(padding.top);
    make.left.equalTo(superview.mas_left).with.offset(padding.left);
    make.bottom.equalTo(superview.mas_bottom).with.offset(-padding.bottom);
    make.right.equalTo(superview.mas_right).with.offset(-padding.right);
}];

5.壹 怎么样促成

我们举个例子,就算对于三个已有类的实例 classInstance,将来要用句点 .
和小括号 () 的情势连接调用它的”方法”
method一,method二,method三,如下图所示:

图片 13

从图中大家得以领略,要完毕链式语法,紧要含有
点语法、小括号调用、一而再走访 三有的:

  • 点语法:在OC中,对于点语法的选取,最广泛于属性的造访,比如对在艺术内部调用
    self.xxx,在类的实例中用 classInstance.xxx;
  • 小括号调用:OC中貌似用中括号 [] 来达成方式的调用,而对此 Block
    的调用则还是封存使用小括号 ( ) 的法门,由此大家可以设想用
    Block来落实链式语法中的 ();
  • 怎样贯彻连接走访?:Block可以驾驭为带有自动变量的匿名函数或函数指针,它也是有重返值的。我们能够把上述类实例每便方法的调用(实质为
    Block 的调用)的再次回到值都设为当前类实例本人,即
    classInstance.method一() 再次回到了脚下 classInstance
    ,此时才能在其背后继续执行 .method二() 的调用,以此类推。

总括一句话:咱们得以定义类的有个别只读 Block 类型的品质,并把那几个 Block
的归来值类型设置为当前类本人,然后完结那个 Block 属性的 getter 方法。

上面是一个德姆o,链式计算器的例证,能够趋之若鹜地调用总计器的加减乘除举办总结:

@interface Cacluator : NSObject

@property (assign, nonatomic) NSInteger result;

// 下面分别定义加减乘除四个只读block类型的属性
// 设置为只读是为了限制只需要实现 getter方法
// 这里每个 Block 类型的属性携带一个 NSInteger 类型的参数,返回参数是当前类型
@property (copy, nonatomic, readonly) Cacluator *(^add)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^minus)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^multiply)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^divide)(NSInteger number);

@end


@implementation Cacluator

// 此处为 add 属性的 getter方法实现
// 前面声明 add 属性的类型为 block 类型,所以此处 getter 返回一个 block
// 对于返回的 block,返回值类型为 Calculator,所以返回self

-(Cacluator *(^)(NSInteger))add{
    return ^id(NSInteger num){
        self.result += num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))minus{
    return ^id(NSInteger num){
        self.result -= num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))multiply{
    return ^id(NSInteger num){
        self.result *= num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))divide{
    return ^id(NSInteger num){
        NSAssert(num != 0, @"除数不能为0");
        self.result /= num;
        return self;
    };
}

@end

 测试代码:

Calculator *calc = [[Calculator alloc] init]; // 初始化一个计算器类实例

calc.add(8).minus(4).multiply(6).divide(3); // 链式调用

NSLog(@"%d", (int)calc.result); // 输出 8

 分析:

上面 calc.add 访问 calc 的 add 属性会调用 [calc add] 方法,此方法会返回一个Block如下:

^id(NSInteger num){
      self.result += num;
      return self;  
};

在这个Block中,前面已声明其返回值类型为:Caculator,所以在其里面返回了 self,这样当调用该 Block 时,会返回 self (实例本身),流程如下:

1.calc.add 获得一个 Block
2.calc.add(8) Block 的执行,并返回了 self (即实例 calc)
3.于是在 calc.add(8) 后面可继续访问 calc 的其他属性,一路点下去

五.壹 怎么样完毕

咱俩举个例子,假设对于二个已有类的实例 classInstance,未来要用句点 .
和小括号 () 的格局连接调用它的”方法”
method1,method二,method3,如下图所示:

图片 14

从图中大家能够领会,要兑现链式语法,首要包涵点语法、小括号调用、延续访问 3有的:

  • 点语法:在OC中,对于点语法的施用,最广泛于属性的拜会,比如对在措施内部调用
    self.xxx,在类的实例中用 classInstance.xxx;
  • 小括号调用:OC中貌似用中括号 [] 来完成情势的调用,而对于 Block
    的调用则如故保留使用小括号 ( ) 的方法,由此大家能够设想用
    Block来促成链式语法中的 ();
  • 怎么样贯彻连接走访?:Block能够清楚为涵盖自动变量的匿名函数或函数指针,它也是有再次来到值的。大家得以把上述类实例每回方法的调用(实质为
    Block 的调用)的再次来到值都设为当前类实例本人,即
    classInstance.method一() 再次回到了当下 classInstance
    ,此时才能在其前面继续执行 .method二() 的调用,以此类推。

小结一句话:大家能够定义类的片段只读 Block 类型的质量,并把那么些 Block
的回到值类型设置为当前类自身,然后达成这几个 Block 属性的 getter 方法。

下边是3个德姆o,链式计算器的例证,能够连绵不断地调用总计器的加减乘除进行计算:

@interface Cacluator : NSObject

@property (assign, nonatomic) NSInteger result;

// 下面分别定义加减乘除四个只读block类型的属性
// 设置为只读是为了限制只需要实现 getter方法
// 这里每个 Block 类型的属性携带一个 NSInteger 类型的参数,返回参数是当前类型
@property (copy, nonatomic, readonly) Cacluator *(^add)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^minus)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^multiply)(NSInteger number);
@property (copy, nonatomic, readonly) Cacluator *(^divide)(NSInteger number);

@end


@implementation Cacluator

// 此处为 add 属性的 getter方法实现
// 前面声明 add 属性的类型为 block 类型,所以此处 getter 返回一个 block
// 对于返回的 block,返回值类型为 Calculator,所以返回self

-(Cacluator *(^)(NSInteger))add{
    return ^id(NSInteger num){
        self.result += num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))minus{
    return ^id(NSInteger num){
        self.result -= num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))multiply{
    return ^id(NSInteger num){
        self.result *= num;
        return self;
    };
}

-(Cacluator *(^)(NSInteger))divide{
    return ^id(NSInteger num){
        NSAssert(num != 0, @"除数不能为0");
        self.result /= num;
        return self;
    };
}

@end

 测试代码:

Calculator *calc = [[Calculator alloc] init]; // 初始化一个计算器类实例

calc.add(8).minus(4).multiply(6).divide(3); // 链式调用

NSLog(@"%d", (int)calc.result); // 输出 8

 分析:

上面 calc.add 访问 calc 的 add 属性会调用 [calc add] 方法,此方法会返回一个Block如下:

^id(NSInteger num){
      self.result += num;
      return self;  
};

在这个Block中,前面已声明其返回值类型为:Caculator,所以在其里面返回了 self,这样当调用该 Block 时,会返回 self (实例本身),流程如下:

1.calc.add 获得一个 Block
2.calc.add(8) Block 的执行,并返回了 self (即实例 calc)
3.于是在 calc.add(8) 后面可继续访问 calc 的其他属性,一路点下去

 伍.二 更简短的达成

地点是由此先声美赞臣文山会海的Block属性, 再去落到实处Block属性的getter
方法来完结链式调用,感觉照旧略微麻烦,大家去看望是还是不是有更简明的落真实意况势:

图片 15

点语法的实质:

  • 在OC中,点语法实际上只是1种替换手段,对于属性的getter方法,class.xxx
    的写法最后会被编译器替换到 [class xxx];对于setter 方法,即把
    class.xxx 写在等号左侧,class.xxx = value 会被转移成 [class
    setXxx:value],本质都以办法调用
  • 即便再class中并不曾显式评释 xxx 属性,在编写翻译代码时,代码中假设有
    class.xxx 的写法也会被轮换来 [class
    xxx],所以假使在class中有贰个扬言为 xxx
    的措施,即可在代码中任哪个地方方写 class.xxx

就此,消除方案是:

  在定义类的头文件的@interface中,直接评释某一办法名称叫xxx,该形式的再次回到值是二个Block,而此block的回到值设为此类本人。

@interface Calculator : NSObject

@property (nonatomic, assign) NSInteger result; // 保存计算结果

// 上面的属性声明其实是可以省略的,只要声明下面方法即可;
// 在 Objective-C 中,点语法只是一种替换手段,class.xxx 的写法(写在等号左边除外)最终会被编译器替换成 [class xxx],本质上是方法调用;

// add、minus、multiply、divide 四个方法都会返回一个 Block,
// 这个 Block 有一个 NSInteger 类型的参数,并且其返回值类型为当前 Calculator 类型;
// 下面四个方法的实现与上面 Calculator.m 中的一致。
- (Calculator * (^)(NSInteger num)) add;
- (Calculator * (^)(NSInteger num)) minus;
- (Calculator * (^)(NSInteger num)) multiply;
- (Calculator * (^)(NSInteger num)) divide;

 Masonry
也是如此做的,只注脚了主意,并从未注解相应的性子。其它,对于Masonry链式语法中的
.and、.with 等写法只是为着让代码读起来更通畅,实现格局为:声美赞臣(Meadjohnson)个名称叫and 和 with 的秘籍,在点子里再次来到self:

- (MASConstraint *)with {
    return self;
}

- (MASConstraint *)and {
    return self;
}

 存在的难题:

当用点语法去访问类的某2个 Block 属性时,Block 前面包车型大巴参数 Xcode

XXXHTTPManager *http = [XXXHTTPManager manager];

// 下面 .get(...) 里面的参数,Xcode 并不会提示自动补全,需要手动去填写,.success(...) .failure(...) 等也一样,
// 这里不能像传统中括号 [] 方法调用那样,输入方法名就可以自动提示该方法所有的参数并按回车自动补全。
http.get(@"https://kangzubin.cn", nil).success(^(NSURLSessionDataTask *task, id responseObject) {
    // Success TODO
}).failure(^(NSURLSessionDataTask *task, NSError *error) {
    // Failure TODO
}).resume();

 Xcode 中有个有力但未被充裕利用的意义:Code
Snippets(代码块)能够化解。

http://www.imlifengfeng.com/blog/?utm\_medium=email&utm\_source=gank.io&p=457

 五.二 越来越精简的兑现

上边是通过先声澳优多元的Block属性, 再去贯彻Block属性的getter
方法来实现链式调用,感觉还是略微劳累,大家去看望是不是有更简明的完毕情势:

图片 16

点语法的原形:

  • 在OC中,点语法实际上只是壹种替换手段,对于属性的getter方法,class.xxx
    的写法最后会被编写翻译器替换到 [class xxx];对于setter 方法,即把
    class.xxx 写在等号左边,class.xxx = value 会被撤换到 [class
    setXxx:value],本质都以艺术调用
  • 就算再class中并从未显式证明 xxx 属性,在编写翻译代码时,代码中壹旦有
    class.xxx 的写法也会被替换来 [class
    xxx],所以固然在class中有2个声称为 xxx
    的艺术,即可在代码中任哪个地方方写 class.xxx

所以,化解方案是:

  在定义类的头文件的@interface中,直接表明某壹方法名叫xxx,该方法的再次回到值是八个Block,而此block的回来值设为此类自个儿。

@interface Calculator : NSObject

@property (nonatomic, assign) NSInteger result; // 保存计算结果

// 上面的属性声明其实是可以省略的,只要声明下面方法即可;
// 在 Objective-C 中,点语法只是一种替换手段,class.xxx 的写法(写在等号左边除外)最终会被编译器替换成 [class xxx],本质上是方法调用;

// add、minus、multiply、divide 四个方法都会返回一个 Block,
// 这个 Block 有一个 NSInteger 类型的参数,并且其返回值类型为当前 Calculator 类型;
// 下面四个方法的实现与上面 Calculator.m 中的一致。
- (Calculator * (^)(NSInteger num)) add;
- (Calculator * (^)(NSInteger num)) minus;
- (Calculator * (^)(NSInteger num)) multiply;
- (Calculator * (^)(NSInteger num)) divide;

 Masonry
也是如此做的,只阐明了措施,并不曾表明相应的本性。其它,对于Masonry链式语法中的
.and、.with 等写法只是为了让代码读起来更通畅,完毕方式为:声喜宝(Nutrilon)个名称为and 和 with 的秘籍,在章程里重返self:

- (MASConstraint *)with {
    return self;
}

- (MASConstraint *)and {
    return self;
}

 存在的标题:

当用点语法去访问类的某二个 Block 属性时,Block 后边的参数 Xcode

XXXHTTPManager *http = [XXXHTTPManager manager];

// 下面 .get(...) 里面的参数,Xcode 并不会提示自动补全,需要手动去填写,.success(...) .failure(...) 等也一样,
// 这里不能像传统中括号 [] 方法调用那样,输入方法名就可以自动提示该方法所有的参数并按回车自动补全。
http.get(@"https://kangzubin.cn", nil).success(^(NSURLSessionDataTask *task, id responseObject) {
    // Success TODO
}).failure(^(NSURLSessionDataTask *task, NSError *error) {
    // Failure TODO
}).resume();

 Xcode 中有个有力但未被充足利用的法力:Code
Snippets(代码块)能够缓解。

http://www.imlifengfeng.com/blog/?utm\_medium=email&utm\_source=gank.io&p=457

相关文章