0
点赞
收藏
分享

微信扫一扫

iOS--第二章block

全栈学习笔记 03-18 10:00 阅读 3
ios

第二章block

blocks 概要

Blocks是c语言的扩展,block是一个带有自动变量值的匿名函数,它也是一个数据类型;
或者说:带有自动变量的匿名函数;

  • 书上还提到了一大串源码例子,不过不是很懂,只知道它强调了Blocks提供了类似于C++和oc类生成实例或对象来保持变量值的方法,即Blocks保持自动变量的值;
  • 带有自动变量值的匿名函数这一概念并不仅指BLOCKs:
    在这里插入图片描述

Blocks模式

Blocks语法

语法类型如下:
^ 返回值类型 参数类型 表达式

void (^block1)()//无返回值,无参数
int (^block2)(int num1,int num2)//int类型返回值i,两个int类型参数

语法中要注意的几个点:

  • 省略返回值类型时,如果表达式中有return语句就使用返回值的类型,如果表达式中没有return语句就使用void类型。表达式中含有多个return语句时,所有return的返回值类型必须相同
  • 如果不使用参数,参数列表也可省略;
^void (void) {printf("Blocks\n");
  • 也可以返回值类型以及参数列表都省略

Blocks类型变量

在这里插入图片描述

这里强调了blocks可以是一种类型变量,后面我们还要学到blocks类型的对象 ;

声明Blocks类型变量的实例如下:

int (^block) (int) ;

Blocks类型变量与一般的c语言变量完全相同 ;可作为以下用于使用

  • 自动变量
  • 函数参数
  • 静态变量
  • 静态全局变量
  • 全局变量

因为和通常的变量相同,所以当然可以由Blockleixing变量向Block类型变量赋值 ;

int (^blk1) (int) = blk ;
int (^blk2) (int) ;
blk2 = blk1 ;

可以作为函数参数传递,也可以作为函数返回值返回:

void func (int (^blk) (int) )  {

int (^func () ) (int ) {
return ^ (int count ) {return count + 1;}//不过这个函数没太看懂,有点奇怪  

上面这几种计述方式非常复杂,应为blocks是一种数据类型,所以我们可以使用typedef解决该问题:

typedef int^blk_t ) (int) ;

上面的例子可改为:

void func (blk_t blk) {
blk_t func () {

同样的:可以通过Block类型变量调用Block和使用Block的指针类型变量 ;
其实可以总结为;Block类型变量可像c语言中其他类型变量一样使用

截获自动变量值

在Blocks中,Blocks可以截获表达式中那个使用的变量,即保存该变量的瞬间值。随意即使之后改变原来的自动变量,也不会改变Blocks所截获的自动变量的值 ;
这一概念的实质是Blocks中的变量会将截获的值存进一个结构体对象中 ;

int main () {
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 ;
val = 10 ;

_block 说明符

自动变量截获只能保存执行Block语法瞬间的值,保存后就不能修改其值,如果我们尝试修改其值 ;

int val = 0 ;
void (^blk) (void) = ^ (val = 1 ;) ;
blk ();
printf ("val = %d\n",val) ;

编译器此时会报错:
在这里插入图片描述

Blocks的实现

Block的实质

Block是“带有自动变量值的匿名函数:,但它实际上作为简单的c语言源代码处理的,通过支持Block的编译器,含有Block语法源代码转换为c语言源代码被编译 ;

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

可以转化为一下的代码:
在这里插入图片描述

交换后的源代码中:

static void _main_Block-func_0 (struct__main_block_imp1_0 * _cself)  {
printf ("Block\n") ;
}

这里根据Block语法所属的函数名和该Block语法在该函数出现的顺序值来给转换后的函数命名 ;
参数_cself 是——main_block_impl_0结构体的指针,

struct _main_block_impl_0 {
struct _block_impl impl ;
struct _main_block_desc_0 * Desc ;
}
struct _block_impl {
void* isa ;
int Flags ;
int Reserved ;
void* FuncPtr ;
} ;
struct _main_Block_desc_0 {
unsigned long reserved ;
unsigned long Block_size ;
};

从上面这些结构体来看,Block本身的实现就是依赖于这些结构体对象,这也能更好的理解之后入变量截获的实现等 ;

struct_main_block_impl_0 tmp = _main_block_impl_0 (_main_block_func_0, &_main_block_desc_0_DATA) ;
struct _main_block_impl_0* blk = &tmp ;

这段代码等同于

void^blk) (void) = ^ {printf("Block\n"); }

struct_main_block_impl_0 tmp = _main_block_impl_0 (_main_block_func_0, &_main_block_desc_0_DATA) ;
第一个参数是由Blcok语法转化的c语言函数指针,第二个参数是作为静态全局变量初始化的_main_block_desc_0 的结构体实例指针 ;这里意味着将Block语法生成的Block赋给Block类型变量blk ;

blk () ;
//变换后并去掉转换部分*blk->impl.FuncPtr) (blk) ;

这里_main_block_func_0 函数的指针被赋值成员变量FuncPtr中 ;
也说明了参数_cself指向Block值 ;

对于isa = &_NSConcreteStackBlock ;

这里要提到前面说过的;所谓Block就是oc对象 ;

在这里插入图片描述
上面所使用的objc_objectj结构体和objc_objc_class 结构体是在各对象和类的实现中使用的最基本的结构体 ;

对于下面

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

该类对象的结构体如下:

struct Myobject {
Class isa ;
int val0 ;
int val1 ;
} ;

即同过isa指针保持该类结构体实例指针 ;

在这里插入图片描述

这是再看上面的代码,_NSConcreteStackBlock相当于class_t结构体实例 ;在讲Block作为oc对象处理时,关于该类信息放置于_NSConcreteStackBlock中 ;

Block结构体:
在这里插入图片描述

截获自动变量值

书上的源码:
在这里插入图片描述
在这里插入图片描述

从上面的源码中,我们可以看到Block语法表达式中使用的自动变量被作为成员变量追加到咯_main_block_impl_0 结构体中 ;

struct _main_block_impl_0 {
struct _blcok_impl impl ;
struct _main_block_desc_0* Desc ;
const char* fmt ;
int val ;
} ;

Blcoks的自动变量截获只针对Block中使用的自动变量 ;

初始化该结构体的构造函数如下:
在这里插入图片描述

_main_block_impl_结构体实例的初始化如下:

在这里插入图片描述

也就是说 自动变量是在结构体中的成员变量中被截获保存其值 ;

总的来说,所谓“截获自动变量值”意味着在执行Block语法时,Block语法表达式所使用的自动变量值被保存到Block的结构体实例中 ;

对于之前说过的Blcok不能直接使用c语言数组类型的自动变量,可以看看下面构造函数:

void func (char a[10]) {
char b [10] = a ;
printf ("%d\n",b[0]) ;
}

int main () {
char a[10] = {2} ;
func (a) ; 
}

将c语言数组类型变量赋值给c语言数组类型变量中
,这在c里时不允许的 ;

_block说明符

由上面可知,Block中所截获的自动变量仅仅截获自动变量的值。在Block结构体实例中重写该自动变量也不会改变原先可活的自动变量 ;
如果尝试修改就会引发编译错误 ;

但c语言中的

  • 静态变量
  • 静态全局变量
  • 全局变量
    在Block中任然可以使用,毕竟Block的本质还是c语言 ;
int global_val = 1 ;
static int static_global_val = 2 ;

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

转换后源码如下
在这里插入图片描述

在这里插入图片描述

主要注意一下静态变量static_val的转换 ;
在结构体得1尘缘变量中保存的是static_val的指针 ;

除了上面的三种变量,还可以使用__block说明符,也可以说是__block存储类说明符,
c语言中的存储域类说明符 :

  • typedef
  • exterm
  • static
  • auto
  • register

__block说明符类似于static,auto,register说明符,用于指定将变量值设置到哪个存储域中。

使用了__block说明符后:

__block int val = 10 ;

这时,————block变量同Block一样变成__block_byref_val_0的结构体类型变量,也就是放在栈上的结构体实例,这个结构体初始化持有原自动变量的成员变量 ;
该结构体的声明如下:

struct __block_byref_val_0 {
void* __isa ;
__block_byref_val_0 * __forwarding ;
int __flags ;
int __size ;
int val ;
};
__block_byref_val_0 val = {
0,
&val,
0,
sizeof(__Block_byref_val_0),
10
};

当给__block变量赋值时

^{val = 1;}

其源代码转换如下:
在这里插入图片描述

这里的Block中的_main_block_impl_0结构体实例持有指向——block变量的__Block_byref_val_0结构体实例指针 ;同时通过其成员变量__forwarding访问成员变量val ;

在这里插入图片描述

Block存储域

Block转换为Block的结构体类型的自动变量, __block变量转换为block变量的结构体类型的自动变量,这些都是在栈上生成的该结构体的实例变量 ;

在这里插入图片描述

这里我们需要了解三个类:

  • _NSConcreteStackBlock
  • _NSconcreteGlobalBlock
  • _NSConcreteMallocBlock
    这三个类的区别在于它们的对象灰分配在不同的存储域中;
    在这里插入图片描述

之前的Block例子都是_NSConcretestackBlock类的
(关于Block对象的类型,我们可以看isa成员变量的赋值)

当在记述全局变量的地方使用Block语法时,生成的Block对象是NSConcreteGlobalBlock类对象

void (^blk) (void) = ^ {printf("Global Block\n") ;} ;
int main () {

从名字是那个就可以看出,这类block是可以全局调用的,和全局变量性质差不多 ;

除了上面这种情况,当Block语法的表达式中不使用应截获的自动变量时,这时的Block也为_NSConcreteGlobalBlock类对象 ;这时的block对象分配程序的数据域中 ;

配置在全局变量上的Block,可以在作用域外通过指针安全使用,但设置在栈上的Block,如果其所属的变量作用域结束,该Block就被废弃 ;为类解决这一问题,Blocks将Block和_block变量从栈上复制到堆上 ;这样即使作用域结束,堆上的Block任然可以存在 ,这类复制生成的Block对象的类为_NSConcreteMallocBlock ;

在这里插入图片描述

顺便注意一下,__block变量的结构体成员__forwarding可以实现无论变量分配在栈上还是堆上时都可以正确访问————block变量 ;

当ARC有效时,大多数情形下编译器都会恰当的进行判断,自动生成将Block从栈上复制到堆上的代码 ;

typedef int (^blk_t) (int) ;
blk_t func (int rate) {
return ^ (int count) (return rate* count;} ;

通过对应的ARC编译器转换之后如下:
在这里插入图片描述

这里的objc——retainBlock函数实际上就是_Block_copy函数 ;

书上把这个地方解释的挺清楚的:
在这里插入图片描述

像上面这种情况,将Block作为函数返回值返回时,编译器会自动生成复制到堆上的代码 ;

还有一些情况需要我们手动生成代码,将Block从栈上复制到堆上:
如向方法或函数的参数中传递Block时;
举个例子:在NSArray类的initWithObjects实例方法上传递Block时需要手动复制

- (id) getBlockArray {
int val = 10 ;
return [[NSArray alloc] initwithObjects :^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk0:%d",val);},nil};
}

该源代码在执行时会发生异常,需要修改一下:

- (id) getBlockArray {
int val = 10 ;
return [[NSArray alloc] initwithObjects :[^{NSLog(@"blk0:%d",val);},copy],[^{NSLog(@"blk0:%d",val);},copy],nil};
}

从上面我们也可以知道BLock类型变量也可以调用copy方法 ;

不同类的Block对象调用copy时,复制的效果也不一样 ;

在这里插入图片描述

举报

相关推荐

第二章 IDLE

第二章 资产

第二章 翻译

TypeScript 第二章

第二章-表格

第二章:Hello,Flutter(二)

Java第二章总结

0 条评论