zoukankan      html  css  js  c++  java
  • 【block第四篇】实现

    -------------------------------------------欢迎查看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
    ", a);};
    }
    A.始终能够正常执行          B.仅仅有在使用ARC的情况下才干正常执行
    C.不使用ARC才干正常执行     D.永远无法正常执行
    //-----------第二道题:选项同第一题--------------
    void exampleB_addBlockToArray(NSMutableArray *array) {
      char b = 'B';
      [array addObject:^{printf("%c
    ", 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
    ");}];
    } 
    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
    ", d);};
    }
    void exampleD() {
      exampleD_getBlock()();
    }
    //-----------第五道题:选项同第一题--------------
    typedef void (^eBlock)(); 
    eBlock exampleE_getBlock() {
      char e = 'E';
      void (^block)() = ^{printf("%c
    ", e);};
      return block;
    }
    void exampleE() {
      eBlock block = exampleE_getBlock();
      block();
    }

    【注】:以上题目摘自:CocoaChina论坛打开链接

    二、block的定义

     Block是C语言的扩充功能。

    能够用一句话来表示Blocks的扩充功能:带有自己主动变量(局部变量)的匿名函数

    命名就是工作的本质。函数名、变量名、方法名、属性名、类名和框架名都必须具备。而能够编写不带名称的函数对程序猿来说相当有吸引力。

        比如:我们要进行一个URL的请求。

    那么请求结果以何种方式通知调用者呢?一般是经过代理(delegate)可是,写delegate本身就是成本,我们须要写类、方法等等。

        这时候,我们就用到了block。

    block提供了相似由C++和OC类生成实例或对象来保持变量值的方法。像这样使用block能够不声明C++和OC类,也没有使用静态变量、静态全局变量或全局变量,仅用编写C语言函数的源代码量就可以使用带有自己主动变量值的匿名函数。

        其它语言中也有block概念。

    查看官方block语法文档

    三、block的实现

        block的语法看上去好像非常特别。但实际上是作为极为普通的C语言代码来处理的。

    这里我们借住clang编译器的能力:具有转化为我们可读源代码的能力。

    控制台命令是: clang -rewrite-objc 源代码文件名称。 
    int main(){
        void (^blk)(void) = ^{printf("block
    ");};
        blk();
        return 0;
    }
    经过 clang -rewrite-objc 之后。代码编程这样了(简化后代码。读者能够搜索keyword在生成文件里查找):
    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
    ");
    }
    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);
    }
    非常多结构体,非常多下划线的变量和函数名。我们一个个来:
    __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的结构体总结一下,得到例如以下形式:

    __main_block_impl_0{
        void *isa;
        int Flags;
        int Reserved;
        void *FuncPtr;
        struct __main_block_desc_0 *Desc;
    }
    总结:所谓block就是Objective-C的对象

    四、捕获自己主动变量值

    int val = 10;
    void (^blk)(void) = ^{printf("val=%d
    ",val);};
    val = 2;
    blk();

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

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

    __block说明符

        前面讲过block所在函数中的。捕获自己主动变量。

    可是不能改动它,不然就是编译错误。

    可是能够改变全局变量、静态变量、全局静态变量。

        事实上这两个特点不难理解:第一、为何不让改动变量:这个是编译器决定的。理论上当然能够改动变量了,仅仅只是block捕获的是自己主动变量的副本,名字一样。

    为了不给开发人员迷惑。干脆不让赋值。道理有点像:函数參数,要用指针,不然传递的是副本。

        第二、能够改动静态变量的值。静态变量属于类的,不是某一个变量。所以block内部不用调用cself指针。所以block能够调用。

        解决block不能保存值这一问题的另外一个办法是使用__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;
    }
    __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可是,没有捕获不论什么自己主动变量,那么它也是全局的。比方以下这种代码:
    typedef int (^blk_t)(int);
    for(...){
        blk_t blk = ^(int count) {return count;};
    }
    尽管。这个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拷贝到堆上。看以下的代码:
    -(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();
    这段代码在最后一行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调用前后是否变化。

    输出一致说明是栈上,不一致说明是堆上。

    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);
    }
    在ARC下——block捕获了自己主动变量,那么block就被会直接生成到堆上了。 val_block = 11 valPointer = 10
    在非ARC下——block捕获了自己主动变量,该block还是在栈上的。

     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

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

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

    __block变量存储区域

    当block被拷贝到堆上时。他所捕获的对象、变量也所有拷贝到堆上。
    回顾一下block捕获自己主动变量的时候,自己主动变量将编程一个结构体。结构体中有一个字段叫__forwarding,用于指向自己主动这个结构体。

    那么有了这个__forwarding指针。不管是栈上的block还是被拷贝到堆上,那么都会正确的訪问自己主动变量的值。

    六、截获对象

    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_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。不推荐!


    文章开头block測试题答案:ABABB

  • 相关阅读:
    Kubernetes 集成研发笔记
    Rust 1.44.0 发布
    Rust 1.43.0 发布
    PAT 甲级 1108 Finding Average (20分)
    PAT 甲级 1107 Social Clusters (30分)(并查集)
    PAT 甲级 1106 Lowest Price in Supply Chain (25分) (bfs)
    PAT 甲级 1105 Spiral Matrix (25分)(螺旋矩阵,简单模拟)
    PAT 甲级 1104 Sum of Number Segments (20分)(有坑,int *int 可能会溢出)
    java 多线程 26 : 线程池
    OpenCV_Python —— (4)形态学操作
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7142153.html
Copyright © 2011-2022 走看看