美文网首页
Block 那些事

Block 那些事

作者: comst | 来源:发表于2016-03-29 06:29 被阅读46次

    Block 那些事

    概念

    Block是iOS4.0+ 和Mac OS X 10.6+ 引进的对C语言的扩展,用来实现匿名函数的特性
    闭包是一个能够访问其他函数内部变量的函数.

    基本语法

    ^(returnType)(argsList)
    body
    }
    returnType可以省略,argsList如果没有可以省略,最简单的block语法形式如:
    ^
    body;
    }
    定义一个无参,返回值为void的block。
    如果定义一个block变量,则语法形式为:
    returnType (^blockVar)(argsList)
    body;
    }
    例如:
    int (^blockVar)(NSString* arg)
    return @“Hello World”;
    }
    上述定义一个 返回值为整数,带有一个字符串参数的blockVar变量。
    为了方便使用常常使用typedef给block类型定义一个别名,如
    typedef int (^returnIntBlock)(NSSstring *arg);
    returnIntBlock blockVar;
    此时returnIntBlock就代表反回值为整数参数为字符串的block类型。blockVar 代表 该block类型的一个变量

    基本用法
    1. 定义普通变量。

       //定义一个返回值为整型,带有两个整形参数的block变量
       NSInteger (^returnIntVar)(NSInteger, NSInteger);
       //给block变量赋值
       returnIntVar = ^NSInteger(NSInteger left, NSInteger right){
               return left + right;
           };
      
    2. 定义属性。

       @property (nonatomic, copy) NSInteger (^returnIntBlock)(NSInteger, NSInteger);
      
    3. block作为方法参数。

       - (void)methodWithBlockArg:(NSInteger (^)(NSInteger, NSInteger))blockArg{
           NSInteger sum ;
           sum =  blockArg(3, 4);
       }
      
    4. block作为方法的返回值。
      - (void (^)(NSString *arg))methodReturnBlock{

           return ^void(NSString *arg){
               NSLog(@"arg: %@", arg);
           };
       }
      
    5. block作为函数的返回值。
      void (^functionReturnBlock(NSString *arg))(NSString *arg){

           return ^void(NSString *arg){
               NSLog(@"arg: %@", arg);
           };
       }
      
    block注意事项
    1. 修改引用的外部变量。
      默认情况下block内部是不能修改应用变量的值的,若要修改,需在定义外部变量时使用__block关键字修饰。
      __block NSInteger a = 3;
      void(^blockOp)() = ^(){
      a = 5;
      };
      blockOp();

    2. 循环引用。最常见的情况是对象拥有block,block内部又去访问对象的属性,这时,在block外面定义一个弱引用。如下
      __weak typeof(self) weakSelf = self;
      self.returnIntBlock = ^(NSInteger left, NSInteger right){

                weakSelf.sum =    left + right ;
               
               return weakSelf.sum;
           };
      
    block原理

    为了研究编译器是如何实现 block 的,我们需要使用 clang。clang 提供一个命令,可以将 Objetive-C 的源码改写成 c 语言的,借此可以研究 block 具体的源码实现方式。该命令是:

    clang -rewrite-objc block.c
    我们先新建一个名为 block1.c 的源文件:
    include <stdio.h>

    int main()
    {
            int a = 100;
            void (^block2)(void) = ^{
            printf("%d\n", a);
            };
            block2();
            return 0;
    }
    

    然后在命令行中输入:

    clang -rewrite-objc block1.c

    如果成功,会在当前目录下生成一个block.cpp的源文件。该文件中的关键代码如下:
    struct __block_impl {
    void *isa;
    int Flags;
    int Reserved;
    void FuncPtr;
    };
    struct __main_block_impl_0 {
    struct __block_impl impl;
    struct __main_block_desc_0
    Desc;
    int a;
    __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
    }
    };
    static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
    int a = __cself->a; // bound by copy
    printf("%d\n", a);
    }

    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 a = 100;
    void (*block2)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a);
    ((void (*)(__block_impl *))((__block_impl *)block2)->FuncPtr)((__block_impl *)block2);
    return 0;
    }
    

    其中:

    • isa 指针,所有对象都有该指针,用于实现对象相关的功能。

    • flags,用于按 bit 位表示一些 block 的附加信息。

    • reserved,保留变量。

    • funcPtr,函数指针,指向具体的 block 实现的函数调用地址。

    • desc, 表示该 block 的附加描述信息,主要是 size 大小,以及 copy 和 dispose 函数的指针。

    • a,capture 过来的变量,block 能够访问它外部的局部变量,就是因为将这些变量(或变量的地址)复制到了结构体中。
      我们修改上面的源码,在变量前面增加 __block关键字:
      include <stdio.h>

      int main()
      {
      __block int i = 1024;
      void (^block1)(void) = ^{
      printf("%d\n", i);
      i = 1023;
      };
      block1();
      return 0;
      }

    生成的关键代码如下,可以看到,差异相当大:

        struct __Block_byref_i_0 {
        void *__isa;
        __Block_byref_i_0 *__forwarding;
        int __flags;
        int __size;
        int i;
        };
        
        struct __main_block_impl_0 {
        struct __block_impl impl;
        struct __main_block_desc_0* Desc;
        __Block_byref_i_0 *i; // by ref
        __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__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_i_0 *i = __cself->i; // bound by ref
        
        printf("%d\n", (i->__forwarding->i));
        (i->__forwarding->i) = 1023;
        }
        
        static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->i, (void*)src->i, 8/*BLOCK_FIELD_IS_BYREF*/);}
        
        static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->i, 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()
        {
        __attribute__((__blocks__(byref))) __Block_byref_i_0 i = {(void*)0,(__Block_byref_i_0 *)&i, 0, sizeof(__Block_byref_i_0), 1024};
        void (*block1)(void) = (void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_i_0 *)&i, 570425344);
        ((void (*)(__block_impl *))((__block_impl *)block1)->FuncPtr)((__block_impl *)block1);
        return 0;
        }
    

    源码中增加一个名为 __Block_byref_i_0 的结构体,用来保存我们要 capture 并且修改的变量 a。
    main_block_impl_0 中引用的是 Block_byref_i_0 的结构体指针,这样就可以达到修改外部变量的作用。
    对于 block 外的变量引用,block 默认是将其复制到其数据结构中来实现访问的。


    对于用 __block 修饰的外部变量引用,block 是复制其引用地址来实现访问的。

    相关文章

      网友评论

          本文标题:Block 那些事

          本文链接:https://www.haomeiwen.com/subject/wqfslttx.html