最新资讯
联系我们
全国服务热线:
地址:
这里是您的公司地址
手机:
138-8888-8888
电话:
邮箱:
这里是您公司的邮箱地址
售后服务 当前位置:售后服务
神奇的 Block添加时间:2022-10-03 04:06:49

(点击上方公众号,神奇可快速关注) 来源:__微凉 链接:http://www.jianshu.com/p/4b1cfd7e6361 本文不做Block的神奇基本介绍和底层实现原理,有兴趣的神奇同学直接戳这篇文章(http://www.jianshu.com/p/51d04b7639f1),写得灰常好,神奇本文只在应用层面上带领读者进行思考,神奇并整理出一些结论.这些结论是神奇我从书上和上网资料收集所得,并通过实践进行验证而来,神奇希望能和高手们共同探讨 :)在看例子之前,神奇至少要知道block有几个类型. _NSConcreteGlobalBlock(全局块) _NSConcreteStackBlock(栈块) _NSConcreteMallocBlock(堆块) 废话不说,神奇直接看例子.测试环境为ARC,神奇就不做MRC的神奇测试了.精神病入门例子一: typedefvoid(^blk_t)(); intmain(intargc,constchar* argv){ blk_t block= ^{ printf("I'm just a block\n"); }; block(); return0; } 很简单的神奇一段代码,执行block之后结果是神奇I'm just a block.但如果问你,这个block是神奇什么类型的block,你会怎么回答?在代码中打一个断点,神奇通过打印block的isa,可以知道该block是什么类型的.第一步:打个断点 第二步:打印isa 然后就能看到结果了: 看到结果,尼玛居然是个全局块,可是我明明是在栈上创建的一个block呀!再来看一个例子,这时定义了一个局部变量,并在block中使用了这个局部变量.例子二: typedefvoid(^blk_t)(); intmain(intargc,constchar* argv){ inti= 1; blk_t block= ^{ printf("%d\n",i); }; block(); return0; } 按照以上步骤再看看block的isa ……我去,怎么成堆块了?别急,再举个??.例子三: typedefvoid(^blk_t)(); intmain(intargc,constchar* argv){ inti= 1; __weak blk_t block= ^{ printf("%d\n",i); }; block(); return0; } 虽然编译器在__weak blk_t block = ^{ 这行爆出了警告,但是程序还是能够正常运行.block并未因为一个弱引用立即释放.然后看看结果: 终于看到栈块了,全家福终于齐人了.下面开始总结了.在哪些情况下,Block为_NSConcreteGlobalBlock类对象? 记述全局变量的地方创建的Block,比如下面的例子. blk_t block= ^{ printf("I'm just a block"); }; intmain(intargc,constchar* argv){ block(); return0; } 不截获自动变量的时候. 即例子一这种情况下.虽然是在栈上创建的一个block,但由于闭包内不截获外部的自动变量(局部变量),将会被编译器编译为_NSConcreteGlobalBlock. 再来总结一下第二个例子.之所以是一个堆块,是因为编译器为块进行了copy操作(实质上是调用_Block_copy函数).以下方式会让块从栈复制到堆上. 调用Block的copy实例方法. [^{ printf("a heap block"); }copy]; // 对block调用copy,会把栈上的block复制到堆上. 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时. 也就是说,有个__strong修饰的变量指向这个block就会让编译器为block调用copy方法. 例子二就是将Block赋值给了一个__strong(默认都是strong)修饰的Block类型成员变量——blk_t block. 因此,在例子三中,我将强引用变成弱引用,创建了一个栈上的block.虽然编译器会有警告,因为编译器在这里可能还不知道那个块也是栈上的,而这个栈上的块,显然不会立即释放. Block作为函数返回值时 例如: blk_t return_A_Block(){ intval= 10; return^{ NSLog(@"%d",val);}; } intmain(intargc,constchar* argv){ NSLog(@"%@",return_A_Block()); return0; } 打印所得是一个堆块.Block的副本Block的类 副本源的配置存储域 复制效果_NSConcreteStackBlock 栈 从栈复制到堆_NSConcreteGlobalBlock 程序的数据区域 什么也不做_NSConcreteMallocBlock 堆 引用计数增加精神病进阶例子四: typedefvoid(^blk_t)(idobj); intmain(intargc,constchar* argv){ blk_t blk; { idarray= [[NSMutableArrayalloc]init]; blk= ^(idobj){ [arrayaddObject:obj]; NSLog(@"%ld",[array count]); }; } // array超出了作用域,在括号外已经不能被使用了 blk([NSObjectnew]); blk([NSObjectnew]); blk([NSObjectnew]); return0; } 打印台输出结果: 可以看到,在超出了作用域后,array依旧能够被访问到.例子五: intmain(intargc,constchar* argv){ blk_t blk; { id array= [[NSMutableArray alloc]init]; id __weak array2= array; blk= ^(id obj){ [array2 addObject:obj]; NSLog(@"%ld",[array2 count]); }; } blk([NSObject new]); blk([NSObject new]); blk([NSObject new]); return0; } 打印台结果: 大相径庭的结果.在例子四中,Block中截获了外部的自动变量,并且根据上面说过的结论,编译器为我们调用了copy方法,这个Block是个堆块.我们将例子四稍加改写,将块改为栈块(即不让编译器为我们调用copy方法): intmain(intargc,constchar* argv){ __weak blk_t blk; { id array= [[NSMutableArray alloc]init]; blk= ^(id obj){ [arrayaddObject:obj]; NSLog(@"%ld",[arraycount]); }; } blk([NSObject new]); blk([NSObject new]); blk([NSObject new]); return0; } 打印出来的结果和例子五一致.从该例子得出的结论是: 只有调用了Block的copy方法,才能持有截获的附有__strong修饰符的对象类型的自动变量值. 基于这个结论,我们还可以得出,在ARC环境下,定义block类型的属性时,可以用strong,并不是非得用copy才是正确的. // 两者效果一样 @property(strong,nonatomic)blk_t *block; @property(copy,nonatomic)blk_t *block; 在例子五中,虽然截获的自动变量是__weak修饰符修饰的对象类型.但是作用域过后array被释放,nil被赋值给了array2,并不能持有对象.这让我们想起了平时为了防止循环引用,我们会用一个弱指针指向self,并让block捕获弱指针而不是让block持有self. 注意,即使你不使用self.object访问实例变量,而是通过_object访问,也同样会造成循环引用.因为无论用什么形式访问实例变量,经过编译后,最终都会转换成self+变量内存偏移的形式来进行访问,还是会造成循环引用. 那么,截获和__block修饰有何不同呢?block本质也是一个结构体,截获的对象会成为结构体成员的一部分.例如: intmain(intargc,constchar* argv){ id obj; ^{ obj; }; return0; } 其中生成的block会是这样子的: struct__main_block_impl_0{ struct__block_impl impl; struct__Person__test_block_desc_0*Desc; id __strong obj; }; 注:在C语言中,结构体不能含有附有__strong修饰的变量.因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存. 而OC却可以,它能够准确的把握block从栈复制到堆以及堆上的block被废弃的时机. 如果是通过__block修饰的一个变量呢? intmain(intargc,constchar* argv){ __block inta; ^{ a= 10; }; return0; } 其中生成的block会是这样子的: struct__main_block_impl_0{ struct__block_impl impl; struct__Person__test_block_desc_0*Desc; __Block_byref_a_0 *a;// by ref }; 此时,被__block修饰的变量变成了一个结构体(__Block_byref_a_0类型).至于这个结构体又长什么样,就不贴代码了,只需知道a被包装到了这个结构体中,成为其中一个成员变量,其他成员变量描述了该结构体的一些信息. 当Block被拷贝到堆上的时候,附有__strong修饰的变量因为Block结构体内有强指针持有,使得该指针所指向的对象在作用域外还有引用计数,因此存活着. 当Block被拷贝到堆上的时候,被__block修饰的变量被包装到了一个新的结构体中,被block结构体持有,该结构体跟随Block也被拷贝到堆上了. 截获的方式并不能修改截获的变量本身,而__block修饰的方式却可以,因为它本质是复制了一份该变量. 根据结论,可以知道用__block修饰的方式也能够避免循环引用.只要在块中将需要避免循环引用的变量置为nil.如: - (id)init{ self= [superinit]; __block id tmp= self; blk_t blk= ^{ tmp.name= @"ye"; tmp= nil; } returnself; } 如果最后不置为nil,那么self持有block结构体,block结构体持有__block变量结构体,__block变量结构体持有self,只有在最后将_block变量结构体中的self置空,才能手动破除循环.这个方式比weak方式优点的地方在于可控制对象的持有期间. 关注「 iOS大全 」看更多精选 iOS 技术文章↓↓↓

sitemap