• iOS


     

    iOS中Blocks的介绍

     
     

    1. 什么是Blocks

          Blocks是C语言的扩充功能。就是:带有自动变量的匿名函数。

          类似C语言的函数指针。但Blocks不是一个指针,而是一个不带名字的函数,它允许开发者在两个对象之间将任意的语句当作数据进行传递,同时它又能获得上下文的信息(闭包Closure),所以在一些场合使用Blocks很方便简洁。

     

    2. Block语法

          我们先来看一个例子吧。

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

          这个Block非常简单,就是把计数器加一,但麻雀虽小,五脏俱全,语法上一个元素都没漏掉。首先是^符号(插入符号caret),然后依次是是返回值类型,参数列表,表达式。

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

        caret    返回参数          参数列表               表达式

          齐全的Block就是这些内容了,不同的Block的表达式的复杂程度各异,但元素就是这么多了。

          不过很多时候我们会遇到没有返回参数,或者没有传入参数,甚至既没有传入参数也没有返回参数的情况。这个时候Block可以省略相关的内容,就是说相对应的那一块不用写了。比如:

          没有返回参数,可能就会写成:^(int count){printf("count=%d", count);}

          没有参数列表,可能会写成:^int{return 1;}

          既没有返回参数也没有参数列表,可能就会写成:^{printf("Blocks Demo! ")};

     

          Block还可以申明变量,Block变量一样可以用typedef来定义成新的类型,这种做法下和函数指针真的非常非常类似,仅仅是将*换成了^。举个例子:

          int (^blk)(int) = ^int (int count) {return count+;};

          熟悉C语言的人对这个都会比较熟悉。这里有一个要说明,上面的赋值语句右侧可以省略掉返回类型(猜测是这部分信息编译器已经可以确定,所以不再是必须提供的了)。这样,上面的语句也可以写成;

           int (^blk)(int) = ^(int count) {return count+;};

          如果使用typedef,就可以更清晰一点:

          typedef int (^blk_t)(int);

          blk_t blk =  ^(int count) {return count+;};

     

    3. 截获自动变量值

          Block是带自动变量的匿名函数,下面就要看看“自动变量”了。Block可以访问在它之前申明的变量,但也有它特殊的地方,先看一个例子:  

    1.     int val = 1;  
    2.     void (^blk)(void) = ^{printf("val=%d ", val);};  
    3.     val = 2;  
    4.     blk();  Block在定义时相当于对val这个变量照了张相,
    5.     return 0;  

          运行结果val=1。请注意,虽然这个时候val的值已经变成了2,但Block里面仍然是1,也就是说,Block在定义时相当于对val这个变量照了张相,然后一直自己使用这张相片,不管val本身何去何从。

          这个特性可以带来很大的便利(记下了定义时的上下文),有时是我们所需要的;但一不小心也很容易错,所以使用时需要注意。Block里面,val就是只读的,而且值是定义时的那个。

          虽然记住上下文,Block的内容也跟随改变或者要修改自动变量的值,这个就需要用到__block关键词了。继续上代码:

    1.     __block int val = 1;  
    2.     void (^blk)(void) = ^{printf("val=%d ", val);};  
    3.       
    4.     val = 2;  
    5.     blk();  
    6.     return 0;  
    7. }  

          这段代码和上一段的区别仅仅是多了__block的声明,但运行结果就是val=2了,也就是说,Block能跟踪val的变化了。

     

          这时,Block里面也可以改变val的值了,就是说,用了__block之后,val对于Block不再是只读的了,而是和自己定义的变量一样了。

          在一个Block里面,往往两种变量都需要有,具体怎么使用,就看具体的情况了。

     

    4. Block的使用

          我总是觉得任何一种技术的出现总是用来解决某个问题的,也决定了在何种情况下使用该技术。Block应该如何使用呢?这是仁者见仁,智者见智的问题了,我接触最多的是在GCD里面。

          个人的感觉这个东东主要用在回调里面,比如:网络连接成功后我应该把某个按钮激活之类的,Block使用起来简洁明快,如鱼得水。GCD实际上也是给出了系统的回调,所以就特别适合Block大显身手。

     

    5. 其他

          Block当然还有其他的一些内容,比如可以作为函数参数来传递,比如当自动变量是ObjC的对象时,虽然不能修改,但可以调用对象的方法,再比如,C语言的数组不能作为自动变量等等。但这些都不是Block主要的内容,最重要的还是灵活的使用。

     

     

     

    深入Blocks分析

    1.简介

          从iOS4开始,苹果引入了这个C语言的扩充功能“Blocks”,在一些特定的场景下也是一把利刃。我前面一篇博客中初步介绍了Blocks这个东西,主要是语法的介绍(《iOS中Blocks的介绍》)。

          我曾经看见了老外的一个系列的Blocks介绍,很有深度(A look inside blocks:Episode 1A look inside blocks:Episode 2, A look inside blocks:Episode 3),里面深入到汇编的层次对Blocks的实现进行了分析。不过如果象我这样对于汇编不熟悉的人肯定也是很多的,理解起来十分痛苦,于是就想到从ObjC本身对Blocks进行的处理里面来入手分析,看看对于Blocks都悄悄做了什么。

    2.环境

          很简单,就是Xcode啦。使用的编译器是CLang,主要是利用了-rewrite-objc这个参数,把源文件转换成中间文件。这样就揭开了面纱的一角。我使用的clang编译器版本是:

          Apple clang version 4.0 (tags/Apple/clang-421.0.60) (based on LLVM 3.1svn)
          Target: x86_64-apple-darwin12.5.0
          Thread model: posix

     

          转成中间文件的命令是:clang -rewrite-objc 源文件

    3. 例子1

     

    1. #include <stdio.h>  
    2.   
    3. int main(int argc, const charchar * argv[])  
    4. {  
    5.     int val = 2;  
    6.     int val1 = 5;  
    7.     void (^blk)(void) = ^{printf("in Block():val=%d ", val);};  
    8.       
    9.     val = 4;  
    10.     blk();  
    11.     printf("in main(): val=%d, val1=%d ", val, val1);  
    12.     return 0;  
    13. }  

          代码的运行结果是:

          in Block():val=2

          in main():val=4, val1=5

          这里我们可以看到Block对于自动变量的“快照”功能。由于转成中间文件之后发现长了很多,变成了100多行(不同的clang版本转换出来的文件还不同,不过实现部分代码还是一样的),下面的代码是相关部分的节选,主要说明苹果是如何实现的。

     

    1. struct __block_impl {  
    2.   voidvoid *isa;  
    3.   int Flags;  
    4.   int Reserved;  
    5.   voidvoid *FuncPtr;  
    6. };  

    1. #include <stdio.h>  
    2.   
    3. int main(int, const charchar **);  
    4.   
    5. struct __main_block_impl_0 {  
    6.   struct __block_impl impl;  
    7.   struct __main_block_desc_0* Desc;  
    8.   int val;  
    9.   __main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, int _val, int flags=0) : val(_val) {  
    10.     impl.isa = &_NSConcreteStackBlock;  
    11.     impl.Flags = flags;  
    12.     impl.FuncPtr = fp;  
    13.     Desc = desc;  
    14.   }  
    15. };  
    16. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
    17.   int val = __cself->val; // bound by copy  
    18. printf("in Block():val=%d ", val);}  
    19.   
    20. static struct __main_block_desc_0 {  
    21.   unsigned long reserved;  
    22.   unsigned long Block_size;  
    23. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};  
    24. int main(int argc, const charchar * argv[])  
    25. {  
    26.     int val = 2;  
    27.     int val1 = 5;  
    28.     void (*blk)(void) = (void (*)(void))&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA, val);  
    29.       
    30.     val = 4;  
    31.     ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);  
    32.     printf("in main(): val=%d, val1=%d ", val, val1);  
    33.     return 0;  
    34. }  

          中间文件确实看起来复杂了不少,不过还是有脉络可循。

          看main函数的内容,里面有个函数指针blk,这个就是指向Block的指针,所以难怪Block变量的声明和函数指针如此相像(就是把*换成^),编译器转换后就是同一个东西啊。

          我们看blk这个函数指针,就是__main_block_impl_0这个结构体变量的指针,这个结构体变量此时已经存在,然后用__main_block_func_0等几个变量赋初值。我们可以看到__main_block_impl_0这个struct中有个val这个项,并且在这里也赋值了,这就是给变量照的“快照”,由于这个变量在这里被记录了,所以无论外面的val变量如何变化,Block运行时使用的值就始终是“快照”的值了。同时我们也注意到__main_block_impl_0这个struct中没有val1这个项,所以说明如果Block中不用到的自动变量是不会自动加入到结构体中的。

          Block的运行就是运行__main_block_impl_0这个struct中的FuncPtr这个指针,这个在前面初始化的时候已经被赋值成__main_block_func_0了,所以这里也就是运行这个函数,并把自己的指针传入。这里我们的实现非常简单,就是一句printf语句。

    4.例子2

     

    1. #include <stdio.h>  
    2.   
    3. int main(int argc, const charchar * argv[])  
    4. {  
    5.     int __block val = 2;  
    6.     int val1 = 5;  
    7.     void (^blk)(void) = ^{printf("in Block():val=%d ", ++val);};  
    8.       
    9.     blk();  
    10.     printf("in main(): val=%d, val1=%d ", val, val1);  
    11.     return 0;  
    12. }  

          这个例子把自动变量val声明成了__block变量,这样语法上Block不是对val进行“快照”,而是会直接使用val变量,同时在Block内部val可读可写,不再是只读的了。

     

          运行结果如下:

          in Block():val=3
          in main(): val=3, val1=5

          同样展开成中间文件来看苹果的实现。

     

    1. struct __block_impl {  
    2.   voidvoid *isa;  
    3.   int Flags;  
    4.   int Reserved;  
    5.   voidvoid *FuncPtr;  
    6. };  

    1. #include <stdio.h>  
    2.   
    3. int main(int, const charchar **);  
    4. struct __Block_byref_val_0 {  
    5.   voidvoid *__isa;  
    6. __Block_byref_val_0 *__forwarding;  
    7.  int __flags;  
    8.  int __size;  
    9.  int val;  
    10. };  
    11.   
    12. struct __main_block_impl_0 {  
    13.   struct __block_impl impl;  
    14.   struct __main_block_desc_0* Desc;  
    15.   __Block_byref_val_0 *val; // by ref  
    16.   __main_block_impl_0(voidvoid *fp, struct __main_block_desc_0 *desc, __Block_byref_val_0 *_val, int flags=0) : val(_val->__forwarding) {  
    17.     impl.isa = &_NSConcreteStackBlock;  
    18.     impl.Flags = flags;  
    19.     impl.FuncPtr = fp;  
    20.     Desc = desc;  
    21.   }  
    22. };  
    23. static void __main_block_func_0(struct __main_block_impl_0 *__cself) {  
    24.   __Block_byref_val_0 *val = __cself->val; // bound by ref  
    25. printf("in Block():val=%d ", ++(val->__forwarding->val));}  
    26. static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->val, (void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}  
    27.   
    28. static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->val, 8/*BLOCK_FIELD_IS_BYREF*/);}  
    29.   
    30. static struct __main_block_desc_0 {  
    31.   unsigned long reserved;  
    32.   unsigned long Block_size;  
    33.   void (*copy)(struct __main_block_impl_0*, struct __main_block_impl_0*);  
    34.   void (*dispose)(struct __main_block_impl_0*);  
    35. } __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0), __main_block_copy_0, __main_block_dispose_0};  
    36. int main(int argc, const charchar * argv[])  
    37. {  
    38.     __Block_byref_val_0 val = {(void*)0,(__Block_byref_val_0 *)&val, 0, sizeof(__Block_byref_val_0), 2};  
    39.     int val1 = 5;  
    40.     void (*blk)(void) = (void (*)(void))&__main_block_impl_0((voidvoid *)__main_block_func_0, &__main_block_desc_0_DATA, (struct __Block_byref_val_0 *)&val, 570425344);  
    41.       
    42.     ((void (*)(struct __block_impl *))((struct __block_impl *)blk)->FuncPtr)((struct __block_impl *)blk);  
    43.     printf("in main(): val=%d, val1=%d ", (val.__forwarding->val), val1);  
    44.     return 0;  
    45. }  

          中间文件又变长了一些,除去我们已经了解的部分,我们可以看到自动变量val不再是直接加入到__main_block_impl_0里面,而是又变成了一个__Block_byref_val_0的struct结构体的指针。

          main函数里面对于val的赋值已经变成了对这样一个数据结构的赋值,第一句上就把2赋给了__Block_byref_val_0里面的val项,然后在blk这个指针初始化的时候,把__Block_byref_val_0的结构体变量指针传入__main_block_impl_0。此后所有对于自动变量val的操作都变成对val.__forwarding->val的操作。这样就解决了Block内外变量同时变化的问题(在操作同一块内存)。

          这里还看见__Block_byref_val_0里面有个__forwarding项,这个项是指向自身的一根指针。在blk指针初始化的时候我们把这个指针的值传入了__main_block_impl_0。

          在__main_block_desc_0里面,多出了两个函数指针,分别用于copy和dispose,这两个函数这里也是自动生成的。

    5.总结

          综合前面的例子来看,Block的实现还是借助了C语言的函数指针来实现了,对于普通的自动变量,在Block声明时会快照内容存储;对于__block变量,则是生成一个数据结构来存储,然后取代所有访问这个变量的地方。

          事实上,因为Block运行时完全可能自动变量的生命周期已经结束,所以Block对于内存的管理是很复杂的,会把内容从栈上copy到堆上(这也是copy和dispose函数的作用)。所以Block虽然威力巨大,但使用时也需要遵循一定的规则。

    iOS中Block介绍(二)内存管理与其他特性

     

    我们在前一章介绍了block的用法,而正确使用block必须要求正确理解block的内存管理问题。这一章,我们只陈述结果而不追寻原因,我们将在下一章深入其原因。

    AD:2014WOT全球软件技术峰会北京站 课程视频发布

    一、block放在哪里

    我们针对不同情况来讨论block的存放位置:

    1.栈和堆

    以下情况中的block位于堆中:

    1. void foo()  
    2. {  
    3.     __block int i = 1024;  
    4.     int j = 1;  
    5.     void (^blk)(void);  
    6.     void (^blkInHeap)(void);  
    7.     blk = ^{ printf("%d, %d ", i, j);};//blk在栈里  
    8.     blkInHeap = Block_copy(blk);//blkInHeap在堆里  
    9. }  
    10.    
    11. - (void)fooBar  
    12. {  
    13.     _oi = 1;  
    14.     OBJ1* oj = self;  
    15.     void (^oblk)(void) = ^{ printf("%d ", oj.oi);};  
    16.     void (^oblkInHeap)(void) = [oblk copy];//oblkInHeap在堆中  

    2.全局区

    以下情况中的block位于全局区:

    1. static int(^maxIntBlock)(int, int) = ^(int a, int b){return a>b?a:b;};  
    2. - (void)fooBar  
    3. {  
    4.      int(^maxIntBlockCopied)(int, int) =[maxIntBlock copy];  
    5. }  
    6. void foo()  
    7. {  
    8.      int(^maxIntBlockCopied)(int, int) = Block_copy(maxIntBlock);  

    需要注意的是,这里复制过后的block依旧位于全局区,实际上,复制操作是直接返回了原block对象。

    二、block引用的变量在哪里

     1.全局区

    全局区的变量存储位置与block无关:

    1. static int gVar = 0;  
    2. //__block static int gMVar = 1;  
    3. void foo()  
    4. {  
    5.     static int stackVar = 0;  
    6. //    __block static int stackMVar = 0;  

    注意:static变量是不允许添加__block标记的

    2.堆栈 

    此时,你可能会问,当函数foo返回后,栈上的j已经回收,那么blkInHeap怎么能继续使用它?这是因为没有__block标记的变量,会被当做实参传入block的底层实现函数中,当block中的代码被执行时,j已经不是原来的j了,所谓物是人非就是这样吧~

    另外,如果使用到变量j的所有block都没有被复制至heap,那么这个变量j也不会被复制至heap。

    因此,即使将j++这一句放到blk()这句之前,这段代码执行后,控制台打印结果也是:1024, 1。而不是1024, 2

    三、其他特性

    1.复制的行为

    对block调用复制,有以下几种情况:

    1.对全局区的block调用copy,会返回原指针,并且这期间不处理任何东西(至少目前的内部实现是这样);

    2.对栈上的block调用copy,每次会返回新复制到堆上的block的指针,同时,所有__block变量都会被复制至堆一份(多次拷贝,只会生成一份)。

    3.对已经位于heap上的block,再次调用copy,只会增加block的引用计数。

    为什么我们不讨论retian的行为?原因是并没有Block_retain()这样的函数,而且objc里面的retain消息发送给block对象后,其内部实现是什么都不做。

    2.objc类中的block复制

    objc类实例方法中的block如果被复制至heap,那么当前实例会被增加引用计数,当这个block被释放时,此实例会被减少引用计数。

    但如果这个block没有使用当前实例的任何成员,那么当前实例不会被增加引用计数。这也是很自然的道理,我既然没有用到这个instance的任何东西,那么我干嘛要retian它?

    我们要注意的一点是,我看到网上有很多人说block引起了实例与block之间的循环引用(retain-cycle),并且给出解决方案:不直接使用self而先将self赋值给一个临时变量,然后再使用这个临时变量。

    但是,大家注意,我们一定要为这个临时变量增加__block标记(多谢第三篇文章回帖网友的提醒)。

    这一章我们以结果导向的方式来说明了各种情况下,block的内存问题,下一章,我将剖析运行时库的源码,从根源阐述block的行为。也就是过程导向的方式了。

     //Block

        // '^'脱字符

        

        //函数指针

        int (*p)(int, int) = sum;

        

        //调用函数

        sum(3, 4);

        p(2 ,4);

        

        //声明一个Block变量

        /*1*///int:返回值   ,后面两个int:参数类型

        int (^block1) (int, int) = nil;

        

        //block 变量存储的值,是block的实现部分

        //实现部分返回值类型常省略

        //参数变量名不能省略

        //以';'结尾

        block1 = ^(int a, int b){

            return a + b;

        };

        

        //使用block1

        int sum = block1(2, 4);

        NSLog(@"%d",sum);

        

        

        /*2*//////////////////////////////////

        void (^block2)() = ^(){

            NSLog(@"无参数 无返回值的  block2(); ");

        };

        

        block2();//调用

        

        

        //3,求两个数最大值 block

        int (^maxValue) (int x,int y) = ^(int a,int b){

            return a > b ? a : b;

        };

        

        int max = maxValue(2,3);//调用

        NSLog(@"最大值:%d",max);

        

        //4, 将字符串转成数字的block

        int (^numberFromString)(NSString *) = ^(NSString *str){

            return [str intValue];

        };

        

        NSLog(@"%d",numberFromString(@"123"));//回调了 ^numberFromString 这个block

        

        

        //5, 给可变数组排序的block

        

        //5,1

        NSComparator sortBlock = ^(id string1, id string2)

        {

            return [string1 compare:string2];

        };

        

        NSMutableArray *stringArray = [NSMutableArray arrayWithObjects:@"a", @"c", @"b", @"d",nil];

        

        [stringArray sortedArrayUsingComparator:sortBlock];

        

        NSLog(@"sortArray:%@", stringArray);

        

        //5,2

        [stringArray sortUsingComparator:^NSComparisonResult(id obj1,id obj2){

            if ([obj1 intValue] > [obj2 intValue]) {

                return NSOrderedDescending;

            }

            return NSOrderedSame;

        }];

        

        NSLog(@"--%@",stringArray);

        

        

        //6, 取别名

        typedef int (^SumBlock) (int,int);

        

        SumBlock sumBlock1 = ^(int a,int b){

            return a + b;

        };

        

        NSLog(@"%d",sumBlock1(7,8));

        

        

        //7, 求阶乘 的block

        int (^jiecheng)(int) = ^(int n){

            int m = 1;

            for (int i = 1; i <= n; i++) {

                m *= i;

            }

            return m;

        };

        

        NSLog(@"阶乘:%d",jiecheng(3));

        

        //8, block内部可以访问外部局部变量,但是不能修改,若要修改需加__block 修饰

        __block int count = 2;

        

        void (^sayHi)() = ^(){

            count = 3;

            NSLog(@"count 被修改成=%d",count);

        };

      

        sayHi();

        

        //9,

       

        

        

  • 相关阅读:
    Excelpackage的简单使用(导出用户信息并保存)
    set nocount on/off的作用,可配合存储过程使用
    在sql中case子句的两种形式
    C#开发微信门户及应用(1)--开始使用微信接口(转)
    张建总的一封信
    Jquery在线工具
    《EnterLib PIAB深入剖析》系列博文汇总 (转)
    微软企业库5.0 学习之路——第十步、使用Unity解耦你的系统—PART1——为什么要使用Unity? (转)
    结合领域驱动设计的SOA分布式软件架构 (转)
    ENode框架旨在帮助我们开发ddd,cqrs、eda和事件采购风格的应用程序。(netfocus 汤雪华)
  • 原文地址:https://www.cnblogs.com/iOS-mt/p/4110523.html
Copyright © 2020-2023  润新知