学院网站开发网站定位,软文推广的标准类型,二维码网页制作免费网站制作,wordpress随机广告文章目录 前言一、Blocks概要1.什么是Blocks 二、Block模式1.block语法2.block类型变量3.截获自动变量值4._Block修饰符5.截获的自动变量 三、Blocks的实现1.Block的实质2.截获自动变量值3._Block说明符4.Block存储域 前言
一、Blocks概要
1.什么是Blocks
Blocks是C语言的扩… 文章目录 前言一、Blocks概要1.什么是Blocks 二、Block模式1.block语法2.block类型变量3.截获自动变量值4._Block修饰符5.截获的自动变量 三、Blocks的实现1.Block的实质2.截获自动变量值3._Block说明符4.Block存储域 前言
一、Blocks概要
1.什么是Blocks
Blocks是C语言的扩充功能并且可以用一句话来表示Blocks的扩充功能Blocks是带有自动变量局部变量的匿名函数。 匿名函数Blocks是一种匿名函数也就是没有特定名称的函数。它们可以在需要的地方定义和使用而无需提前声明或命名。这使得Blocks非常灵活可以作为参数传递给其他函数或方法或者作为变量保存和执行。 自动变量局部变量Blocks可以捕获其定义范围内的自动变量也称为局部变量。当一个Block被定义时它会在其内部创建一个副本用于在Block执行时访问该变量的值。这意味着即使变量超出了其定义范围Block仍然可以访问和使用该变量的值。 闭包由于Blocks可以捕获自动变量它们形成了一个封闭的环境即闭包。这意味着Block可以在其定义范围之外访问和使用自动变量。当Block被传递到其他函数或方法时它会携带其封闭环境中的自动变量以便在执行时可以访问这些变量的值。 二、Block模式
1.block语法
完整形式的Block语法和一般的C语言函数相比只有两点不同。第一点是没有函数名因为它是匿名函数。第二点是返回值类型前带有“^”插入记号记号。下面是Block语法格式 ^ 返回值类型 参数列表 表达式 ^int (int count){return count 1;}Block语法可以省略返回值类型
^(int count){return count 1;}如果不使用参数Block语法还可以省略参数列表
^void (void){printf(Blocks\n);}2.block类型变量
在C语言中可以将函数地址赋值给函数指针的类型变量中同样在Block语法下可将Block语法赋值给Block类型的变量中。示例如下
int (^blk)(int) ^(int count) {return count 1;};
int (^blk_t)(int);
blk_t blk;Block类型变量声明 返回参数 (^变量名称)(接受参数); 同时使用typedef重命名Block类型因为Block类型变量和平时的使用类型相同为了方便我们使用我们通常都是用typedef来重命名Block。
typedef 返回参数 (^该Block的名称)(接收参数)
此时就可以用该Block的名称代表该Block了。
typedef int (^blk_t)(int)blk_t blk ^(int count) {return count 1;};
3.截获自动变量值
Block截获自动变量值就是说在定义Block的时候其之前的变量都会被该Block所截获该Block后再改变变量的值然后再调用其Block中所捕获的值不会受到改变。
int main(int argc, const char * argv[]) {int dmy 256;int val 10;const char *fmt val %d\n;void (^blk)(void) ^{printf(fmt, val);};val 2;fmt These values were changed. val %d\n;blk();return 0;
} 此时我们发现在定义Block之后的这valfmt两个变量被改变了但是再次调用该Block还是定义Block之前的值这就是捕获。我的理解就是定义Block之前的自动变量都会被该Block捕获在定义Block后改变变量其值还是该Block之前的定义的值不会收到影响。
4._Block修饰符
自动变量截获只能保存执行Block语法瞬间的值并且保存后就不能修改这个值如果要修改这个自动变量的值就需要在在声明时给这个自动变量附加_Block说明符。
int main(int argc, const char * argv[]) {__block int val 10;__block const char *fmt val %d\n;void (^blk)(void) ^{printf(fmt, val);val 20;};val 2;fmt These values were changed. val %d\n;blk();printf(fmt, val);return 0;
} 5.截获的自动变量
前面提到向Block截获的自动变量赋值会报错如果调用变更该对象的方法则不会产生编译错误。 用OC中的NSMutableArray来说截获它其实就是截获该类对象用的结构体实例指针就是说你只要不改变其指针的指向和其指针的值他就不会出错。
int main(int argc, const char * argv[]) {id array [[NSMutableArray alloc] init];void (^blk)(void) ^{id obj [[NSObject alloc] init];[array addObject:obj];};blk();return 0;
}
这里因为你并没有改变其array指针的指向和值而只是在其地址后边新加了一个值所以他就不会报错。
int main(int argc, const char * argv[]) {id array [[NSMutableArray alloc] init];void (^blk)(void) ^{array [[NSMutableArray alloc] init];};blk();return 0;
}
但是你这样写就改变了array的初始地址所以他就会报错。
另外在使用C语言数组是必须小心使用其指针。
int main(int argc, const char * argv[]) {const char text[] hello; //这里是定义一个数组void (^blk)(void) ^{printf(%c\n, text[2]);};blk();return 0;
}
这里你看似没有什么错误但是会出现下面的编译错误。 因为在现在的Blocks中截获自动变量的方法并没有实现对C语言数组的截获这时我们可以使用指针来解决该问题。
int main(int argc, const char * argv[]) {const char *text hello; //这里是使用指针引用void (^blk)(void) ^{printf(%c\n, text[2]);};blk();return 0;
}
三、Blocks的实现
1.Block的实质
Block实质是Objective-C对闭包的对象实现简单说来Block就是对象。
下面分别从表层到底层来分析一下
表层分析Block的实质它是一个类型
Block是一种类型一旦使用了Block就相当于生成了可赋值给Block类型变量的值。举个例子
int (^blk)(int) ^(int count){return count 1;
};等号左侧的代码表示了这个Block的类型它接受一个int参数返回一个int值。 等号右侧的代码是这个Block的值它是等号左侧定义的block类型的一种实现。 如果我们在项目中经常使用某种相同类型的block我们可以用typedef来抽象出这种类型的Block
typedef int(^AddOneBlock)(int count);AddOneBlock block ^(int count){return count 1;//具体实现代码
};这样一来block的赋值和传递就变得相对方便一些了, 因为block的类型已经抽象了出来。
深层分析Block的实质它是Objective-C对象
Block其实就是Objective-C对象因为它的结构体中含有isa指针。
下面将Objective-C的代码转化为C的代码来看一下block的实现。
int main()
{void (^blk)(void) ^{printf(Block\n);};return 0;
}通过clang编辑器转换为c
struct __block_impl {void *isa;int Flags;int Reserved;void *FuncPtr;
};//block结构体
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;//Block构造函数__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {impl.isa _NSConcreteStackBlock;//isa指针impl.Flags flags;impl.FuncPtr fp;Desc desc;}};//将来被调用的block内部的代码block值被转换为C的函数代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf(Block\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)};//main 函数
int main()
{void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA));return 0;
}首先我们看一下从原来的block值OC代码块转化而来的C代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {printf(Block\n);
}这里*__cself 是指向Block的值的指针也就相当于是Block的值它自己相当于C里的thisOC里的self。 而且很容易看出来cself 是指向mainblockimpl0结构体实现的指针。 结合上句话也就是说Block结构体就是mainblockimpl0结构体。Block的值就是通过mainblockimpl_0构造出来的。 下面来看一下这个结构体的声明
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 flags0) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};可以看出_mainblockimpl0结构体有三个部分
第一个是成员变量impl它是实际的函数指针它指向_mainblockfunc0。来看一下它的结构体的声明
struct __block_impl {void *isa;int Flags;int Reserved; //今后版本升级所需的区域void *FuncPtr; //函数指针
};第二个是成员变量是指向_mainblockdesc0结构体的Desc指针是用于描述当前这个block的附加信息的包括结构体的大小等等信息
static struct __main_block_desc_0 {size_t reserved; //今后升级版本所需区域size_t Block_size;//block的大小} __main_block_desc_0_DATA { 0, sizeof(struct __main_block_impl_0)};第三个部分是mainblockimpl0结构体的构造函数mainblockimpl0 就是该 block 的实现
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags0) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}在这个结构体的构造函数里isa指针保持这所属类的结构体的实例的指针。_mainblockimlp0结构体就相当于Objective-C类对象的结构体这里的_NSConcreteStackBlock相当于Block的结构体实例,也就是说block其实就是Objective-C对于闭包的对象实现。
2.截获自动变量值
使用Block的时候不仅可以使用其内部的参数还可以使用Block外部的局部变量。而一旦在Block内部使用了其外部变量这些变量就会被Block保存。 通过先前的例子我们知道 Block可以截获局部变量。 修改Block外部的局部变量Block内部被截获的局部变量不受影响。 修改Block内部到局部变量编译不通过。
通过C的代码来看一下Block在截获变量的时候都发生了什么
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt; //被添加int val; //被添加__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags0) : fmt(_fmt), val(_val) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt __cself-fmt; // bound by copyint val __cself-val; // bound by copyprintf(fmt,val);}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 dmy 256;int val 10;const char *fmt var %d\n;void (*blk)(void) ((void (*)())__main_block_impl_0((void *)__main_block_func_0, __main_block_desc_0_DATA, fmt, val));val 2;fmt These values were changed. var %d\n;((void (*)(__block_impl *))((__block_impl *)blk)-FuncPtr)((__block_impl *)blk);return 0;
}单独抽取_mainblockimpl0来看一下
struct __main_block_impl_0 {struct __block_impl impl;struct __main_block_desc_0* Desc;const char *fmt; //截获的自动变量int val; //截获的自动变量__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, const char *_fmt, int _val, int flags0) : fmt(_fmt), val(_val) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;}
};我们可以看到在block内部语法表达式中使用的自动变量fmtval被作为成员变量追加到了_mainblockimpl0结构体中注意block没有使用的自动变量不会被追加如dmy变量。 在初始化block结构体实例时请看mainblockimpl0的构造函数还需要截获的自动变量fmt和val来初始化mainblockimpl0结构体实例因为增加了被截获的自动变量block的体积会变大。 再来看一下函数体的代码
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {const char *fmt __cself-fmt; // bound by copyint val __cself-val; // bound by copyprintf(fmt,val);
}从这里看就更明显了fmt,var都是从__cself里面获取的更说明了二者是属于block的。而且从注释来看注释是由clang自动生成的这两个变量是值传递而不是指针传递也就是说Block仅仅截获自动变量的值所以这就解释了即使改变了外部的自动变量的值也不会影响Block内部的值。 既然我们无法在Block中改变外部变量的值所以也就没有必要在Block内部改变变量的值了因为Block内部和外部的变量实际上是两种不同的存在前者是Block内部结构体的一个成员变量后者是在栈区里的临时变量。 现在我们知道被截获的自动变量的值是无法直接修改的但是有两个方法可以解决这个问题 改变存储于特殊存储区域的变量。 通过__block修饰符来改变。
3._Block说明符
前面提到Block中使用自动变量后在Blockj的结构体实例中重写该自动变量也不会改变原先截获的自动变量的值。不过这样的话就不能在Block中保存值了不是很方便。因此下面提供两种方法来解决。 第一种方法是C语言中有一个变量允许Block改写值即静态变量也就是用static修饰的变量。但是他并不太好因为变量作用域结束的同时原来的自动变量被废弃因此Block 中超过变量作用域而存在的变量同静态变量一样将不能通过指针访问原来的自动变量。 第二种方法是使用_Block说明符。更准确的表达方式是“_Block存储域类说明符”。C语言中有以下存储域类说明符
typedefexternstaticautoregister
使用__block修饰符它类似于static、auto、和register说明符用于指定将变量值设置到哪个存储域中。
下面是个例子
int main(int argc, const char * argv[]) {__block int val 10;void (^blk)(void) ^{val 1;};return 0;
}
其源代码如下
//__block说明符修饰后的变量的结构体
struct __Block_byref_val_0 {void *__isa; //指向所属类的指针__Block_byref_val_0 *__forwarding; //指向自己的内存地址的指针int __flags; //标志性参数暂时没用到所以默认为0int __size; //该结构体所占用的大小int val; //该结构体存储的值即原变量的赋的值
};//block本体
struct __main_block_impl_0 {struct __block_impl impl; //block的主体struct __main block desc 0* Desc; //存储该block的大小__Block_byref_val_0 *val; //__block修饰的变量的值//构造函数__main_block_impl_0(void *fp, struct __main_block_desc 0 *desc, __Block_byrefval_0 *_val, int flags0) : val(_val-__forwarding) {impl.isa _NSConcreteStackBlock;impl.Flags flags;impl.FuncPtr fp;Desc desc;
};//封装的block逻辑存储了block的代码块
static void __main_block_func_0(struct__main_block_impl_0 *_cself) {__Block_byref_val_0 *val __cself-val;(val-__forwarding-val) 1;
}
static void_main_block_copy_0(struct __main_block_impl_0* dst, struct __main_block_impl_0* src) {//根据auto变量的修饰符__strong、__weak、__unsafe_unretained做出相应的操作形成强引用retain或者弱引用_Block_object_assign(dst-val, src-val, BLOCK_FIELD_IS_BYREF);
}static void __main_block_dispose_0(struct __main_block_imp1_0* src) {//自动释放引用的auto变量相当于release_Block_object_dispose(src-val, BLOCK_FIELD_IS_BYREF);
}static struct __main_block_desc_0 {unsigned long reserved; //保留字段unsigned long Block_size; //block大小void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*); //copy的函数指针下面使用构造函数进行了初始化void (*dispose)(struct __main_block_impl_0*); //dispose的函数指针下面使用构造函数进行了初始化
}//构造函数初始化保留字段、block大小及两个函数__main_block_desc_0_DATA {0,sizeof(structmain_block_impl_0),__main_block_copy_O, __main_block_dispose_0
};
int main() {//之前的 __block int val 10;变成了结构体实例struct __Block_byref_val_0 val {0, //isa指针val, //指向自身地址的指针0, //标志变量sizeof(__Block_byref_val_0), //block大小10 //该数据的值};blk __main_block_impl_0(
__main_block_func_0, __main_block_desc_0_DATA, val, 0x22000000);return 0;
}
通过源代码发现__block说明符修饰的变量变成了一个结构体它是利用这个结构体来进行存储数据的
struct __Block_byref_val_0 val {0, //isa指针val, //指向自身地址的指针0, //标志变量sizeof(__Block_byref_val_0), //block大小10 //该数据的值};那么为什么会有成员变量__forwarding呢 因为__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上还是堆上时都能够正确的访问__block变量。
4.Block存储域
Block转换为Block的结构体类型的自动变量_Block变量转换为 block变量的结构体类型的自动变量。所谓结构体类型的自动变量就是栈上生成的该结构体的实例。
Block的所属类
_NSConcreteStackBlock //栈 _NSConcreteGlobalBlock //全局.data区 _NSConcreteMallocBlock //堆
全局Block
在记述全局变量的地方使用Block语法时生成的Block为_NSConcreteGlobalBlock类对象。
void (^blk)(void) ^{printf(Global Block\n);};int main(void) {······
}
其isa指针初始化如下
impl.isa _NSConcreteGlobalBlock;
该类Block用结构体实例设置在程序的数据区域中。因为在使用全局变量的地方不能使用自动变量所以不存在对自动变量进行截获。只在截获自动变量时Block 用结构体实例截获的值才会根据执行时的状态变化。也就是说全局Block不可以截获自动变量否则其就不可以设置为全局Block。
也就是说即使在函数内而不在记述广域变量的地方使用Block语法时只要Block不截获自动变量就可以将Block用结构体实例设置在程序的数据区域。以上的这些情况下Block为_NSConcreteGlobalBlock类对象。即Block配置在应用程序的数据区域中。
栈Block
其isa指针初始化如下
impl.isa _NSConcreteStackBlock;
除上述的设置两种全局Block之外的Block语法生成的Block为_NSConcreteStackBlock类对象且设置在栈上。
配置在全局变量上的Block从变量作用域外也可以通过指针安全地使用。但设置在栈上的Block如果其所属的变量作用域结束该Block就被废弃。由于__block变量也配置在栈上同样地如果其所属的变量作用域结束则该__block变量也会被废弃。我们也就无法再访问到该变量了。
为了解决无法访问被废弃变量这个问题就出现了堆block同时也出现了将Block和__block变量从栈上复制到堆上的方法。这样即使Block 语法记述的变量作用域结束堆上的Block还可以继续存在。下面我们就来说说
堆Block
其isa指针初始化如下
impl.isa _NSConcreteMallocBlock;
堆上的Block其实就是将栈上的Block复制过来而成的有时我们在_block变量配置在堆上的状态下也可以访问栈上的__block变量。在此情形下只要栈上的结构体实例成员变量__forwarding指向堆上的结构体实例那么不管是从栈上的__block 变量还是从堆上的__block 变量都能够正确访问。 当ARC有效时大多数情形下编译器会恰当地进行判断自动生成将Block从栈上复制到堆上的代码。这是为了防止其被废弃而导致我们访问错误。 下面这个例子就是堆Block用它来说明
typedef int (^blk_t)(int);blk_t func(int rate) {return ^(int count) {return rate * count;};
}
源代码
blk_t func(int rate) {//因为ARC处于有效的状态所以blk_t tmp实际上与附有__strong 修饰符的blk_t __strong tmp 相同blk_t tmp __func_block_impl_0(__func_block_func_0__func_block_desc_0_DATA, rate);//通过 objc4运行时库的runtime/objc-arrmm可知objc_retainBlock函数实际上就是_Block_copy 函数tmp objc_retainBlock(tmp);//等同于 tmp _Block_copy(tmp);//最后将tmp放入自动释放池中进行返回return objc_autoreleaseReturnValue(tmp);
}
/**将通过Block语法生成的Block*即配置在栈上的Block用结构体实例*赋值给相当于Block类型的变量tmp中。*/
tmp _Block_copy(tmp);
/**_Block_copy 函数*将栈上的Block复制到堆上。*复制后将堆上的地址作为指针赋值给变量tmp。*/return objc_autoreleaseReturnValue(tmp);
/**将堆上的Block作为Objective-c对象*注册到autoreleasepool中然后返回该对象。*/
通过上面的例子就可以知道将 Block 作为函数返回值返回时编译器会自动生成复制到堆上的代码。