zoukankan      html  css  js  c++  java
  • block的内部实现

    主要内容:

    一、block相关的题目

    二、block的定义

    三、block的实现

    四、捕获自动变量值

    五、block存储区域

    六、截获对象

    一、block相关的题目

    这是一篇比较长的博文,前部分是block的测试题目,中间是block的语法、特性,block讲解block内部实现和block存储位置,请读者耐心阅读。具备block基础的同学,直接调转到block的实现

    下面列出了五道题,看看能否答对两三个。主要涉及block栈上、还是堆上、怎么捕获变量。答案在博文最后一行

    1. //-----------第一道题:--------------  
    2. void exampleA() {  
    3.   char a = 'A';  
    4.   ^{ printf("%c\n", a);};  
    5. }  
    6. A.始终能够正常运行          B.只有在使用ARC的情况下才能正常运行  
    7. C.不使用ARC才能正常运行     D.永远无法正常运行  
    8. //-----------第二道题:答案同第一题--------------  
    9. void exampleB_addBlockToArray(NSMutableArray *array) {  
    10.   char b = 'B';  
    11.   [array addObject:^{printf("%c\n", b);}];  
    12. }   
    13. void exampleB() {  
    14.   NSMutableArray *array = [NSMutableArray array];  
    15.   exampleB_addBlockToArray(array);  
    16.   void (^block)() = [array objectAtIndex:0];  
    17.   block();  
    18. }  
    19. //-----------第三道题:答案同第一题--------------  
    20. void exampleC_addBlockToArray(NSMutableArray *array) {  
    21.   [array addObject:^{printf("C\n");}];  
    22. }   
    23. void exampleC() {  
    24.   NSMutableArray *array = [NSMutableArray array];  
    25.   exampleC_addBlockToArray(array);  
    26.   void (^block)() = [array objectAtIndex:0];  
    27.   block();  
    28. }  
    29. //-----------第四道题:答案同第一题--------------  
    30. typedef void (^dBlock)();   
    31. dBlock exampleD_getBlock() {  
    32.   char d = 'D';  
    33.   return ^{printf("%c\n", d);};  
    34. }  
    35. void exampleD() {  
    36.   exampleD_getBlock()();  
    37. }  
    38. //-----------第五道题:答案同第一题--------------  
    39. typedef void (^eBlock)();   
    40. eBlock exampleE_getBlock() {  
    41.   char e = 'E';  
    42.   void (^block)() = ^{printf("%c\n", e);};  
    43.   return block;  
    44. }  
    45. void exampleE() {  
    46.   eBlock block = exampleE_getBlock();  
    47.   block();  
    48. }  

    二、block的定义

     Block是C语言的扩充功能。可以用一句话来表示Blocks的扩充功能:带有自动变量(局部变量)的匿名函数。命名就是工作的本质,函数名、变量名、方法名、属性名、类名和框架名都必须具备。而能够编写不带名称的函数对程序员来说相当有吸引力。

        例如:我们要进行一个URL的请求。那么请求结果以何种方式通知调用者呢?通常是经过代理(delegate)但是,写delegate本身就是成本,我们需要写类、方法等等。
        这时候,我们就用到了block。block提供了类似由C++和OC类生成实例或对象来保持变量值的方法。像这样使用block可以不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量,仅用编写C语言函数的源码量即可使用带有自动变量值的匿名函数。
        其他语言中也有block概念。点击查看官方block语法文档

    三、block的实现

        block的语法看上去好像很特别,但实际上是作为极为普通的C语言代码来处理的。这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。
    控制台命令是: clang -rewrite-objc 源代码文件名。 
    1. int main(){  
    2.     void (^blk)(void) = ^{printf("block\n");};  
    3.     blk();  
    4.     return 0;  
    5. }  
    经过 clang -rewrite-objc 之后,代码编程这样了(简化后代码,读者可以搜索关键字在生成文件中查找):
    1. struct __block_impl{  
    2.     voidvoid *isa;  
    3.     int Flags;  
    4.     int Reserved;  
    5.     voidvoid *FuncPtr;  
    6. };  
    7. static struct __main_block_desc_0{  
    8.     unsigned long reserved;  
    9.     unsigned long Block_size  
    10. }__main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};  
    11.   
    12. struct __main_block_impl_0{  
    13.     struct __block_impl impl;  
    14.     struct __main_block_desc_0 *Desc;  
    15. }  
    16. static struct __main_block_func_0(struct __main_block_impl_0 *__cself)  
    17. {  
    18.     printf("block\n");  
    19. }  
    20. int main(){  
    21.     struct __main_block_impl_0 *blk =  &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA);  
    22.     (*blk->impl.FuncPtr)(blk);  
    23. }  
    很多结构体,很多下划线的变量和函数名。我们一个个来:
    __block_impl:更像一个block的基类,所有block都具备这些字段。
    __main_block_impl_0:block变量。
    __main_block_func_0:虽然,block叫,匿名函数。但是,这个函数还是被编译器起了个名字。
    __main_block_desc_0:block的描述,注意,他有一个实例__main_block_desc_0_DATA
        上述命名是有规则的:main是block所在函数的名字,后缀0则是这个函数中的第0个block。由于上面是C++的代码,可以将__main_block_impl_0的结构体总结一下,得到如下形式:
    1. __main_block_impl_0{  
    2.     voidvoid *isa;  
    3.     int Flags;  
    4.     int Reserved;  
    5.     voidvoid *FuncPtr;  
    6.     struct __main_block_desc_0 *Desc;  
    7. }  
    总结:所谓block就是Objective-C的对象

    四、捕获自动变量值

    1. int val = 10;  
    2. void (^blk)(void) = ^{printf("val=%d\n",val);};  
    3. val = 2;  
    4. blk();  

        上面这段代码,输出值是:val = 10.而不是2,点击这里查看【block第二篇】block捕获变量和对象。

    那么这个block的对象结构是什么样呢,请看下面:
    1. __main_block_impl_0{  
    2.     voidvoid *isa;  
    3.     int Flags;  
    4.     int Reserved;  
    5.     voidvoid *FuncPtr;  
    6.     struct __main_block_desc_0 *Desc;  
    7.     int val;  
    8. }  
    这个val是如何传递到block结构体中的呢?
    1. int main(){  
    2.     struct __main_block_impl_0 *blk =  &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA,val);  
    3. }  
    注意函数调用最后一个参数,即val参数。
    那么函数调用的代码页转化为下面这样了.这里的cself跟C++的this和OC的self一样。
    1. static struct __main_block_func_0(struct __main_block_impl_0 *__cself)  
    2. {  
    3.     printf("val=%d\n",__cself-val);  
    4. }  

    __block说明符

        前面讲过block所在函数中的,捕获自动变量。但是不能修改它,不然就是编译错误。但是可以改变全局变量、静态变量、全局静态变量。
        其实这两个特点不难理解:第一、为何不让修改变量:这个是编译器决定的。理论上当然可以修改变量了,只不过block捕获的是自动变量的副本,名字一样。为了不给开发者迷惑,干脆不让赋值。道理有点像:函数参数,要用指针,不然传递的是副本。
        第二、可以修改静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block可以调用。
        解决block不能保存值这一问题的另外一个办法是使用__block修饰符。
    1. __block int val = 10;  
    2. void (^blk)(void) = ^{val = 1;};  
    该源码转化后如下:
    1. struct __block_byref_val_0{  
    2.     voidvoid *__isa;  
    3.     __block_byref_val_0 *__forwarding;  
    4.     int _flags;  
    5.     int __size;  
    6.     int val;  
    7. }  
    __main_block_impl_0中自然多了__block_byreg_val_0的一个字段。注意:__block_byref_val_0结构体中有自身的指针对象,难道要
    _block int val = 10;这一行代码,转化成了下面的结构体
    __block)byref_val_0 val = {0,&val,0,sizeof(__block_byref_val_0),10};//自己持有自己的指针。
    它竟然变成了结构体了。之所以为啥要生成一个结构体,后面在详细讲讲。反正不能直接保存val的指针,因为val是栈上的,保存栈变量的指针很危险。

    五、block存储区域

    这就需要引入三个名词:
    ● _NSConcretStackBlock
    ● _NSConcretGlobalBlock
    ● _NSConcretMallocBlock
    正如它们名字说的那样,说明了block的三种存储方式:栈、全局、堆。__main_block_impl_0结构体中的isa就是这个值。
    【要点1】如果是定义在函数外面的block是global的,另外如果函数内部的block但是,没有捕获任何自动变量,那么它也是全局的。比如下面这样的代码:
    1. typedef int (^blk_t)(int);  
    2. for(...){  
    3.     blk_t blk = ^(int count) {return count;};  
    4. }  
    虽然,这个block在循环内,但是blk的地址总是不变的。说明这个block在全局段。
    【要点2】一种情况在非ARC下是无法编译的:
    typedef int(^blk_t)(int);
    blk_t func(int rate){
        return ^(int count){return rate*count;}
    }
    这是因为:block捕获了栈上的rate自动变量,此时rate已经变成了一个结构体,而block中拥有这个结构体的指针。即如果返回 block的话就是返回局部变量的指针。而这一点恰是编译器已经断定了。在ARC下没有这个问题,是因为ARC使用了autorelease了。
    【要点3】有时候我们需要调用block 的copy函数,将block拷贝到堆上。看下面的代码:
    1. -(id) getBlockArray{  
    2.     int val =10;  
    3.     return [[NSArray alloc]initWithObjects:  
    4.         ^{NSLog(@"blk0:%d",val);},  
    5.         ^{NSLog(@"blk1:%d",val);},nil];  
    6. }  
    7.   
    8. id obj = getBlockArray();  
    9. typedef void (^blk_t)(void);  
    10. blk_t blk = (blk_t){obj objectAtIndex:0};  
    11. blk();  
    这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。
    【要点4】不管block配置在何处,用copy方法复制都不会引起任何问题。在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会打扫战场。
    注意:在栈上调用copy那么复制到堆上,在全局block调用copy什么也不做,在堆上调用block 引用计数增加
    【注意】本人用Xcode 5.1.1 iOS sdk 7.1 编译发现:并非《Objective-C》高级编程这本书中描述的那样
    int val肯定是在栈上的,我保存了val的地址,看看block调用前后是否变化。输出一致说明是栈上,不一致说明是堆上。
    1. typedef int (^blkt1)(void) ;  
    2. -(void) stackOrHeap{  
    3.     __block int val =10;  
    4.     intint *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上  
    5.     blkt1 s= ^{  
    6.         NSLog(@"val_block = %d",++val);  
    7.         return val;};  
    8.     s();  
    9.     NSLog(@"valPointer = %d",*valPtr);  
    10. }  
    在ARC下——block捕获了自动变量,那么block就被会直接生成到堆上了。 val_block = 11 valPointer = 10
    在非ARC下——block捕获了自动变量,该block还是在栈上的。 val_block = 11 valPointer = 11

    调用copy之后的结果呢:

    1. -(void) stackOrHeap{  
    2.     __block int val =10;  
    3.     intint *valPtr = &val;//使用int的指针,来检测block到底在栈上,还是堆上  
    4.     blkt1 s= ^{  
    5.         NSLog(@"val_block = %d",++val);  
    6.         return val;};  
    7.     blkt1 h = [s copy];  
    8.     h();  
    9.     NSLog(@"valPointer = %d",*valPtr);  
    10. }  

    在ARC下>>>>>>>>>>>无效果。 val_block = 11 valPointer = 10

    在非ARC下>>>>>>>>>确实复制到堆上了。 val_block = 11 valPointer = 10
    用这个表格来表示
     

    在ARC下:似乎已经没有栈上的block了,要么是全局的,要么是堆上的
    在非ARC下:存在这栈、全局、堆这三种形式。
    更详细的描述专题点击打开链接
     

    __block变量存储区域

    当block被复制到堆上时,他所捕获的对象、变量也全部复制到堆上。
    回忆一下block捕获自动变量的时候,自动变量将编程一个结构体,结构体中有一个字段叫__forwarding,用于指向自动这个结构体。那么有了这个__forwarding指针,无论是栈上的block还是被拷贝到堆上,那么都会正确的访问自动变量的值。

    六、截获对象

    block会持有捕获的对象。编译器为了区分自动变量和对象,有一个类型来区分。
    1. static void __main_block_copy_0(struct __main_block_impl_0 *dst, struct __main_block_impl_0 *src){  
    2.     _Block_objct_assign(&dst->val,src->val,BLOCK_FIELD_IS_BYREF);  
    3. }  
    4. static void __main_block_dispose_0(struct __main_block_impl_0 *src){  
    5.     _block_object_dispose(src->val,BLOCK_FIELD_IS_BYREF);  
    6. }  
    BLOCK_FIELD_IS_BYREF代表是变量。BLOCK_FIELD_IS_OBJECT代表是对象
    【__block变量和对象】
        __block修饰符可用于任何类型的自动变量
    【__block循环引用】
    根据上面讲的内容,block在持有对象的时候,对象如果持有block,会造成循环引用。解决办法有两种:
    1. 使用__weak修饰符。id __weak obj = obj_
    2. 使用__block修饰符。__block id tmp = self;然后在block中tmp = nil;这样就打破循环了。这个办法需要记得将tmp=nil。不推荐!
    技术改变世界,成就人生辉煌!
  • 相关阅读:
    KDJ回测
    利用网易获取所有股票数据
    利用东方财富网获取股票代码
    python发邮件
    用指针向数组插入元素
    冒泡排序
    Hadoop的安装与配置
    关于执行memcached报错问题
    tomcat Linux安装
    网易CentOS yum源
  • 原文地址:https://www.cnblogs.com/wuhanpjf/p/4601756.html
Copyright © 2011-2022 走看看