blocks_blockstack - CSDN
精华内容
参与话题
  • Blocks的实现

    2019-02-27 03:03:03
    Blocks的原理,每当自己对知识体系有一定提升之后,再回过头来看一下曾经读过的书籍,会发现对它的理解逐步加深。借着读书笔记活动,立个小目标,把Block彻底搞明白,重读《Objective-C高级编程 iOS与OS X多线程和...

    前言

    Blocks的原理,每当自己对知识体系有一定提升之后,再回过头来看一下曾经读过的书籍,会发现对它的理解逐步加深。借着读书笔记活动,立个小目标,把Block彻底搞明白,重读《Objective-C高级编程 iOS与OS X多线程和内存管理》第二章节block原理部分,一方面给自己做个笔记,另一方面加深一下印象。

    目录

    • Block的实质
    • Block捕获自动变量的值
    • __block的实质
    • Block存储域
    • __block变量存储域
    • 截获对象
    • __block变量和对象
    • Block循环引用

    1.block实质

    block代码:

    void (^blk)(void) = ^ {
            printf("Block");
        };
        blk();
    复制代码

    执行xcrun -sdk iphonesimulator clang -rewrite-objc 源代码文件名就能将含有Block的代码转换为C++的源代码。我是按照书上的示例,同样转换的main.m文件,转换完之后这里就会多出一个main.cpp的文件,打开很恐怖,六万多行...

    实际上和block相关的代码在最后几十行:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
            printf("Block");
        }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, char * argv[]) {
    
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
        
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    复制代码

    这就是我们一直在使用的block,因为都是struct结构看上去有点抽象,不过理解起来并不难。

    首先先从__main_block_func_0函数开始,因为我们想要执行的回调看源码都是写在这个函数里面的,block使用的匿名函数(也就是我们定义的block)实际上被作为简单的C语言函数(block__main_block_func_0)来处理,该函数的参数__cself相当于OC实例方法中指向对象自身的变量self,即__self为指向Block值的变量。__self与OC里面的self相同也是一个结构体指针,是__main_block_impl_0结构体的指针,这个结构体声明如下:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    复制代码

    第一个变量是impl,也是一个结构体,声明如下:

    struct __block_impl {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
    };
    复制代码

    先看FuncPrt,这个就是block括号中函数的函数指针,调用它就能执行block括号中的函数,实际上在调用block的时候就是调用的这个函数指针,执行它指向的具体函数实现。 第二个成员变量是Desc指针,以下为其__main_block_desc_0结构体声明:

    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    }
    复制代码

    其结构为今后版本升级所需的区域和Block的大小。 实际上__main_block_impl_0结构体展开最后就是这样:

    struct __main_block_impl_0 {
      void *isa;
      int Flags;
      int Reserved;
      void *FuncPtr;
      struct __main_block_desc_0* Desc;
    };
    复制代码

    还定义了一个初始化这个结构体的构造函数:

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    复制代码

    这就是整个__main_block_impl_0结构体所包含的,既然定义了这个结构体的初始化函数,那在详细看一下它的初始化过程,实际上该结构体会像下面这样初始化:

    isa = &_NSConcreteStackBlock;
    Flags = 0;
    Reserved = 0;
    FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    复制代码

    __main_block_func_0这不就是上面说到的那个指向函数实现的那个函数指针,也就是说只需要调用到结构体里面的FuncPtr就能调用到我们的具体实现了。那这个构造函数在哪里初始化的,看上面的源码是在我们定义block的时候:

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
    复制代码

    简化为:

    struct __mian_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    struct __main_block_impl_0 *blk = &tmp;
    复制代码

    该源代码将__mian_block_impl_0结构体类型的自动变量,即栈上生成的__mian_block_impl_0结构体实例的指针,赋值给__mian_block_impl_0结构体指针类型的变量blk。听起来有点绕,实际上就是我们最开始定义的blk__main_block_impl_0结构体指针指向了__main_block_impl_0结构体的实例。

    接下来看看__main_block_impl_0结构体实例的构造参数:

    __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
    复制代码

    第一个参数为由Block语法转换的C语言函数指针,第二个参数是作为静态全局变量初始化的__main_block_desc_0结构体实例指针,以下为__main_block_desc_0结构体实例的初始化部分代码:

    static struct __main_block_desc_0 __main_block_desc_0_DATA = { 
        0, 
        sizeof(struct __main_block_impl_0)
    };
    复制代码

    __main_block_impl_0结构体实例的大小。

    接下来看看栈上的__main_block_impl_0结构体实例(即Block)是如何根据这些参数进行初始化的。也就是blk()的具体实现:

    ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    复制代码

    简化以下:

    (*blk->impl.FuncPtr)(blk);
    复制代码

    FuncPtr正是我们初始化__main_block_desc_0结构体实例时候传进去的函数指针,这里使用这个函数指针调用了这个函数,正如我们刚才所说的,有block语法转换的__main_block_func_0函数的指针被赋值成员变量FuncPtr中。blk也是作为参数进行传递的,也就是最开始讲到的__cself。到此block的初始化和调用过程就结束了。

    2.Block捕获自动变量的值

    基于上面的例子,额外增加个局部变量val:

     int val = 0;
     void (^blk)(void) = ^{
        NSLog(@"%d",val);
      };
            
     val = 10;
     blk();
            
    复制代码

    转换为C++代码如下:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int val;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int val = __cself->val; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,val);
            }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    int main(int argc, char * argv[]) {
        
        int val = 0;
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
    
        val = 10;
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    }
    复制代码

    这与上一节转换的代码稍有差异,自动变量被作为了成员变量追加到了__main_block_impl_0结构体中。在__main_block_impl_0结构体中声明的成员变量类型与自动变量类型完全相同(block语法表达式中,没有使用的自动变量不会被追加,也就是如果变量没有在block内被使用,是不会被捕获到的)。

    另外__main_block_impl_0结构体的构造函数与上一篇也有差异:

    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    复制代码

    在初始化__main_block_impl_0结构体实例时,自动变量val被以参数的形式传递到了结构体里面,就是在我们定义block的时候,捕获的自动变量会被用来初始化这个结构体:

    void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, val));
    复制代码

    实际上带有这种自动变量的block会像下面这样初始化:

    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = 0;
    impl.FuncPtr = __main_block_func_0;
    Desc = &__main_block_desc_0_DATA;
    val = 0;
    复制代码

    由此可以看到,在__main_block_impl_0结构体被初始化的时候,变量val的值被捕获到了并且赋值给了__main_block_impl_0结构体里面的_val成员变量,其实是值的捕获,并非内存地址,所以我们在外部无法修改。

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int val = __cself->val; // bound by copy
    
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_057be8_mi_0,val);
            }
    复制代码

    __cself->val__cself上一篇已经讲过了它指向的就是这个block对象__cself->val就是访问的__main_block_impl_0的成员变量_val,而自动变量的值又赋给了_val,所以我们在外部改变自动变量的值在block内部是不会生效的。

    3.__block的实质

    我们如果想要修改block截获的自动变量的值,静态全局变量,全局变量和静态变量,block内是不会捕获到他们的的,所以这类变量在block内部,是可以进行改写值的。那么他们具体在代码层面上是怎么做的还是通过上面的命令看一下源码:

    int global_var = 1;
    static int static_global_var = 2;
    
    int main(int argc, char * argv[]) {
        static int static_var = 3;
        void (^blk)(void) = ^{
            global_var *= 1;
            static_global_var *= 2;
            static_var *= 3;
        };
        
        blk();
        
        return 0;
    }
    复制代码

    经过转换后的源码:

    int global_var = 1;
    static int static_global_var = 2;
    
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      int *static_var;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_var, int flags=0) : static_var(_static_var) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *static_var = __cself->static_var; // bound by copy
    
            global_var *= 1;
            static_global_var *= 2;
            (*static_var) *= 3;
        }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
    
    int main(int argc, char * argv[]) {
        static int static_var = 3;
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_var));
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    复制代码

    对静态全局变量static_global_var和全局变量global_var的访问与转换前完全相同, 那么静态局部变量是如何转换的呢:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      int *static_var = __cself->static_var; // bound by copy
    
            global_var *= 1;
            static_global_var *= 2;
            (*static_var) *= 3;
        }
    复制代码

    通过int *static_var = __cself->static_var能够看出,实际上是将静态局部变量static_var的指针传递给了__main_block_impl_0结构体,这也是超出变量作用域去使用变量的一种方法,那就是通过指针去访问。那为什么在局部变量不这么使用呢,这个后面再说,我们现在只需要知道static修饰的局部变量是可以在block内部进行值的改变的。回归主题,那么自动变量我们是怎么去修改它的值的,就是通过__block进行修饰,看下代码:

    int main(int argc, char * argv[]) {
        __block int var = 10;
        void (^blk)(void) = ^{
            var = 1;
        };
        
        blk();
        
        return 0;
    }
    
    复制代码

    转换后如下:

    struct __Block_byref_var_0 {
      void *__isa;
    __Block_byref_var_0 *__forwarding;
     int __flags;
     int __size;
     int var;
    };
    
    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      __Block_byref_var_0 *var; // by ref
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_var_0 *_var, int flags=0) : var(_var->__forwarding) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_var_0 *var = __cself->var; // bound by ref
    
            (var->__forwarding->var) = 1;
        }
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->var, (void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->var, 8/*BLOCK_FIELD_IS_BYREF*/);}
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    int main(int argc, char * argv[]) {
        __attribute__((__blocks__(byref))) __Block_byref_var_0 var = {(void*)0,(__Block_byref_var_0 *)&var, 0, sizeof(__Block_byref_var_0), 10};
        void (*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_var_0 *)&var, 570425344));
    
        ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
    
        return 0;
    }
    复制代码

    不难发现多了一个__Block_byref_var_0结构体实例,它也正是__block的实现。该结构体中的成员变量var就相当于block外面的自动变量的成员变量。然后我们再看一下block是怎么给这个成员变量进行赋值操作的:

    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
      __Block_byref_var_0 *var = __cself->var; // bound by ref
    
            (var->__forwarding->var) = 1;
        }
    复制代码

    刚刚在block中给静态变量赋值的时候,使用了指向该静态变量的指针,但是用__block修饰的时候,实际上能够看到__Block_byref_var_0结构体中也就是__block有一个成员变量__Block_byref_var_0 *__forwarding,是一个指向该实例自身的指针,通过成员变量__forwarding就能访问到它自身的var,那么究竟为什么要通过这个指向自身的__forwarding来访问成员变量var下一节会说,我们先知道它就是使用这种方式来访问这个自动变量的。实际上我们为什么能访问到这个成员变量var,是因为在给自动变量定义为__block类型的时候,就会初始化一个__Block_byref_var_0类型的结构体,并且默认将该变量初始化为10(因为我们给var初始化的10),相当于持有了原自动变量的成员变量。然后在初始化__main_block_impl_0结构体的时候就将这个block结构体作为参数传递了过去,这样__cself->var实际上就是我们刚才说的初始化的block的结构体,var->__forwarding->var就是访问了这个block的结构体的__forwarding成员变量,__forwarding成员变量指向的又是自身,所以__forwarding->var返回的就是自身的成员变量var,这样整个流程就走通了,具体为什么要有个_forwarding我们继续往下看。

    4.Block存储域

    通过上面的分析,现在出现了几个问题需要解释:

    1.为什么要有个_forwarding?(后面说)

    2.BLock作用域在栈上,超出变量作用域是不是就销毁了?

    上面分析的block和__block都是结构体类型的自动变量,在栈上生成,称为“栈块”,实际上还存在两种block,“堆块”“全局块”。全局块与全局变量一样,设置在程序的.data数据区域,堆块顾名思义,分配在堆上,类型分别如下:

    • _NSConcreteGlobalBlock
    • _NSConcreteStackBlock
    • _NSConcreteMallocBlock

    有两种情况,是默认会分配在数据区域上的:

    • 1.记述全局变量的地方有block语法时。
    • 2.block语法的表达式中不使用截获的自动变量的值。

    除此之外的Block语法生成的Block为设置在栈上的_NSConcreteStackBlock类对象。配置在全局变量上的Block从变量作用域外也可以通过指针安全的使用,但设置在栈上的Block,如果所属的变量作用域结束,该Block就被废弃。由于__block也配置在栈上,同样的__block变量也会被废弃。Blocks提供了将block和__block变量从栈上复制到堆上的方法来解决这个问题。这样即使block语法记述的变量作用域结束,堆上的block还可以继续存在(原文解释)。大概意思就是有些情况下,编译器会默认对栈block生成一个copy到堆上的操作。大多数情况下,编译器会适当的进行判断是否会将栈块拷贝到堆上,有一种情况除外:

    • 向方法或函数的参数中传递Block。

    就是说block作为参数传递的时候是需要我们手动执行copy的,编译器不会自动执行copy。尽管这样,还是有两种情况是不需要我们手动实现,因为他们函数内部已经实现了copy操作:

    • Cocoa框架的方法切方法中含有usingBlock。
    • GCD的API。

    举个例子,把书上面的例子自己手动实现了一下:

    - (void)viewDidLoad {
        [super viewDidLoad];
        // Do any additional setup after loading the view, typically from a nib.
        id object = [self getBlockArray];
        typedef void(^blk_t)(void);
        blk_t blk = (blk_t)[object objectAtIndex:0];
        blk();
    }
    
    - (id)getBlockArray {
        int var = 10;
        return [[NSArray alloc] initWithObjects:
                ^{NSLog(@"blk0:%d",var);},
                ^{NSLog(@"blk1:%d",var);}, nil];
    }
    复制代码

    在执行blk()的时候程序异常崩溃了。因为getBlockArray函数执行结束的时候,在栈上创建的block被废弃了,这个时候编译器并没有自动执行copy操作,需要我们手动实现。为什么编译器不对所有的栈块都执行copy到堆上,书上明确说明了:block从栈复制到堆上是相当消耗CPU的,将block设置在栈上也能够使用时,将block复制到堆上只是在浪费CPU资源。所以这种情况下对block执行copy就可以了:

    - (id)getBlockArray {
        int var = 10;
        return [[NSArray alloc] initWithObjects:
                [^{NSLog(@"blk0:%d",var);} copy],
                [^{NSLog(@"blk1:%d",var);} copy], nil];
    }
    复制代码
    2018-12-24 12:33:33.526163+0800 Blocks-捕获变量的值[54592:3223484] blk0:10
    复制代码

    文中还提到了如果多次调用copy会不会有问题,答案当然是没有问题,在ARC下是不用担心多次copy引起内存问题。

    还有一个_forwarding的问题没有说,至少现在已经知道我们设置在栈上的block因为执行了copy操作到了堆上,所以我们无需关心它会超出作用域而被释放的问题了,那么_forwarding继续往下看。

    5.__block变量存储域

    如果在Block中使用了__block变量,那么该Block从栈复制到堆时,所使用的__block也会被复制到堆上,并且会被Block持有。每一个使用了当前__block变量的Block被复制到堆上时都会对这个__block引用计数+1,如果配置在堆上的Block被废弃,相应的它所使用的__block引用计数会-1,直到所有的Block被释放那么此__block也会随之释放。

    也就是说,除了上面说到的两种情况,我们其余的Block基本都会复制到堆上,也就是说我们使用的__block也会相应的跟着复制到堆上,像OC对象一样,拥有引用计数。那么我们再分析一下之前遗留的问题,_forwarding是干嘛的,当__block被复制到堆上的时候,栈上面的__block结构体里面的_forwarding成员变量就会指向堆里面的__block结构体实例,此时堆上面的__block变量的_forwarding会指向自己本身。也就如下图这个样子:

    回顾一下上面__block的实质举过的例子,我们在用__block修饰自动变量的时候,在func函数里面修改此变量值的时候,通过(var->__forwarding->var) = 1;这种方式去改变的,var->__forwarding实际上访问的是堆上的__block结构体,var->__forwarding->var就是堆里面结构体的var成员变量。这样就算是栈上面的__block被释放了,我们还可以去访问堆里面的var,这也是为什么自动变量不像static静态变量那样通过指针去访问了,因为自动变量在作用域结束之后就会被释放了,拷贝到堆上,作用域结束堆上面还会有其相应拷贝,这份拷贝只有在使用了它的Block释放之后才会释放。

    6.截获对象

    前面分析了__block修饰的自动变量超出作用域也能使用的原理,实际上对于对象类型,Block对其捕获之后在处理上和__block很像,那么具体使用__block对变量捕获之后当Block和__block被拷贝到堆上和他们被释放这两个过程具体做了什么之前也没有详细讲到,通过捕获对象的学习,也可以对前面做个总结和思考。直接看下书上面的示例代码:

    typedef void(^blk_t)(id);
        blk_t blk;
        
        {
            id array = [[NSMutableArray alloc] init];
            blk = [^(id obj){
                [array addObject:obj];
                NSLog(@"array count : %ld",[array count]);
            } copy];
        }
        
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
    复制代码
    2018-12-25 12:25:06.678625+0800 Blocks-捕获变量的值[56349:3341197] array count : 1
    2018-12-25 12:25:06.679199+0800 Blocks-捕获变量的值[56349:3341197] array count : 2
    2018-12-25 12:25:06.679210+0800 Blocks-捕获变量的值[56349:3341197] array count : 3
    复制代码

    按理来说array在超出变量作用域的时候会被废弃,但是根据打印结果来看一切正常。也就是说array在超出变量作用域后依然存在。通过转换的源码如下:

    Block结构体部分:

    struct __main_block_impl_0 {
      struct __block_impl impl;
      struct __main_block_desc_0* Desc;
      id array;
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, id _array, int flags=0) : array(_array) {
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself, id obj) {
      id array = __cself->array; // bound by copy
    
                ((void (*)(id, SEL, ObjectType _Nonnull))(void *)objc_msgSend)((id)array, sel_registerName("addObject:"), (id)obj);
                NSLog((NSString *)&__NSConstantStringImpl__var_folders_p6_239crx8x16s8vby9bfclq5d40000gn_T_main_1dc794_mi_0,((NSUInteger (*)(id, SEL))(void *)objc_msgSend)((id)array, sel_registerName("count")));
            }
            
    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    
    static struct __main_block_desc_0 {
      size_t reserved;
      size_t Block_size;
      void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);
      void (*dispose)(struct __main_block_impl_0*);
    } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};
    复制代码

    使用Block部分:

    typedef void(*blk_t)(id);
        blk_t blk;
    
        {
            id array = ((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)((NSMutableArray *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("alloc")), sel_registerName("init"));
            blk = (blk_t)((id (*)(id, SEL))(void *)objc_msgSend)((id)((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344)), sel_registerName("copy"));
        }
    
        ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
        ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
        ((void (*)(__block_impl *, id))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init")));
    复制代码

    这一块我自己测试了一下暂时有点疑问。因为按照书上的示例来看,array前是有__strong修饰的,但是从我转换的源码来看并未看到__strong修饰符。是否是说如果没有使用weak修饰默认为strong?我先默认这个id array是被__strong修饰的。文中讲到,C语言结构体不能附有__strong修饰符的变量,因为编译器不知道应何时进行C语言结构体的初始化和废弃操作,不能很好的管理内存。但是它能很好的把握Block从栈复制到堆和把Block从堆上废弃的时机,因此就算Block结构体中含有OC修饰符的变量也一样能够跟随者Block的废弃而废弃。

    因为Block结构体中含有__strong修饰符的对象,所以需要对它进行管理,和之前的Block源码对比,在struct __main_block_desc_0结构体中多了两个函数指针:

    • void (copy)(struct __main_block_impl_0, struct __main_block_impl_0*);
    • void (dispose)(struct __main_block_impl_0);

    这其实在分析__block原理的时候就有了,实际上他们用处是一样的,都是用来管理Block内存用的。

    static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {
        _Block_object_assign((void*)&dst->array, (void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    复制代码

    _Block_object_assign函数相当于调用了retain函数,将对象赋值在对象类型的结构体成员变量中。

    static void __main_block_dispose_0(struct __main_block_impl_0*src) {
        _Block_object_dispose((void*)src->array, 3/*BLOCK_FIELD_IS_OBJECT*/);
    }
    复制代码

    _Block_object_dispose函数相当于调用了release实例方法的函数,释放赋值在对象类型的结构体成员变量中的对象。但是从转换的源码来看,__main_block_copy_0__main_block_dispose_0函数指针都没有被调用,那么它们是在什么时候触发的?

    • 栈上的Block复制到堆时 --> 触发copy函数。
    • 堆上的Block被废弃时 -->触发dispose函数。

    Block被废弃上面说了就是没有对象强引用它就会被回收了,就会调用dispose方法。那么什么时候栈上的Block会复制到堆上呢?

    • 1.调用Block的copy实例方法。
    • 2.Block作为函数返回值返回时。
    • 3.将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时。
    • 4.在方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时。

    这样通过__strong修饰符修饰的自动变量,就能够在作用域外使用了。

    在前面使用__block的时候实际上这两个函数就已经用到了,略微有点不同之处:

    • 截获对象时 --> BLOCK_FIELD_IS_OBJECT
    • __block变量时 --> BLOCK_FIELD_IS_BYREF

    通过这两个参数用来区分是Block捕获的是对象类型还是__block变量。除此之外他们在copy和dispose时都是一样的,都是被Block持有和释放。

    7.__block变量和对象

    对于Block截获__block修饰的变量还是直接截获对象的处理过程,上面都已经分析完了,包括它们关于内存的处理也都清晰了,唯独使用__block修饰id类型的自动变量还没有说,实际上__block说明符可以指定任何类型的自动变量,当然包括对象类型。还是按照书上面的例子看下代码:

    __block id obj = [[NSObject alloc] init];
    复制代码

    等同与

    __block id __strong obj = [[NSObject alloc] init];
    复制代码

    通过clang转换如下:

    /* __block结构体部分*/
    struct __Block_byref_obj_0 {
      void *__isa;
    __Block_byref_obj_0 *__forwarding;
     int __flags;
     int __size;
     void (*__Block_byref_id_object_copy)(void*, void*);
     void (*__Block_byref_id_object_dispose)(void*);
     id obj;
    };
    static void __Block_byref_id_object_copy_131(void *dst, void *src) {
     _Block_object_assign((char*)dst + 40, *(void * *) ((char*)src + 40), 131);
    }
    static void __Block_byref_id_object_dispose_131(void *src) {
     _Block_object_dispose(*(void * *) ((char*)src + 40), 131);
    }
    复制代码
    /*__block变量声明部分*/
    __attribute__((__blocks__(byref))) __Block_byref_obj_0 obj = {
            (void*)0,(__Block_byref_obj_0 *)&obj,
            33554432,
            sizeof(__Block_byref_obj_0),
            __Block_byref_id_object_copy_131,
            __Block_byref_id_object_dispose_131,
            ((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)((NSObject *(*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSObject"), sel_registerName("alloc")), sel_registerName("init"))
        };
    复制代码

    这里出现了上一节讲到的_Block_object_assign_Block_object_dispose函数。实际上编译器默认这个obj为__strong类型,当Block从栈复制到堆上时,使用_Block_object_assign函数持有Block截获的对象,当堆上的Block被废弃时,使用_Block_object_dispose函数释放Block截获的对象。这说明使用__block修饰的__strong类型的对象,当__block变量从栈复制到堆上并且在堆上继续存在,那么该对象就会继续处于被持有状态。这与Block中使用赋值给附有__strong修饰符的对象类型自动变量的对象相同。

    那么除了__strong,如果用__weak修饰呢?

    __weak修饰的对象,就算是使用了__block修饰,一样还是会被释放掉,实际上书上的源代码也是给了我们这样一个结论,只不过对象会自动置为nil。而使用__unsafe_unretained修饰时,注意野指针问题。

    8.Block循环引用

    避免循环引用,根据Block的用途可以选择使用__block变量,__weak修饰符和__unsafe_unretained修饰符来避免循环引用。

    __weak和__unsafe_unretained修饰弱引用,不用考虑释放问题。

    __block修饰,属于强引用,需要在Block内对修饰的对象置nil,为避免循环引用必须执行Block,但是__block变量可以控制对象的持有时间。

    展开全文
  • Code::Blocks手册 使用篇

    千次阅读 2015-06-01 16:09:37
    ...  译者言:工欲善其事,必先利其器。一个好的工具能事半功倍。写程序时,特别是写C++程序,大部分人脑子里想到的第一个工具就是VisualStudio。不可否认,VS很好很强大,用户体验非常好。但VisualSt
    原手册下载:http://www.codeblocks.org/docs/manual_en.pdf
    

       译者:JGood(http://blog.csdn.net/Jgood )

       译者言:工欲善其事,必先利其器。一个好的工具能事半功倍。写程序时,特别是写C++程序,大部分人脑子里想到的第一个工具就是VisualStudio。不可否认,VS很好很强大,用户体验非常好。但VisualStudio也是有缺点的:它非常庞大;只支持VC,不支持其他的编译器;VS只能在windows下跑,在其他os上就无用武之地;VS是要钱的,而且费用不非(Express版本免费)。Code::Blocks是一个非常优秀的工具,如果您正在寻找VisualStudio之外的,开源、免费、轻便、支持多种编译器、跨平台的C/C++IDE,那么Code::Blocks就是一个很好的选择。

       说明:笔者打算用两篇日志来完成对Code::Blocks手册前二章的编译,分别是:使用篇、插件篇。本文是第一篇:Code::Blocks使用篇。原手册第三章介绍Code::Blocks变量、脚本的使用,第四章介绍如何从源码编译Code::Blocks,这两章内容不是很多,笔者认为对大部分用户帮助不是不大,暂不打算翻译。笔者使用的Code::Block版本是nightlybuilds,svn6088(可以在这个地址下载:http://forums.codeblocks.org/index.php/topic,11875.0.html)。使用的编译器是GCC3.4.5。每个版本之间的使用可能会有细微的差别。因为水平有限,难免出错,欢迎指正!

    Code::Blocks:免费、开源、跨平台、功能强大的C/C++ IDE。

    Code::Blocks 手册 Version 1.0

    感谢CodeBlocks项目组:

    Anders F. Bjorklund (afb), Biplab Kumar Modak (biplab),Bartomiej wiecki (byo), PaulA. Jimenez (ceniza), Koa Chong Gee(cyberkoa), Daniel Orb (daniel2000), Lieven de Cock(killerbot),Yiannis Mandravellos (mandrav), Mispunt (mispunt), Martin Halle(morten-macy), Jens Lody (jens), Jerome Antoine (dje), Damien Moore(dmoore), Pecan Heber(pecan), Ricardo Garcia (rickg22), Thomas Denk(thomasdenk), tiwag (tiwag)

    Permission is granted to copy, distribute and/or modify thisdocument under the terms of the GNU Free Documentation License,Version 1.2 or any later version published by the Free SoftwareFoundation.

    1 CodeBlocks项目管理

       下图是CodeBlocks运行时的用户界面:

    CodeBlocks运行界面

    •    管理(Management):管理窗口包含Project视图与Symbols视图。Project视图显示当前CodeBlocks打开的所有项目(译者注:类似与VS解决方案资源管理器);Symbols视图显示项目中的标识符:类,函数、变量等信息(译者注:类似与VS的类视图)。
    •    代码编辑器:支持代码折叠,关键字高亮显示。上图main.cpp正在被编辑。
    •    打开文件列表:显示当前在代码编辑器中打开的所有文件列表。上图中打开的文件列表为:main.cpp, person.cpp,person.hpp
    •    代码段(CodeSnippets):管理常用的代码段、常用文件链接(links to files)与URL。可以通过菜单View->CodeSnippets 来显示该面板。
    •    日志和其他:这个窗口用于输出日志信息,显示查询结果等等。
    •    状态栏提供了以下这些信息:
    •        编辑器中打开文件的绝对路径;
    •        文件的编码类型;
    •        光标所在的行与列;
    •        当前的键盘模式(insert 或者 overwrite);
    •        当前的文件状态。被修改过的(但尚未保存)文件将被标记为“modified”,否则这里为空;
    •        文件操作的权限。如果文件是只读的,这里将会显示“Read only”,在 Open fileslist中,该文件会使用一个加琐的图标来显示;
    •        个性化配置名称;

       CodeBlocks提示了非常灵活和强大的项目管理功能。下文将介绍项目管理的一些常用功能。

    1.1 项目视图(Project View)

    管理窗口

       在CodeBlocks中,Project的源文件(如C/C++的源文件及其对应的头文件)和编译属性设置信息都保存在<name>.cbp文件里。可以通过菜单[File–>Project]启动工程创建向导来创建新的Project,然后通过管理窗口的上下文菜单[Addfiles](译者注:上下文菜单,指当前窗口中选中目标项目,然后右键显示的菜单)向Project中添加文件。CodeBlocks会自动根据文件的后缀将它们放入不同的类别文件夹中(译者注:这个文件夹是虚拟的,实际并不存在)。下面是默认的分类:

    •    Sources:包含源文件,后缀为*.c、*.cpp;
    •     ASMSources:包括汇编源文件,后缀一般为*.s、*.S、*.ss、*.asm;
    •    Headers:包括头文件,后缀一般为*.h、 *.hpp;
    •    Resources:包括资源文件,后缀一般为*.res、*.xrc;

       通过管理窗口的上下文菜单[Project tree –>Edit file types&categories],用户可以新建自定义的文件类别,并为其相应的后缀。例如,如果你希望*.ld的文件放到Linkerscript的分类里,你只需新建类别Linkerscript,并为它指定*.ld后缀即可。

       提示: 如果你取消选中管理窗口上下文菜单的[Projecttree–>Categorize by filetypes]选项,所有的项目文件将会按它们在文件系统中的位置来显示。

    1.2 项目备注(Notes for Projects)

       可以给CodeBlocks项目添加一些备注,用于对项目进行概要的描述与说明,这些信息有助于其他成员迅速的了解项目。备注信息被保存在项目工程文件里,并可以设置为随着项目的启动而显示。如图:

    设置项目备注信息

    1.3 项目模板

    项目模板

       CodeBlocks支持许多不同类型的项目模板,它们会在新建项目的时候显示,创建新项目的时候往往从这些模板中选择(如上图:)。用户可以自定义工程模板。工程模板保存编译器的类型、编译选项、资源的配置等相关信息。项目模板保存在DocumentsandSettingsn\<user>\ApplicationData\codeblocks\UserTemplates目录中。如果你希望该工程模板被本机的所有用户使用,必须把对应的模板文件拷贝到CodeBlocks的安装目录。模板在CodeBlocks重启之后生效,通过菜单[New->Project->Usertemplates]就可以看到自定义的模板。

       提示: 用户可以通过右键选中工程向导中的模板对其进行编辑。如下图:

    编辑模板

    1.4 从编译模式创建项目(Create Projects from Build Targets)

       一个项目往往有不同的编译模式,最常见的是Release和Debug。不同的编译模式表示用于构建当前项目所使用的不同编译器选项、调试信息或者依赖的文件。每一种编译模式都可以被保存成独立的工程文件。通过上下文菜单[Project->Properties],在BuildTargets标签中点击“Create project from target”按钮来生成对应编译模式的工程文件,如图:

    编译模式

    1.5 编译模式(Virtual Targets)

       一个项目可以有多种不同的编译模式,最常用的编译模式为“Debug”和“Release”。“Debug”模式下会包含许多Debug信息,而“Release”模式下没有这些信息。也可以通过上下文菜单[Project->Properties->BuildTargets]添加其他的编译模式。编译模式将显示在工具栏中:

    编译模式  

    1.6 预生成和生成后步骤 (Pre- and Postbuild setps)

       Code::blocks允许在项目生成前和生成后执行额外的操作,这些操作分别被称为预生成(Prebuilt)或生成后(Postbuilt)步骤。下面是典型的生成后步骤:(译者注:笔者对低层的东西知道的不多,不是很清楚下面这些步骤的意思。)

           Creating an Intel Hexformat from a nished object
           Manipulating objects by objcopy
           Generating dump les by objdump

    1.7 为构建目标添加脚本(Adding Scripts in Build Targets)

       Code::Blocks允许使用脚本来调用菜单命令,控制项目的生成。

       提示: 脚本可以被包含到一个构建目标(Build Target)中。

    1.8 解决方案(Workspace)与项目依赖

       CodeBlocks可以同时打开多个项目,通过菜单[File->Saveworkspace]把它们集成到一个解决方案中,并生成一个对应的解决方案文件(<name>.workspace)。下次打开解析方案文件(<name>.workspace)时,这些项目会被一起加载进来。

       复杂的软件系统通常由不同的模块、组件以独立的工程项目组成,它们之间往往存在依赖关系。

       例如:项目A以库(library)的形式提供一些公用函数给其他项目调用,如果项目A中的源代码被修改,这个库就得重新编译。项目B使用项目A中实现的函数,那么项目B就依赖项目A。CodeBlocks把这些项目依赖的信息保存到解决方案文件中,所以解决方案中的工程可以各自独立的创建、编译而不相互影响(译者注:由解决方案文件来维护各项目的依赖关系)。这些依赖关系会决定项目的生成顺序。通过菜单[Project->Properties],然后选择[Project’sdependencies]按钮来设置项目之间的依赖关系。如下图:

    项目依赖

    1.9 包含汇编文件

        略。

    1.10 代码编辑器与工具 (Editor and Tools)

    1.10.1 默认代码 (DefaultCode)   

       公司的编码规范往往要求源文件有统一的布局(译者注:例如源文件的开始处以统一的格式给出文件创建的时间、作者、实现的功能描述等相关信息)。CodeBlocks允许预定义一些内容,当新建C/C++文件时这些内容会自动添加到文件的开始处。这里把预定义的内容称为defaultcode。可以通过菜单[Settings–>Editor–>DefaultCode]来设置defaulgcode,通过菜单[File->New->File]创建的代码文件将自动添加上defaultcode。例如:

     

    1.10.2 缩写(Abbreviation)

       定义一个常用的代码片断[typing],并给它提供一个名字[Abbreviation],在写程序的时候,只要给出这个名字,然后按快捷键Ctrl+J,CodeBlocks就会用预先定义的代码片断来替换该名字。(译者注:VS中也有类似的功能,在C#程序时,只要写出关键字for,然后连续按两次Tab键,编辑器会自动生成for语句的框架,是不是很方便?~_~)。通过菜单[Settings->Editor]来设置Abbreviation,如下图:

    代码缩写

       abbreviation也支持参数变量(Parametrisation)(如:$NOW表示当前时间)。如:

    缩写--> 缩写2

    1.10.3 个性化(Personalities)

       CodeBlocks的配置信息被作为应用程序数据而保存在codeblocks文件夹的<user>.conf文件中。该配置文件中保存的信息包括:上次打开的项目、代码编辑器的设置、符号栏显示等等。默认的个性化设置被保存在default.conf文件中。通过命令行参数-personality=myuser来调用CodeBlocks,配置信息将被保存到myuser.conf中。如果该文件不存在,系统将自动创建它。如果以命令行的方式来启动CodeBlocks,并传递命令参数--personality=ask,将会显列出当前所有的修改化配置列表,用户选择其一启动CodeBlocks。如下图:

    个性化启动

    1.10.4 配置文件(Configuration Files)

       CodeBlocks的配置信息保存在codeblocks目录下的default.conf文件中。当使用个性化设置的时候,配置信息将被保存到<personality>.conf文件里。

       cb_share_conf(一个辅助工具,可以在CodeBlocks安装目录里找到)被用来管理与保存这些设置信息。

       如果你想为电脑的不同账户定义一个公用的默认设置,defual.conf文件应该被保存到\Documents andSettings\Default User\ApplicationData\codeblocks。CodeBlocks在第一次启动的时候,会将该配置文件拷贝到当前账户的applicationdata目录下(并作为当前帐户的配置文件)。

       如果想在usb设备上创建一个绿色版本号的CodeBlocks,请执行下面步骤:将CodeBlocks安装目录拷贝到usb设备上,将配置文件default.conf拷贝到该目录中,该配置文件将被用于全局的设置。确保系统有权限对该文件进行写入,否则CodeBlocks对配置文件的修改将不会被保存。

    1.10.5 导航与搜索(Navigate and Search)

       CodeBlocks提供了很多方法用于文件和函数之间导航。书签就是最常用的一种方式。通过快捷键Ctrl +B在源文件中设置/删除一个书签,通过Alt + PgUp或Alt + PgDn在不同的书签之间跳转。

       在管理窗口的Project视图中选中解决方案或项目,右键点击在弹出菜单中选择[Findfile],输入你查找的文件名称,然后回车,该文件将被选中(如果文件存在的话),再按回车,编辑器就会打开该文件。如下图:

    查找文件

       在CodeBlocks中,你可以很容易的在头文件与源文件之间导航:

           1. 将光标置于头文件被包含处(例如:#include “header.hpp”),右键选择性“open includefile”,编辑器将打开该包含文件。(译者注:可以在VS中使用快捷键 Ctrl + Shift + G实现同样的操作。这个功能非常方便,特别是要查看源代码时。)

    open_include

           2. 通过右键菜单的[Swap header/source],在头文件与源文件之间切换。

           3. 选中一个定义(如变量名,类型名等等),在右键菜单中选择[Finddeclaration],编辑器就会打开该定义的声明。(译者注:可以使用右键菜单的[Findimplementation]定位到函数的实现处,在VS中使用快捷键F12实现同样的操作。这也是一个非常方便的功能。)。

    转到定义处

        CodeBlocks提供多种方式用于对单个文件或整个目录进行搜索。通过菜单[Search->Find]或[Search–> Find in Files]来打开搜索对话框。

        Alt + G 和Ctrl + Alt + G这两个快捷键用于打开 文件/函数跳转对话框,只要输入文件/函数的名称,就可以很方便的跳转到指定文件或函数。文件名/函数名搜索还支持 * 和 ?等能配符。(译者注:可以使用Visual Assist插件在VS中实现类似的功能。这两个功能真的很方便实用。)

    select_func  select_func

       提示: 使用Ctrl + PgUp 和 Ctrl + PgDn可以在前后函数之间跳转。

       在文本编辑器中,使用快捷键Ctrl + Tag可以在当前所有打开的文件之间跳转。(译者注:VS也有类似的功能,而且快捷键也是Ctrl+ Tag,这是巧合吗?)

    在打开文章之间跳转

     

           显示行号。通过菜单[Settings->General Settings],选中[Show linenumbers]来显示行号。使用快捷键 Ctrl + G 可以快速的跳转到指定行。

    1.10.6 符号视图(Symbol view)

       CodeBlocks管理窗口提供的符号视图,以树的形式显示(导航)C/C++源文件中的类、函数、变量定义。可以选择符号显示的区域是:当前文件、当前项目、整个解决方案。(译者注:与VS的类视图面板实现类似的功能。)

    符号视图

     

     

       提示: 在搜索输入框中输入符号的名称,符号浏览器将会过滤不符条件的符号。

       符号视图将符号分为如下分类:

    •        Global functions: 全局函数;
    •        Global typedefs: 通过typedef定义的别名;
    •        Global variables:全局变量;
    •        Preprocessor symbols: 通过#define宏定义的预处理指示符;

       结构和类的定义显示在pre-processorsymbols下面。如果一个分类被选中,属于该分类的标识符将显示在视图的下半部分。双击这些标识符,代码编辑器将定位到定义这些标识符的文件所在处。

    1.10.7 集成外部的帮助文档

       CodeBlocks支持添加外部的帮助文档集成到开发环境中。通过菜单[Settings->Environment]来设置。把你选择的chm格式的文档以添加到HelpFiles,并将其作为默认的帮助文档,在编辑器中选择一个函数,对应的文档就会出现通过快捷键F1。…

    1.10.8 集成外部工具

       通过菜单[Tools->ConfigurationTools–>Add],把外部工具集成到CodeBlocks开发环境中。这些外部的工具允许以参数的形式访问CodeBlocks的内建(Built-in)变量(如当前项目所在的文件夹${PROJECT_DIR})。利用这个功能,我们给CodeBlocks添加一个菜单项:打开当前项目所在的文件夹。请看图:

    集成外部工具

    (译者注:这是一个非常实用的功能。利用这个功能,我在我的CodeBlocks中,添加了三个我最常用的菜单项:使用Notepad++打开当前文件;打开当前项目所在的文件夹;以Dos窗口打开当前文件夹。爽歪歪~~)

    external_tool

    1.11 使用CodeBlocks的小提示(Tips)

       这节我们将展示一些CodeBlocks非常有用的提示。

    1.11.1 修改跟踪(Tracking of Modifications)

       CodeBlocks通过行号旁边的小坚条颜色来跟踪源文件的修改。未保存的修改行将被标记为黄色,而已保存的修改行标记为绿色。你可以通过菜单[Search->Gotonext changed line]或者[Search->Goto previous changedline]在修改内容之间导航(对应的快捷键是 Ctrl + F3 和 Ctrl + Shift +F3)。(译者注:VS也提供类型的功能。)

    跟踪文本修改

    trace_modify

       可以通过菜单[Settings->Editor->Margins andcaret],取消选中[Use Changebar]来取消该功能。

       提示:如果文件被关闭,记录在该文件上的undo/redo信息和修改标识(changebars)将会清空。如果文件处理打开状态,可以通过菜单[Edit->Clearchanges history]或者右键菜单相应选项来显式地清空这些信息。

    1.11.2 与其他应用程序交互

       CodeBlocks能够在运行时与其他应用程序进行交互。windows下通过DDE(Dynamic DataExchange)实现这种进程间的交互,而在其他操作系统下,基于TCP来实现交互。

       以下语法的命令可以发送给CodeBlocks运行实例:

           [<command>(“<parameter>”)

       当前可以使用的命令:

           Open:命令[Open(“D:\Temp\test.txt”)],在CodeBlocks实例中(或者启动新的CodeBlocks进程,如果需要的话)打开一个文件。

           OpenLine:命令[OpenLine(“D:\Temp\test.txt:10”)],在CodeBlocks中打开文件,并定位到指定行数,冒号后面的数字用于指定行号。(译者注:不是很明白原文的意思:Thiscommand opens a le at a given line number in a CodeBlocksinstance.)

           Raise:让CodeBlocks实例获得焦点。不就给该命令提供参数。

    1.11.3 配置环境变量

       操作系统的配置信息被定义在环境变量中。例如环境变量Path包含一个编译器的安装目录路径,操作系统在运行期间都可以随时访问该环境变量。如果同一编译器的另一个版本被安装,就可能会发生错误,如:调用的编译器版本不正确。

       有时候因为项目的需要,必须在同一机器上安装同一编译器的不同版本。为了避免上述错误的发生,可以在项目启动之前修改环境变量。显然,这个方法很容易出错,很不灵活。基于这个问题,CodeBlocks提供了一个简单的解决方法:创建不同版本的、只能在CodeBlocks内使用的环境变量,然后根据项目的需要选择适当的版本环境变量。通过菜单[Settings->Environment],在EnvironmentVaribales面板中点击Create按钮创建不同版本的环境变量,如下图:

    环境变量设置  

    1.11.4 切换布局

       CodeBlocks可以根据手头任务的需要选择不同的配置/视图,并保存这些配置/视图。默认情况下这些配置保存在default.conf文件里。以命令行方式启动Cdoeblocks,并传递--personality=ask参数,就可以在列出的个性化配置列表中选择一个视图启动。有时候,你可能希望在应用程序的使用过程中切换布局,如编码模式与调试模式下可能需要不同的布局。CodeBlocks提供了一种机制来实现这种需求,通过菜单[View->Layouts->Savecurrent],并输入布局的命名,保存布局。通过[View->Layouts->Layoutname]来切换布局。(译者注:笔者使用的版本,通过[View->Perspectives]来保存\切换布局,如下图:)

    切换布局

    1.11.5 项目切换

       多个项目同时在CodeBlocks中打开时,用户希望快速的在这些项目之间切换。CodeBlocks提供一组快捷键来实现:

    •       Alt + F5:将视图中前一个项目设为激活(Active)项目。(译者注:类似于VS中将某个项目设为启动项目。)
    •        Alt + F6: 将视图中后一个项目设为激活项目。
    •        F11: 在编辑器中切换源文件(name.cpp)和头文件(name.h)。
     
    1.11.6 扩展编译器设置

       在编译一个项目的过程中,编译信息将会显示在消息窗口的BuildLog视图中。如果你想获取更详细的编译信息,通过菜单[Settings->Compiler andDebugger],在Other Settings面板中选择Compiler logging下拉列表项:

    编译选项设置

        “Fullcommand line”选项意味着将在BuildLog视图中显示所有的编译信息。可以将这些日志信息保存为HTML文件,通过选中”Save build log to HTML filewhen finished’。另外,通过选中”Display build processbar”,CodeBlocks支持在Build Log视图中显示编译进度。

    1.11.7 编辑器缩放

       CodeBlocks提供了一个非常高效的编辑器。这个编辑器允许你缩放打开文本的字体大小。如果你的鼠标有滚轮,你只要按住Ctrl键,同时滚支鼠标滚轮,就可以实现文本的缩放。

       提示: 通过菜单[Edit->Specialcommands->Zoom->Reset]来重置缩放。

    1.11.8 自动换行模式

       在CodeBlcoks中打开一个文本文件,使用自动换行模式可以将很长的行以多行的形式显示在一个屏幕内,有利于编辑。通过菜单[Settings->Editor->OtherOptions],选中Word warp来激活自动换行模式。…

    1.11.9 块选文本

       CodeBlocks支持在代码编辑器中块选文本。按住Alt键的同时按住鼠标左键在编辑器选择一块区域。如果你想选择数组的几列进行复制和粘贴,这个功能是非常有用的(译者注:VS也提示类似的功能,快捷键也一样。)。如图:

    块选文本

    1.11.10 代码折叠

       CodeBlocks支持代码折叠,允许将函数、类的实现折叠起来。

       提示:通过菜单[Settings->Editor->Folding],可以设置代码折叠的样式和层次限制(depthlimit)。

    1.11.11 自动完成

      在CodeBlocks中打开一个项目时,编译器的相关目录(include目录)与项目的源文件/头文件将被解析,提取有关类型定义、函数、宏等的信息,CodeBlocks使用这些信息来实现自动完成功能。通过菜单[Settings->Editors->Codecompletion]启用该功能,通过快捷键 Ctrl + Space 来显示提示信息(译者注:默认的快捷键 Ctrl + Space不适合中国人的键盘习惯,建议改成其他快捷键。)。通过[Settings->Editor->Syntaxhighlighting],你可以添加自定义的关键字。

    1.11.12 查找破损文件(Find broken files)

       如果一个文件已经从磁盘中删除,但它仍然被包含在项目文件(project.cbp)中(译者注:项目的文件信息保存在*.cbp文件里。),这个文件在项目面板中显示一个破损符号(如下图)。应该通过上下文菜单[Removefile from project]将它从项目中移除。

    borken_file

       一个大的工程可能包含许多子文件夹,搜索破损文件会非常花费时间。CodeBlocks提供ThreadSearch插件来解决这个问题。在ThreadSearch中输入要查找的表达式,并设置查找的范围:“Projectfiles”或者“Workspacefiles”,ThreadSearch将会分析所有包括在项目或者解决方案中的文件。当ThreadSearch找到一个跋损文件的时候,会发出一个文件丢失的错误。

    1.11.13 包含库

       在项目的编译选项中,你可以通过”Add”按键添加项目所使用的库。库可以以绝对路径的形式给出,也可以只给出名称而无需提供lib前缀和文件扩展名。

       例如:存在这样的一个库文件:<path>\libs\lib<name>.a,只要给出<name>,链接器就可以找到对应的库文件。

    1.11.14 对象链接顺序

       在编译过程中,源文件name.c/cpp将会被编译成目标文件name.o,然后链接器把独立的目标文件链接成可执行文件name.exe(对于嵌入式系统,将链接成name.elf)。这种情况下,可能需要预先定义对象链接的顺序。在CodeBlocks中,可以设置相关源文件的优先级来实现。在上下文菜单中选择[Properties],在Build标签中定义。较低优先级使文件较先链接。

    1.11.15 自动保存

       CodeBlcoks允许自动保存当前正在编辑的文件和项目,或者对它们进行备份。可以通过菜单[Settings->Environment->Autosave]来激活该功能。

    1.11.16 文件扩展名设置

       在CodeBlocks中,可以选择多次方式来处理文件不同后缀的文件,可以在菜单[Settings->Environmentsettings->Files extension handling]设置相应的处理方式:“Launchan external program”指定外部程序来打开文件;“Launch the associatedapplication”使用系统默认程序来打开文件;“Open it in Code::Blockseditor”使用Code::Blocks编辑器来打开文件。如下图:

    文件处理方式  

    1.12 通过命令行操作CodeBlocks

       CodeBlocks能够通过命令行来运行。在这种情况下,需要通过一些选项来控制项目的构建过程。因为CodeBlocks是scriptable的,所以CodeBlocks项目的构建可以集成到你自己的工作过程中。

       codeblocks.exe /na /nd --no-splash-screen --built<name>.cbp --target=’Release’

    <filename>指定CodeBlock项目文件(*.cbp)或解决方案文件(*.workspace)。

    --file=<filename>[:line]:使用CodeBlocks打开指定文件。可选的行号指示代码编辑器跳转到该行。

    /h, --help:显示帮助信息。

    /na, --no-check-associations:不执行文件关联检查。(windows only)

    /nd, --no-dde:不启动DDE服务。(windows only)

    /ni, --no-ipc:不启动IPC服务。(Linux and Mac only)

    /ns, --no-splash-screen:应用程序启动的时候,不显示启动画面。

    /d, --debug-log:显示应用程序的调试日志

    --prefix=<str>:设置共享数据文件夹的前缀

    /p, --personality=<str>,--profile=<str>:设置要使用的个性化配置。你可以使用“ask”参数来列出可选择的个性化配置。

    --rebuild:清理并重新编译工程或解决方案。

    --build:编译工程或解决方案。

    --target=<str>:设置编译模式,如:--target=’Release’

    --no-batch-window-close:编译完成的时候,不关闭日志窗口。

    --batch-build-notify:编译完成的时候显示提示信息

    --safe-mode:启动的时候,所有插件都不可用。

    > <build logfile>:重定向标准输出到日志文件。这是标准DOS/*nixshell的输出重定向,并非CodeBlocks内置选项。

    1.13 快捷键

       在IDE中使用快捷键比使用鼠标更为高效。下表给出CodeBolcks默认的快捷键。(译者注:笔者将CodeBlocks中的快捷键设置为与VS大体一致,使用CodeBlocks时非常顺手。)

    Function Shortcut Key
    Undo last action Ctrl + Z
    Redo last action Ctrl + Shift + Z
    Cut selected text Ctrl + X
    Copy selected text Ctrl + C
    Paste text from clipboard Ctrl + V
    Select all text Ctrl + A
    Swap header / source F11
    Comment highlighted code Ctrl + Shift + C
    Uncomment highlighted code Ctrl + Shift + X
    Duplicate line caret is on Ctrl + D
    Auto-complete / Abbreviations Ctrl + Space / Ctrl + J
    Show call tip Ctrl + Shift + Space
    Swap line caret is on with line above it Ctrl + T
    Toggle bookmark Ctrl + B
    Goto previous bookmark Alt + PgUp
    Goto next bookmark Alt + PgDown
    Toggle current block folding F12
    Toggle all folds Shift + F12

    CodeBlocks代码编辑器组件提供的快捷键,这些快捷键不能重新绑定(rebound)。

    Function Shortcut Key
    Magnify text size. Ctrl + Keypad "+"
    Reduce text size. Ctrl + Keypad "-"
    Restore text size to normal. Ctrl + Keypad "/"
    Cycle through recent files. Ctrl + Tab
    Indent block. Tab
    Dedent block. Shift + Tab
    Delete to start of word. Ctrl + BackSpace
    Delete to end of word. Ctrl + Delete
    Delete to start of line. Ctrl + Shift + BackSpace
    Delete to end of line. Ctrl + Shift + Delete
    Go to start of document. Ctrl + Home
    Extend selection to start of document. Ctrl + Shift + Home
    Go to start of display line. Alt + Home
    Extend selection to start of display line. Alt + Shift + Home
    Go to end of document. Ctrl + End
    Extend selection to end of document. Ctrl + Shift + End
    Go to end of display line. Alt + End
    Extend selection to end of display line. Alt + Shift + End
    Expand or contract a fold point. Ctrl + Keypad "*"
    Create or delete a bookmark. Ctrl + F2
    Go to next bookmark. F2
    Select to next bookmark. Alt + F2
    Find selection. Ctrl + F3
    Find selection backwards. Ctrl + Shift + F3
    Scroll up. Ctrl + Up
    Scroll down. Ctrl + Down
    Line cut. Ctrl + L
    Line copy. Ctrl + Shift + T
    Line delete. Ctrl + Shift + L
    Line transpose with previous. Ctrl + T
    Line duplicate. Ctrl + D
    Find matching preprocessor conditional, skipping nestedones. Ctrl + K
    Select to matching preprocessor conditional. Ctrl + Shift + K
    Find matching preprocessor conditional backwards, skippingnested ones. Ctrl + J
    Select to matching preprocessor conditional backwards. Ctrl + Shift + J
    Previous paragraph. Shift extends selection. Ctrl + [
    Next paragraph. Shift extends selection. Ctrl + ]
    Previous word. Shift extends selection. Ctrl + Left
    Next word. Shift extends selection. Ctrl + Right
    Previous word part. Shift extends selection. Ctrl + /
    Next word part. Shift extends selection. Ctrl + \

    Files

    Function Shortcut Key
    New file or project Ctrl + N
    Open existing file or project Ctrl + O
    Save current file Ctrl + S
    Save all files Ctrl + Shift + S
    Close current file Ctrl + F4 / Ctrl + W
    Close all files Ctrl + Shift + F4 / Ctrl + Shift + W

    CodeBlocks的Tab组件所提供的快捷键,这些快捷键不能重新绑定(rebound)。

    Function Shortcut Key
    Activate next open file Ctrl + Tab
    Activate previous open file Ctrl + Shift + Tab

    View

    Function Shortcut Key
    Show / hide Messages pane F2
    Show / hide Management pane Shift + F2
    Move project up (in Project tree) Ctrl + Shift + Up
    Move project down (in Project tree) Ctrl + Shift + Down
    Activate prior (in Project tree) Alt + F5
    Activate next (in Project tree) Alt + F6
    Zoom in / out Ctrl + Roll Mouse Wheel
    Focus editor CTRL + Alt + E

    Search

    Function Shortcut Key
    Find Ctrl + F
    Find next F3
    Find previous Shift + F3
    Find in files Crtl + Shift + F
    Replace Ctrl + R
    Replace in files Ctrl + Shift + R
    Goto line Ctrl + G
    Goto next changed line Ctrl + F3
    Goto previous changed line Ctrl + Shift + F3
    Goto file Alt + G
    Goto function Ctrl + Alt + G
    Goto previous function Ctrl + PgUp
    Goto next function Ctrl + PgDn
    Goto declaration Ctrl + Shift + .
    Goto implementation Ctrl + .
    Open include file Ctrl + Alt + .

    Build

    Function Shortcut Key
    Build Ctrl + F9
    Compile current file Ctrl + Shift + F9
    Run Ctrl + F10
    Build and Run F9
    Rebuild Ctrl + F11

    Debug

    Function Shortcut Key
    Debug F8
    Continue debugging Ctrl + F7
    Step over a code block F7
    Step into a code block Shift + F7
    Step out of a code block Ctrl + Shift + F7
    Toggle breakpoint F5
    Run to cursor F4
    Previous error Alt + F1
    Next error Alt + F2
    展开全文
  • IOS --- Blocks详解(一)

    2019-12-01 14:53:04
    一:什么是Blocks Blocks是C语言的扩充功能,用一句话表示他的扩充功能:带有自动变量(局部变量)的匿名函数。顾名思义,匿名函数就是不带有名称的函数。而C语言是不允许这样的函数存在的,即便是函数指针,也是...

    一:什么是Blocks

      Blocks是C语言的扩充功能,用一句话表示他的扩充功能:带有自动变量(局部变量)的匿名函数。顾名思义,匿名函数就是不带有名称的函数。而C语言是不允许这样的函数存在的,即便是函数指针,也是知道函数名的。

    int (*funcptr) (int) = &func;
    int result = (*funcptr)(10);

      通过Block,源代码中就能使用匿名函数,到这里我们知道了“带有自动变量值的匿名函数”中“匿名函数”的概念。那么带有“自动变量值”究竟是什么呢?

    //在C语言中能够使用的变量
    * 自动变量(局部变量)
    * 函数参数
    * 静态变量 (静态局部变量)
    * 静态全局变量
    * 全局变量
    
    //在函数多次调用之间能够传递值的变量有:
    * 静态变量(静态局部变量)
    * 静态全局变量
    * 全局变量

      另外,“带有自动变量值的匿名函数”这一概念并不仅指Blocks,他还应用于许多语言中。在计算机科学中,此概念也称为闭包(Closure),lambda计算等。

    二:Blocks语法

      首先先来看一个简单的块用法

    ^void (int event){
        printf("buttonID:%d event = %d\n",i,event);
    }

      如上所示,完整形式的Block语法与一般的C语言函数定义相比,仅有两点不同。

      1* 没有函数名

      2* 带有^

      第一点不同没有函数名,因为它是匿名函数。第二点不同是返回值类型前带有“^”(插入记号,caret)记号。因为OS X,IOS应用程序的源代码中将大量使用Block,所以插入该记号便于查找。

    //完整模式
    ^ 返回值类型  参数列表  表达式
    
    //省略模式
    ^ 参数列表 表达式
    
    ^ 表达式

    例如:

    ^int (int count){return count + 1;}

      表达式中的省略问题:

    1. 省略返回值类型时,如果表达式中有return语句就使用该返回值的类型,如果表达式中没有return语句就使用void类型

    2.如果不使用参数,参数列表也可以省略:

    ^void (void){printf("");}
    
    //可以省略参数
    
    ^{printf("");}

    三:Blocks 类型变量

      上面陈述到,在Block语法单从其记述方式上来看,除了没有名称以及带有“^”以外,其他的都和C语言函数定义相同。在定义C语言函数时,就可以将所定义函数的地址赋值给函数指针类型变量中。

    int func(int count)
    {
        return count + 1;
    }
    
    int (* funcptr)(int) = &func;

      同样地,在Block语法中,可将Block语法赋值给声明为Block类型的变量中,即:源代码中一旦使用Block语法就相当于生成了可赋值给Block类型变量的“值”。Blocks中由Block语法生成的值也被称为“Block”。“Block”即指源代码中的Block语法,也指由block语法所生成的值。

      声明Block类型变量的示例如下:

    int (^blk)(int);//Block变量的声明

      与前面使用函数指针的源代码对比可知,声明Block类型变量仅仅是将声明函数指针类型变量的“*”变为了"^"。该Block变量和一般的C语言变量完全相同。

    可作为以下用途使用
     * 自动变量
     * 函数参数
     * 静态变量
     * 静态全局变量
     * 全局变量

      使用Block语法将Block赋值给Block类型变量

    int (^blk)(int) = ^(int count){return count+ 1;};
    
    //并且,多个Block类型变量之间可以相互传值

      在函数参数和函数返回值中使用Block类型变量

    //函数参数
    void func(int (^blk)(int)){}
    
    //函数返回值
    int (^func()(int))
    {
        return ^(int count){return count+1;};
    }

      由此可见,在函数参数和返回值使用Block类型变量时,记述方式极为复杂。这时,我们可以像使用函数指针类型时那样,使用typedef解决该问题。

    typedef int(^blk_t)(int);

      这样我们将blk_t声明为了类型变量,和原先对比

    //原来
    void func( int (^blk)(int) )
    
    void func (blk_t)
    {}
    
    
    //原来
    int (^func()(int))
    
    blk_t func(){}
    

    Block类型变量可完全像通常的C语言变量一样使用,因此也可以使用指向Block类型变量的指针,即Block的指针类型变量

    typedef int (^blk_t)(int);
    
    blk_t blk = ^(int count){return count + 1;};
    
    
    blk_t *blkptr = &blk;
    (*blkptr)(10);

    四:截取自动变量值

    ”带有自动变量值“究竟是什么?“带有自动变量值”在Blocks中表现为“截取自动变量值”。截取自动变量值的示例如下:

    int main()
    {
        int dmy = 256;
        int val = 10;
        const char *fmt = "val = %d\n";
        void (^blk)(void) = ^{printf(fmt,val);};
    
        val = 2;
        fmt = "These values were changed.val = %d\n";
    
        blk();
        return 0;
    }
    
    //该源代码中,Block语法的表达式使用的是他之前声明的自动变量fmt 和 val。在Blocks中,Block表达式截获所使用的自动变量的值,即保存该自动变量的瞬间值,所以在执行Block语法后,即使改写了Block中使用的自动变量值也不会影响Block执行时自动变量的值,
    所以执行结果为 val = 10;

    五: __block说明符

      实际上,自动变量值截获只能保存执行Block语法瞬间的值。保存后就不能改写值。下面我们尝试在BLOCK内改写截获的自动变量值,看看会出现什么结果。

    int val = 0;
    void (^blk)(void) = ^{val = 1;};
    
    blk();
    printf("val = %d \n",val);
    
    //以上为在Block语法外声明的给自动变量赋值的源代码,该源代码会产生编译错误。

      若想在Block语法的表达式中将赋值给在Block语法外声明的自动变量,需要在该自动变量上附加__block说明符号。

    __block int val = 0;
    void (^blk)(void) = ^{val = 1;};
    
    blk();
    printf("val = %d \n",val);
    

      输出val = 1;

    六:截获的自动变量

      如果将值赋值给Block中截获的自动变量,就会产生编译错误。

      那么截获 Objective-C对象,调用变更该对象的方法也会产生编译错误吗?

    id array = [[NSMutableArray alloc] init];
    
    void (^blk)(void) = ^{
        id obj = [[NSObject alloc]init];
        [array addObject:obj];
    };
    //这样是没有问题的,而向截获的变量array赋值则会产生编译错误。
    
    //修改array的值会产生编译错误,但是使用不会产生编译错误。

    注意:在使用C语言数组时必须小心使用其指针,源代码如下:

    const char text[] = "hello";
    void (^blk)(void) = ^{printf("%c\n",text[2]);};
    
    //只是使用C语言的字符串字面量数组,而并没有向截获的自动变量赋值,因此看似没有问题,但是实际上会产生编译错误
     error:cannot refer to declaration with an array type inside block printf("%c\n",text[2]);
     note:declared here const char text[] = "hello";
    
    这是因为在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截取,这时,使用指针可以解决该问题。
    
    const char* text = "hello";
    void (^blk)(void) = ^{printf(text[2]);};

     

    展开全文
  • 一、 Blocks是C语言的 扩展功能:带有自动变量(局部变量)的匿名函数。 C语言中用到的变量:自动变量(局部变量)、静态变量、静态局部变量、全局变量、函数参数 函数多次调用之间能够传递值的变量有:静态变量...

    一、  Blocks是C语言的 扩展功能带有自动变量(局部变量)的匿名函数。

    C语言中用到的变量:自动变量(局部变量)、静态变量、静态局部变量、全局变量、函数参数

    函数多次调用之间能够传递值的变量有:静态变量(静态局部变量)、静态全局变量、全局变量。因为在整个程序中,一个变量总保持在一个内存区域,虽然多次调用但是该变量值总能保持不变,在任何时候以任何状态调用,使用的都是同样的变量值。

    二、Blocks模式

    1、Block语法:^ 返回值类型 (参数列表) {表达式}

    Block类型变量

    a  通过typedef声明blk_type类型变量

    typedef int (^blk_type)(int);

    blk_type blk_1;

    b   一般声明Block类型变量:

    int (^blk_1)(int);

    将Block赋值为Block类型变量:

    int (^blk_1)(int) = ^(int count){return count+1;};

    int (^blk_2)(int);

    blk_2=blk_1;

    Block类型变量与C语言变量一样,可以作为:自动变量、静态变量、静态局部变量、全局变量、函数参数

    (函数返回值、指向Blcok类型的变量即Block的指针类型变量)。

    2、截获自动变量值

    “带有自动变量值”在Blocks中表现为“截获自动变量值”:即保存该自动变量的瞬间值,保存后就不能改写该值

    3、__block说明符

    若想在Blocks语法表达式中将 值 赋给在Blocks语法外声明的 自动变量,需要在该自动变量上附加__block说明符。

    4、截获的自动变量(非OC对象、OC对象、数组)

    a 将值赋值给Block中截获的自动变量,会产生编译错误。

    b 截获OC对象,对OC对象赋值会产生编译错误( obj=obj ),但是使用OC对象就不会产生编译错误( [obj addObject:**] )

    c 在Blocks中截获自动变量的方法并没有实现对C语言数组的截获,可以使用指针解决该问题。

      const char text[]="hello";

    void(^blk)(void)=^{printf("%c\n",text[2]);}

    以上代码报错。修改为:

    const char *text="hello";

    void(^blk)(void)=^{printf("%c\n",text[2]);}

    三、Blocks实现

    1、Block的实质:一个Block是一个结构体实例,结构体中有isa指针,指向所属类,说明:Block是OC对象。

    注:所属类也是一个结构体实例,结构体中有isa指针,指向其父类-----一级一级直到超类

    “OC中由类生成对象”:意味着“生成由该类生成的对象的结构体实例”,生成的各个对象,即由该类生成的对象的各个结构体实例,结构体中成员变量isa指针保持该类的结构体实例指针。

    2、截获自动变量值

    “截获自动变量值”:在执行Block语法时,Block语法表达式所 使用自动变量值 被保存到Block的结构体实例(Block自身)中。

      问题:c 在Blocks中截获自动变量的方法并没有实现对C语言数组的截获

      因为C语言中不能数组赋值给数组,当自动变量为数组,保存到Block的结构体实例中出现数组赋值给数组,所以报错。

    3、__block说明符

    Block中 仅 “截获自动变量值”,重写该自动变量的值也不会改变原先截获的自动变量。

    这样造成无法在Block中保存值,解决这个问题的两种方法:

    a 使用静态变量、静态全局变量、全局变量,允许在Block中改值。

    (注: 保存到Block结构体中的是指针,使用静态变量的指针进行对其访问)

    b 使用__block说明符

      自动变量附上__block存储域说明符:__block变量同Block一样变成结构体类型的自动变量,即栈上的生成一个结构体,持有原自动变量的成员变量。并且结构体中有成员变量__forwarding持有指向自身的指针。

    注意:Block变量中有成员变量指针:指向__block变量的结构体。Block变量使用多个__block变量,就有多个成员变量指向不同__block变量。

    4、Block存储域

    Block对象的类有三种:

                    类                                    设置对象的存储域

    _NSConcreteStackBlock                    栈

    _NSConcreteGlobalBlock                   程序的数据区域(.data区)

    _NSConcreteMallocBlock                   堆

    a   结构体类型的自动变量,即栈上生成的该结构体的实例。

         前面Block转换为Block的结构体类型的自动变量,__block变量转换为__block变量的结构体类型的自动变量,都是在栈上的_NSConcreteStackBlock对象。

    b   1、Block表达式中不使用应截获的自动变量时(不截获自动变量,可以将Block用结构体实例存储在数据区)

         2、在写全局变量的有Block时(在写全局变量的地方不能使用自动变量,不存在对自动变量截获,Block用结构体的实例内容不 依赖执行时状态,整个程序中只需一个实例)

         以上两种情况:Block为_NSConcreteGlobalBlock类对象。

    c  将 _NSConcreteStackBlock对象 复制到堆上,就是_NSConcreteMallocBlock对象

    -----------------Block超出变量作用域可存在的原因-----------------Blocks提供了将Block和__block变量从栈上复制到堆上的方法,这样即使Block变量的作用域结束,堆上的Block还可以存在,不受作用域影响。

    -----------------__block变量用结构体成员变量__forwarding存在的原因-----------------__block变量的成员变量__forwarding可以实现,无论__block变量在栈上还是在堆上,都能正确访问__block变量。栈上__block变量的成员变量__forwarding指向堆上的结构体实例,堆上的__block变量的成员变量__forwarding指向自身结构体。

     

    ARC有效时,以下情况,编译器提供自动生成将Block从栈上复制到堆上的代码

    a、Block作为函数返回值时

    b、Cocoa框架的方法且方法名含有usingBlock等时、GCD的API

    编译器不能判断,需要手动copy情况:

    a、Block作为函数参数时

    ---------------手动用copy复制效果(ARC有效时,多次调用也没有问题)--------------------

      类                                              设置对象的存储域                                       复制效果

    _NSConcreteStackBlock                    栈                                                           从栈复制到堆

    _NSConcreteGlobalBlock                   程序的数据区域(.data区)                   什么也不做

    _NSConcreteMallocBlock                   堆                                                           引用计数增强

    5、__block变量存储域

                                                    ---------------Block从栈复制到堆时对__block变量产生对影响---------------

    __block变量对配置存储域  Block从栈复制到堆时的影响
     栈  从栈复制到堆并被Block持有
    堆   被Block持有

      多个Block中使用__block变量,在任何一个Block从栈复制到堆时,__block变量也会一并从栈复制到堆并被Block持有,剩下的Block从栈复制到堆时,被复制的__block变量引用计数增加。在堆上的Block被废弃,它所持有的__block变量也就被释放。

    __block变量用结构体成员变量__forwarding存在的原因。通过Block的复制,__block变量也会一并从栈复制到堆,可以同时访问栈上和堆上的__block变量,栈上__block变量的成员变量__forwarding指向堆上的结构体实例,堆上的__block变量的成员变量__forwarding指向自身结构体。

    val.__forwarding->val;

    无论栈上__block变量val还是堆上__block变量val,此时访问的都是同一个__block变量(堆上的)。

    6、截获对象

    Block中截获的对象(有_strong修饰),是在Block用的结构体中附有_strong修饰符的成员变量,堆上Block持有该截获的对象,【和__block变量一样,由于被堆上Block持有】,因而超出其变量作用域可存在。

    只有调用copy函数(自动或手动)才能持有 截获的【有_strong修饰的对象类型】、【__block变量】。

    ---------------调用copy函数和dispose函数的时机--------------

                copy函数                                栈上的Block复制到堆时

              dispose函数                              堆上的Block被废弃时

    Block中使用对象类型自动变量时,什么时候栈上的Block会被复制到堆?

    ARC有效时,以下情况,编译器提供自动生成将Block从栈上复制到堆上的代码

    a、Block作为函数返回值时

    b、Cocoa框架的方法且方法名含有usingBlock等时、GCD的API中传递Block时

    c、调用Block的copy实例方法时

    d、将Block赋值给附有_strong修饰符id类型的类或者Block类型成员变量时

           id_obj  =  ^(){} ;

            Block类型成员变量  =    ^(){} ;    

    7、__block变量和对象

    __block说明符可以指定任何类型的自动变量。

    ARC有效时,id类型以及对象类型变量必定附加所有权修饰符,缺省为_strong修饰符。

    __block id obj=[[NSObject alloc] init];      ------等同于------      __block id __strong obj = [[NSObject alloc] init];//超出作用域可存在

     id __weak obj=obj1;      ---------等同于----------      __block id __weak obj =obj1;//超出作用域无效

    8、Block循环引用

    如果Block中使用附有__strong修饰符的对象类型自动变量,那么当Block从栈复制到堆时,该对象为Block 持有,容易引起循环引用

    typedef void (^blk_t)(void);
    @interface MyObject:NSObject
    {
       blk_t blk;
    }
    @end
    
    @implementation MyObject
    -(id)init{
      self = [super init];
      blk = ^{NSLog(@"self = %@",self);}//将Block赋值给Block类型的成员变量,自动copy到堆
      return self;
    }
    
    
    -(void)dealloc{
      NSLog(@"dealloc");
    }
    
    @end
    
    int main()
    {
      id o=[[MyObject alloc] init];
      NSLog(@"%@",o);
      return 0;
    }

         

    使用Block成员变量循环引用 

    a  声明附有__weak修饰符的变量并赋值self,可以避免循环引用。也可以使用__unsafe_unretained修饰符。     

    //@interface 声明中MyObject中有属性id obj;
    -(id)init{
      self=[super init];
      id __weak tmp=self;//声明附有--weak修饰符的变量,并将self赋值使用
      blk=^{NSLog(@"self = %@",tmp);};
      id __weak obj0=obj;
      blk=^{NSLog(@"self = %@",obj0);};//此时如果写obj相当于self->obj,依然会引起循环引用
      return self;
    }

     

     使用Block成员变量避免循环引用

    b 使用__block变量避免循环引用

    typedef void (^blk_t)(void);
    @interface MyObject:NSObject
    {
       blk_t blk;
    }
    @end
    
    @implementation MyObject
    -(id)init{
      self = [super init];
      __block id tmp = self;//使用__block变量
      blk = ^{  NSLog(@"self = %@",tem);
                tmp=ni;  //变量赋值nil
             }//将Block赋值给Block类型的成员变量,自动copy到堆
      return self;
    }
    
    -(void)execBlock
    {
      blk();
    }
    
    -(void)dealloc{
      NSLog(@"dealloc");
    }
    
    @end
    
    int main()
    {
      id o=[[MyObject alloc] init];
      [o execBlock];//执行Block
      return 0;
    }

    如果没有调用execBlock实例方法,即不执行赋值给成员变量blk的Block,会引起循环引用并引起内存泄漏。

                                  

    通过执行execBlock实例方法,Block被执行,__block变量tmp被赋值nil,__block变量tmp对MyObject类对象的强引用失效。

    使用__block变量 和 使用__weak 及__unsafe_unretained 避免循环引对比:

    使用__block变量优点:

    1、通过__block变量可控制对象的持有期间

    2、在不能使用__weak的环境中不使用__unsafe_unretained即可。

    在执行Block时可动态决定是否将nil或其他对象赋值在__block变量中。

    使用__block变量缺点:

    为避免循环引用必须执行Block

    9、copy/release

    ARC无效时,需要手动将Block从栈复制到堆。用copy实例方法用来复制,用release实例方法来释放。

    只要Block有一次复制到堆上,就可以用retain方法持有。在栈上Block调用retain不起任何作用。

    ARC无效时,__block说明符被用来避免Block中的循环引用。

    这是由于当Block从栈复制到堆时,若Block使用的变量为附有__block的id类型或对象类型的自动变量,不会被retain,,若Block使用的变量为没有__block的id类型或对象类型的自动变量,则被retain,引起循环引用。

     由于ARC有效和无效时,__block说明符用途有很大区别,需要注意!

     

     

     

     

     

     

     

     

     

                                                               

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

     

    展开全文
  • Blocks

    2019-08-03 16:15:52
    Description solution 这题和之前做过的一题的一个套路非常类似:把不是更优的决策给去掉,使得序列变得具有单调性,分析这题: 发现如果两个右端点 \(i\),\(j\) 满足 \(sum[j]<sum[i]\) 且 \(j<...
  • Code::Blocks 17.12 中文版

    2020-07-30 23:32:02
    Code::Blocks 17.12 中文版 汉化方法: 1.关闭Code::Blocks 2.将share文件夹覆盖到Code::Blocks根目录下 3.打开Code::Blocks 4.依次点击Settings --> Environment... -->View 5 .将第二个选项...
  • Code::blocks的安装以及使用方法

    万次阅读 多人点赞 2018-06-07 14:03:49
    1.安装首先进入官网http://www.codeblocks.org/点击downloads这里会有很多个链接,很多小伙伴都不知道选择哪个,我们选择第一个链接这里我选择第四个...使用接下来是如何生成你的第一个文件在左上角找到file里的n...
  • Code::Blocks

    2020-07-24 23:31:39
    Frotran语言的编辑,修改,应用。C/C++, python, the open source
  • CodeBlocks下载与安装教程

    万次阅读 多人点赞 2018-12-15 19:38:49
    一、下载教程 ... 2.进入下载页面Downloads,一般都会选择第一个Download the binary release(二进制版本) ...3.进入之后,就会出来很多版本,我们要选择一个合适的版本,一般都会下载自带编译器的版本,这里到后面...
  • 译: Code::Blocks手册 使用篇

    万次阅读 热门讨论 2010-01-25 14:12:00
    原手册下载:http://www.codeblocks.org/docs/manual_en.pdf 译者:JGood(http://blog.csdn.net/Jgood) 译者言:工欲善其事,必先利其器。一个好的工具能事半功倍。写程序时,特别是写C++程序,大部分人脑子里想到...
  • 今天第一次用code:blocks,在网上搜索了code:blocks的快捷键,现在整理下来一些常用的,以备以后查看。1.查找:Ctrl+F2. Build并运行:F93. Build项目:Ctrl+F94. 运行:Ctrl+F105.debug:F86.Step over a code ...
  • CodeBlocks的安装及使用

    万次阅读 多人点赞 2020-10-14 23:17:28
    CodeBlocks的安装及使用下载并安装CodeBlocks一、在百度等搜索引擎搜索codeblocks或直接输入网址http://www.codeblocks.org/进入CodeBlocks官网 二、进入下载页面三、一般使用的话选择安装二进制版就可以四、选择...
  • 有没有免费的Building Blocks.dotx office 页码模板?
  • code:blocks无法编译运行问题

    万次阅读 2018-06-30 10:01:28
    code:blocks安装后编译无法通过1.首先可能是环境没有配置好,首先打开设置,编译器,可执行工具链,在编译器的安装目录下选择安装的编译器bin目录。如果没有安装编译器,下载mingw:http://www.mingw.org/下载后选择...
  • code blocks 如何实现一键代码格式化

    万次阅读 2016-08-10 11:23:16
    问题:code blocks 如何实现一键代码格式化解答:直接右键,选择format use ASstyle
  • 上午的时候ubuntu弹出了一个提示信息,大概通知了一下显卡升级失败的问题,...blocks这个画面上闪,结合上午的崩溃信息,大概就知道了是显卡驱动出现了问题。 首先先Ctrl+Alt+F1……F6,具体是F几,就挨个试一下...
  • Sonarqube duplicated blocks of code must be remove(Source files should not have any duplicated blocks) 解决办法1: 在UI上面把 Copy-paste detection 去除 可以在Sonarqube Server -》 Administration -》 ...
  • Ubuntu17.10 重启时出现/dev/sda1: clean, ***/*** files, ***/*** blocks 进不去系统的解决方案 按Ctrl+Alt+F1到F6 (至于具体是F几,根据自己的电脑尝试),我的电脑是Ctrl+Alt+F2进入非图形界面,然后输入: ...
  • 今日全编译Android代码出现如下错误:error: ext4_allocate_best_fit_partial: failed to allocate 74 blocks, out of space? Creating filesystem with parameters: Size: 3170938880 Block size: 4096 Blocks ...
  • Scratch Blocks本地环境搭建

    千次阅读 2018-09-17 13:46:21
    Scratch-Blockly配置过程 ...如果你想对Scratch Blocks有进一步的了解,或者想在自己的电脑或服务器上搭建Scratch Blocks的环境,供教学和研究使用,您可以参照下面的内容,进行Scratch Blocks相关环境...
1 2 3 4 5 ... 20
收藏数 215,900
精华内容 86,360
关键字:

blocks