-------------------------------------------欢迎查看block连载博客【专栏】--------------------------------------
【block编程第一篇】block语法 【block编程第二篇】block捕获变量和对象。
【block编程第三篇】block的内存管理。 【block编程第四篇】block内部实现(当前)
【block编程第五篇】block中怎样避免循环引用
------------------------------------------------------------------------------------------------------------------------------
一、先看几道block相关的题目
这是一篇比較长的博文,前部分是block的測试题目,中间是block的语法、特性,block解说block内部实现和block存储位置。请读者耐心阅读。具备block基础的同学。直接调转到block的实现。
以下列出了五道题。看看是否能答对两三个。主要涉及block栈上、还是堆上、怎么捕获变量。
答案在博文最后一行
//-----------第一道题:-------------- void exampleA() { char a = 'A'; ^{ printf("%c\n", a);}; } A.始终能够正常执行 B.仅仅有在使用ARC的情况下才干正常执行 C.不使用ARC才干正常执行 D.永远无法正常执行 //-----------第二道题:选项同第一题-------------- void exampleB_addBlockToArray(NSMutableArray *array) { char b = 'B'; [array addObject:^{printf("%c\n", b);}]; } void exampleB() { NSMutableArray *array = [NSMutableArray array]; exampleB_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); } //-----------第三道题:选项同第一题-------------- void exampleC_addBlockToArray(NSMutableArray *array) { [array addObject:^{printf("C\n");}]; } void exampleC() { NSMutableArray *array = [NSMutableArray array]; exampleC_addBlockToArray(array); void (^block)() = [array objectAtIndex:0]; block(); } //-----------第四道题:选项同第一题-------------- typedef void (^dBlock)(); dBlock exampleD_getBlock() { char d = 'D'; return ^{printf("%c\n", d);}; } void exampleD() { exampleD_getBlock()(); } //-----------第五道题:选项同第一题-------------- typedef void (^eBlock)(); eBlock exampleE_getBlock() { char e = 'E'; void (^block)() = ^{printf("%c\n", e);}; return block; } void exampleE() { eBlock block = exampleE_getBlock(); block(); }
【注】:以上题目摘自:CocoaChina论坛打开链接
二、block的定义
Block是C语言的扩充功能。
能够用一句话来表示Blocks的扩充功能:带有自己主动变量(局部变量)的匿名函数。
命名就是工作的本质。函数名、变量名、方法名、属性名、类名和框架名都必须具备。而能够编写不带名称的函数对程序猿来说相当有吸引力。
那么请求结果以何种方式通知调用者呢?一般是经过代理(delegate)可是,写delegate本身就是成本,我们须要写类、方法等等。
block提供了相似由C++和OC类生成实例或对象来保持变量值的方法。像这样使用block能够不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量,仅用编写C语言函数的源代码量就可以使用带有自己主动变量值的匿名函数。
三、block的实现
这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。
int main(){ void (^blk)(void) = ^{printf("block\n");}; blk(); return 0; }
struct __block_impl{ void *isa; int Flags; int Reserved; void *FuncPtr; }; static struct __main_block_desc_0{ unsigned long reserved; unsigned long Block_size }__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)}; struct __main_block_impl_0{ struct __block_impl impl; struct __main_block_desc_0 *Desc; } static struct __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("block\n"); } int main(){ struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA); (*blk->impl.FuncPtr)(blk); }
__main_block_impl_0:block变量。
__main_block_func_0:尽管。block叫匿名函数。
可是,这个函数还是被编译器起了个名字。
__main_block_desc_0:block的描写叙述,注意,他有一个实例__main_block_desc_0_DATA
由于上面是C++的代码,能够将__main_block_impl_0的结构体总结一下,得到例如以下形式:
__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; }
四、捕获自己主动变量值
int val = 10; void (^blk)(void) = ^{printf("val=%d\n",val);}; val = 2; blk();
上面这段代码,输出值是:val = 10.而不是2,这里查看【block第二篇】block捕获变量和对象。
__main_block_impl_0{ void *isa; int Flags; int Reserved; void *FuncPtr; struct __main_block_desc_0 *Desc; int val; }
int main(){ struct __main_block_impl_0 *blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val); }注意函数调用最后一个參数,即val參数。
static struct __main_block_func_0(struct __main_block_impl_0 *__cself) { printf("val=%d\n",__cself-val); }
__block说明符
可是不能改动它,不然就是编译错误。
可是能够改变全局变量、静态变量、全局静态变量。
为了不给开发人员迷惑。干脆不让赋值。道理有点像:函数參数,要用指针,不然传递的是副本。
__block int val = 10; void (^blk)(void) = ^{val = 1;};
struct __block_byref_val_0{ void *__isa; __block_byref_val_0 *__forwarding; int _flags; int __size; int val; }
注意:__block_byref_val_0结构体中有自身的指针对象。难道要
之所以为啥要生成一个结构体,后面在具体讲讲。反正不能直接保存val的指针。由于val是栈上的,保存栈变量的指针非常危急。
五、block存储区域
typedef int (^blk_t)(int); for(...){ blk_t blk = ^(int count) {return count;}; }
-(id) getBlockArray{ int val =10; return [[NSArray alloc]initWithObjects: ^{NSLog(@"blk0:%d",val);}, ^{NSLog(@"blk1:%d",val);},nil]; } id obj = getBlockArray(); typedef void (^blk_t)(void); blk_t blk = (blk_t)[obj objectAtIndex:0]; blk();
在ARC环境下,假设不确定是否要copy block尽管copy就可以。ARC会打扫战场。
输出一致说明是栈上,不一致说明是堆上。
typedef int (^blkt1)(void) ; -(void) stackOrHeap{ __block int val =10; int *valPtr = &val;//使用int的指针,来检測block究竟在栈上。还是堆上 blkt1 s= ^{ NSLog(@"val_block = %d",++val); return val;}; s(); NSLog(@"valPointer = %d",*valPtr); }
val_block = 11 valPointer = 11
调用copy之后的结果呢:
-(void) stackOrHeap{ __block int val =10; int *valPtr = &val;//使用int的指针,来检測block究竟在栈上。还是堆上 blkt1 s= ^{ NSLog(@"val_block = %d",++val); return val;}; blkt1 h = [s copy]; h(); NSLog(@"valPointer = %d",*valPtr); }
在ARC下>>>>>>>>>>>无效果。 val_block = 11 valPointer = 10
__block变量存储区域
那么有了这个__forwarding指针。不管是栈上的block还是被拷贝到堆上,那么都会正确的訪问自己主动变量的值。
六、截获对象
编译器为了区分自己主动变量和对象,有一个类型来区分。
static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){ _Block_objct_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF); } static void __main_block_dispose_0(struct __main_block_impl_0 *src){ _block_object_dispose(src->val,BLOCK_FIELD_IS_BYREF); }
__block修饰符可用于不论什么类型的自己主动变量
【__block循环引用】
依据上面讲的内容,block在持有对象的时候,对象假设持有block,会造成循环引用。解决的方法有两种:
1. 使用__weak修饰符。
id __weak obj = obj_
2. 使用__block修饰符。__block id tmp = self;然后在block中tmp = nil。这样就打破循环了。这个办法须要记得将tmp=nil。不推荐!
文章开头block測试题答案:ABABB