当前位置: 首页 > 科技观察

深入Block

时间:2023-03-21 21:36:38 科技观察

的实现先看Block声明一个block返回值的语法(^name)(parameterlist)=^(parameterlist){};int(^name)(int,int)=^(inta,intb){return(a+b);};作为函数参数:-(void)testBlock:(NSString*(returntype)(^)(inta))s(块名){NSString*a=s(1);}[selftestBlock:^NSString*(inta){a=5;return@"1";}];然后通过底层代码分析block的实现。iOS中一共有三种block类型,下面会详细介绍NSConcreteGlobalBlock;//global中定义的NSConcreteStackBlock;//NSConcreteMallocBlockdefinedlocally;//从最简单的看先在heap中分配intmain(intargc,char*argv[]){void(^block)(void)=^{NSLog(@"1");};(在终端找到这个.m文件,然后clang-rewrite-objc代码文件名,可以看到文件夹里有一个.cpp文件,本来想改ViewController文件的,用的是UIKit库里面,而且编译的时候一直显示找不到,所以我编译的main.m文件)被编译成了block源码。这里我们分析一下(通俗易懂):staticvoid_main_block_func_0(struct_main_block_impl_0*__cself){NSLog((NSString*)&__NSConstantStringImpl__var_folders_yq_s_hjnhd12x79wq1ldg1jdr_w0000gn_T_main_50d1d6_mi_0);}可以看到这对应到我们代码中的实现,所以我们可以知道block中的实现该块使用的匿名函数实际上被视为一个函数。但是传入的是:一个_main_block_impl_0类型的结构体,其中包含一个block_impl结构体和一个_main_block_desc_0结构体。其次是他们的构造函数。我们简单看一下这个_main_block_impl_0结构体:isa指向这个block的类型。这里声明这个block是NSConcreteStackBlock类型的。flag是标志,可以看到,默认结构是0;还有一个FuncPtr,它是指向函数地址的指针。还有一个__main_block_desc_0结构体,下面可以看到这个结构体的初始化,一个是reserverd默认为0,一个是block_size。是这个impl的大小。所以,这个_main_block_impl_0可以理解为一个block实例,里面的成员变量有一个指向要执行的函数的指针,isa(和所有oc对象一样),和一个size。现在看主函数的内容void(*name)(void)=((void(*)())&__main_block_impl_0((void*)__main_block_func_0,&__main_block_desc_0_DATA));这里执行的操作是:初始化一个block实例,通过给我们一个这样的name变量,也就是用name的指针指向这个block实例。执行时,直接在本块中找到指向函数地址的指针。从上面我们可以了解到,块的本质是一个对象,它包含了一个指向函数首地址的指针,以及一些与自身相关的成员变量。那么我们就来看看block是如何访问外部变量的,这是我们最关心的。先看局部变量:inta=10;intb=20;void(^block)()=^{NSLog(@"%d--%d",a,b);};block();块访问局部变量首先出现在_main_block_impl_0的定义中。块中使用的变量作为成员变量添加到结构中。_main_block_impl_0(void*fp,struct_main_block_desc_0*desc,int_a,int_b,intflags=0):a(_a),b(_b){impl.isa=&_NSConcreteStackBlock;impl.Flags=flags;impl.FuncPtr=fp;Desc=desc;}在初始化的时候,还需要传入a和b的值。void(**block)()=((void(*)())&_main_block_impl_0((void*)_main_block_func_0,&_main_block_desc_0_DATA,a,b));这里,在初始化的时候,传入a和b的值来初始化一个_main_block_impl_0结构体。这里需要注意的是a和b的值是传入的,所以我们在结构体中改变a和b是无效的。如果要更改块内的值,编译会报错。有以下几种方法1、静态变量,全局变量直接上代码,嘻嘻intglobal_val=1;staticintstatic_global_val=2;intmain(intargc,char*argv[]){staticintstatic_val=3;void(^block)()=^{global_val=10;static_global_val=20;static_val=30;};NSLog(@"%d-%d-%d",global_val,static_global_val,static_val);block();NSLog(@"%d-%d-%d",global_val,static_global_val,static_val);}然后改写访问全局变量因为这个_main_block_func_0可以直接访问静态变量,所以可以直接访问这个变量的值,也可以改,不要担心调用这个函数,全局变量无法访问,导致报错。所以这里调用全局变量的时候,调用全局变量是很普通的,没有区别。局部静态变量传递一个指针。由于您不必担心它会在函数结束时或其他任何地方被释放,因此您可以安全地访问该值。global_val=10;static_global_val=20;(*static_val)=30;改变的是这个指针指向的值可以看到这里传递了指针。_main_block_func_0的范围在主函数之外。要访问这个变量,你只能传递一个指针。第二种方法是在参数中加上__block属性intmain(intargc,char*argv[]){__blockintblock_val=3;void(^block)()=^{block_val=30;};NSLog(@"%d",block_val);block();NSLog(@"%d",block_val);NSLog(@"%@",block);}__block参数,可以看到多了一个这样的结构_Block_byref_block_val_0,其中包含:_isa初始化为0,__forwarding;//持有实例本身的指针intflags;is0ints__Block_byref_block_val_0ize;sizeintblock_val;//存储这个变量的值本来就是一个局部变量,赋值的时候封装成一个结构体直接在这个结构体中赋值(block_val->__forwarding->block_val)=30;所以,访问这个变量的时候,实际上是在访问这个结构体的这个变量。不要担心_forwarding的作用。Block的三种类型:{NSConcreteGlobalBlock;//全局定义的NSConcreteStackBlock;//局部定义的NSConcreteMallocBlock;//分配在堆中}设置在栈上的块,当“名称变量”的作用域结束时,块变量也会被丢弃。因此iOS提供了将block结构和_block变量复制到堆中的方法。即使block的name变量结束,堆上的block也可以继续访问。这时候_block变量结构中的_forwarding变量就可以实现了,不管是在堆上还是在栈上。两者都可以正确访问_block变量。可以这样理解,当_block变量被复制到堆中时,_forwarding指向堆中的自身。所以无论你是在栈中还是在堆中访问自己,最终访问的都是堆中的值。Block对_block的内存管理方式和ARC机制是完全一样的。_main_block_desc中的copy和dispose就是这个__block的retain和release操作。那么那个时候什么块会被复制到堆中呢?使用块的复制方法。当块作为函数返回值返回时。block在外面调用_strongid类的时候,或者使用_block的时候。在方法中,在usingblock或GCD中使用API??时。这里要说一下,在局部函数中,定义block时,打印出来的还是NSConcreteGlobalBlock类型,而只要使用了外部变量,不管是assign、week还是strong类型,打印出来的都是NSConcreteMallocBlock类型。所以我猜测这是否是苹果新版本的改进。为了让块访问无效变量,直接将块复制到堆中,从而也复制了一个变量。可能是我忽略了中间的某个步骤,居然到了这里。我不需要再描述它了。我也知道为什么会发送无限循环,以及如何解决。在块中使用self时,块被复制到堆中。首先,栈上的block有一个holder,就是变量名。当变量名超出范围时,堆栈上的块被释放。,那么当block复制到堆中时,其中一个blockholder是self,那么当复制block时,它的变量是一个self指针,也会被复制,self指向这个block,block持有self,self持有块,两者都不会被释放。要打破这个循环,需要将self设置为__week,即使复制一个week指针,也不会影响self的引用计数。