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

  • 相关阅读:
    关于秒杀的系统架构优化思路
    如何设计一个秒杀系统
    RabittMQ实践(二): RabbitMQ 与spring、springmvc框架集成
    RabittMQ实践(一): RabbitMQ的安装、启动
    Java 网络IO编程总结(BIO、NIO、AIO均含完整实例代码)
    Linux 网络 I/O 模型简介(图文)
    Java 网络编程(六) 使用无连接的数据报(UDP)进行通信
    Java 网络编程(五) 使用TCP/IP的套接字(Socket)进行通信
    Java 网络编程(四) InetAddress类
    Java 网络编程(三) 创建和使用URL访问网络上的资源
  • 原文地址:https://www.cnblogs.com/mthoutai/p/7142153.html
Copyright © 2011-2022 走看看