美文网首页
Block简介

Block简介

作者: kReader | 来源:发表于2018-03-14 16:38 被阅读0次

          Block是C语言的扩充功能。一言以蔽之,带有局部变量的匿名函数,简单说它就是一个函数指针。

    一、语法:

    格式:Block变量 = Block值

    具体形式如下:

    声明并赋值:返回值类型(^变量名)(参数列表) = ^返回值类型 参数列表 表达式;

    调用:变量名(参数列表);

    使用实例1:

    // 声明Block变量,并赋值

    void(^name)(int num) = ^void(int num){ NSLog(@"%d", ++num); };

    // 使用Block变量

    name(6);

    使用实例2:

    // 定义Block类型

    typedef void(^BlockType)(int num);

    // 用定义的类型声明变量,并赋值

    BlockType name = ^void(int num){ NSLog(@"%d", ++num); };

    // 调用

    name(8);

    二、Block的种类

    2.1、NSGlobalBlock

    a.全局区域创建的Block,永远是 NSGlobalBlock

    b.局部区域创建的Block,有指针变量引用,不访问局部变量/成员变量,是 NSGlobalBlock

    d.局部区域创建的Block,没有指针变量引用,不访问局部变量/成员变量,是 NSGlobalBlock

    2.2、NSMallocBlock

    c.局部区域创建的Block,有指针变量引用,访问局部变量/成员变量,是 NSMallocBlock

    2.3、NSStackBlock

    e.局部区域创建的Block,没有指针变量引用,访问局部变量/成员变量,是 NSStackBlock

    对于d\e两种情况的一般用法,如下:

    // 1> 作为方法参数,如:[self methodBlock:^{ NSLog(@">>>"); }];

    // 2> 创建后直接调用(一般不这么用),如:^{ NSLog(@">>>"); }();

    三、实质:

    获取源码的方法:

    1> 创建Command Line Tool工程。

    2> 添加Block相关代码。

    3> 打开终端并CD到main.m文件所在的文件夹。

    4> 执行命令:clang -rewrite-objc main.m,该文件夹下会多出main.cpp文件,该文件的末尾就是相关源码。

    以下是5种比较典型的使用情况的源码:

    情况1:局部Block不访问Block外部的变量

    // OC代码

    // main函数中,声明局部Block变量,并赋值,Block的实现只是打印一个字符串,然后调用该Block。

    // clang编译后的相关代码

    // 该结构体和Block相关,我们只关注两个属性:isa && FuncPtr

    // isa:表示Block的类型

    // FuncPtr:表示Block的实现(其实是个函数指针)

    // 该结构体有两个属性和一个构造函数

    // impl:和Block相关的结构体,其结构体指针和__main_block_impl_0结构体指针相同(因为它是第一个成员变量,固它们的首地址相同)

    // Desc:是个结构体,对结构体__main_block_impl_0的描述,即开辟内存空间时的依据

    // __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0):构造函数,该函数需要两个参数,分别是函数指针和描述性的结构体指针

    // 静态函数,其实就是Block的对应实现

    // 结构体实例,是__main_block_impl_0结构体的描述

    // 主函数,函数内部

    // 1、声明了结构体指针变量并赋值

    // 2、根据该指针找到对应的函数,调用了该函数

    // 大概流程就是:

    1、从 main 函数开始

    1.1、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

    1.2、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印一个字符串。

    情况2:局部Block读取其外部的局部变量

    // OC代码

    // main函数中,声明局部变量 num 并赋值,声明局部Block变量,并赋值,Block的实现打印一个字符串和 num 值,然后调用该Block。

    // clang编译后的相关代码

    // 和情况1一样

    // 比情况1多了一个 num 变量,构造函数也多了一个对应该变量的参数

    // 该函数内部声明一个局部变量,并根据__cself获取对应的值赋给该变量,然后打印相应的东西

    // 注意:根据OC对象的定义,对象其实就是结构体指针,所以这里__cself是一个Block对象

    // 和情况1一样

    // 主函数,函数内部

    // 1、声明变量 num 并赋值 666

    // 2、声明了结构体指针变量并赋值

    // 3、根据该指针找到对应的函数,调用了该函数

    // 大概流程就是:

    1、从 main 函数开始

    1.1、声明变量 num 并赋值 666

    1.2、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针和num,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

    1.3、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印一个字符串。

    情况3:局部Block修改其外部的局部变量

    // OC代码

    //main函数中,声明__block修饰的局部变量 num 并赋值,声明局部Block变量,并赋值,Block的实现打印一个字符串和 ++num 值,然后调用该Block。

    // clang编译后的相关代码

    // 和情况1一样

    // num 变量转变的结构体

    // 对比情况2,这里把int类型的num变量,换成了__Block_byref_num_0类型的结构体指针(即对象),这里__block修饰后的num变量被编译成了对象

    // __cself对象中获取num对象,又从num对象中获取__forwarding对象,然后从__forwarding对象中获取num变量,执行++num之后打印输出最后的值

    // __forwarding就是一个指向自身结构体的首地址的结构体指针,这样做的目的就是在把变量从栈区复制到堆区的时候,不论变量在哪一个存储区域,都可以正确访问其对应的变量值

    // 复制对象(引用计数+1)

    // 处理对象(引用计数-1)

    // 结构体实例,是__main_block_impl_0结构体的描述,对比情况2,成员变量中多了两个函数指针,这两个函数指针就是结构体指针(即对象)的内存管理函数,系统会在适当的时候自动调用它们

    // 主函数,函数内部

    // 1、声明结构体变量 num 并赋了相关的值

    // 2、声明了结构体指针变量block并赋值,这里调用构造函数创建该结构体的时候,第三个参数是num的结构体指针,即num对象

    // 3、根据该指针找到对应的函数,调用了该函数

    // 大概流程就是:

    1、从 main 函数开始

    1.1、声明结构体变量 num 并赋了相关的值

    1.2、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针和num对象,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

    1.3、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印相关值。

    情况4:局部Block访问(读取&修改)全局变量

    // OC代码

    // 声明全局int类型的变量num,并赋值6

    // main函数中,声明局部Block变量,并赋值,Block的实现打印一个字符串和 num 值,然后调用该Block

    // clang编译后的相关代码

    // 和情况1一样

    // 声明全局变量num并赋初始值6。

    // 和情况1一样

    // 和情况1一样,只是多了num变量的打印,num值来自全局变量

    // 和情况1一样

    // 和情况1一样

    // 大概流程就是:

    1、从 main 函数开始

    1.1、声明结构体指针block,然后调用__main_block_impl_0结构体的构造函数,传入参数__main_block_func_0函数指针(函数首地址,函数名即函数首地址)和__main_block_desc_0_DATA指针,得到__main_block_impl_0结构体,并将得到的结构体的地址赋值给block。

    1.2、block就是__main_block_impl_0结构体的首地址,也是__block_impl结构体的首地址,所以根据block->FuncPtr,可以拿到对应函数指针,然后调用该函数,该函数打印一个字符串。

    情况5:全局Block访问(读取&修改)全局变量

    // OC代码

    //全局Block读取全局变量

    //全局Block修改全局变量

    // main函数调用Block

    // clang编译后的相关代码

    // 和情况4一样

    // 和情况4一样

    // 和情况4一样

    // 和情况4一样

    // 和情况4一样

    // 调用对应的函数构造__blk1_block_impl_0静态结构体

    // 把上面的结构体指针(对象)赋值给blk1变量

    // main函数,调用全局blk1

    // 大概流程就是:

    1、从 main 函数开始

    1.1、直接调用代表Block实例的全局的结构体指针

    综上可知:

    1、Block其实就是OC所谓的实例,每一个Block,都会被编译为对应的结构体指针,通过这个结构体指针,可以找到对应的函数实现以及Block内部访问的外部的局部变量或成员变量。

    2、Block有3种类型:_NSConcreteGlobalBlock(全局)、_NSConcreteStackBlock(栈)、__NSConcreteMallocBlock(堆)

    3、访问全局变量或者不访问任何外部变量的Block变量(注意是变量,不是实现),永远都是全局Block

    4、参数形式出现的栈Block,如果在方法内部被一个新Block指针变量引用,那么该栈Block被会复制一份到堆区,新Block指针指向这个堆Block

    5、__block修饰后的变量,该变量会被编译为一个结构体,且该结构体包含这个变量,并以结构体指针(对象)的形式被访问,因此当一个变量没有被__block修饰时,Block内部捕获的是该变量的值,被__block修饰时,Block内部捕获的是包含该变量的对象

    四、总结

          归根结底,Block就是一个包含函数实现和所使用的变量(如果需要的话)的结构体指针,即OC对象,所以Block可以被当做全局变量、局部变量、成员变量、参数、返回值,来回传递使用,更重要的是,它能携带函数的实现(函数指针),这意味着它能封装一段代码块,被来回传递,在适当的时候调用执行,执行的同时也可以修改它携带的变量值,起到异地异步传值的的目的。

          因为Block是个OC对象,当它本身作为一个对象的成员变量时,而其内部又引用这个对象或者这个对象的成员变量和方法的时候(或者多个对象依次引用构成了循环链),就会导致循环引用,从而导致内存问题。这种情况,一般用 __weak 弱化 self 指针就可以了。这里不细说各种循环引用的问题。

    相关文章

      网友评论

          本文标题:Block简介

          本文链接:https://www.haomeiwen.com/subject/xbtfqftx.html