底层学习07_Block

182 阅读7分钟

Block

  • 概念:Block是C语言的扩充功能.是带有自动变量(局部变量)的匿名函数

Block的实质

  • 源码
struct Block_layout {
    void *isa;
    volatile int32_t flags; // contains ref count
    int32_t reserved;
    BlockInvokeFunction invoke;
    struct Block_descriptor_1 *descriptor;
    // imported variables
};
  • 可以看出block是一个结构体, 现在我们分析一个具体的myBlock
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        NSLog(@"Hello, World!");
        
        void(^myBlock)(void) = ^() {
            
        };
        
        myBlock();
    }
    return 0;
}
  • clang -rewrite-objc之后
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) {


        }

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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        NSLog((NSString *)&__NSConstantStringImpl__var_folders_pv_z69915_n7y703_tcrj_fsw7h0000gp_T_main_9174ff_mi_0);

        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));

        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}
  • 我们将 __main_block_impl_0分为3个部分看,由于构造函数也一并写入,看起来很复杂,去掉该构造函数之后,可以看出结构体__main_block_impl_0的具体信息:
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
};
  • 我们展开第一个__block_impl结构体的声明
struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
  • 第二个__main_block_desc_0
static struct __main_block_desc_0 {
  size_t reserved;
  size_t Block_size;
}
  • 构造函数:
__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的源代码.

  • 我们再看__main_block_impl_0构造函数的调用

void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// 去掉转换部分 ->
struct __main_block_impl_0 tmp = __main_block_impl_0(__main_block_func_0, &__main_block_desc_0_DATA);
struct __main_block_impl_0 *myBlock = &tmp;
  • 这样就容易理解多了.其实就是将结构体实例的指针赋值给变量myBlock.

  • 再看调用myBlock的部分

((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
// 去掉转换->
(*myBlock->impl.FuncPtr)(myBlock)
  • 这就是简单的使用函数指针调用函数.
  • 另外我们可以在Block结构体中看到isa,说明Block其实是Objective-C对象.
  • Block结构体中flags含义
enum {
    BLOCK_DEALLOCATING =      (0x0001),  // runtime
    BLOCK_REFCOUNT_MASK =     (0xfffe),  // runtime
    BLOCK_NEEDS_FREE =        (1 << 24), // runtime
    BLOCK_HAS_COPY_DISPOSE =  (1 << 25), // compiler
    BLOCK_HAS_CTOR =          (1 << 26), // compiler: helpers have C++ code
    BLOCK_IS_GC =             (1 << 27), // runtime
    BLOCK_IS_GLOBAL =         (1 << 28), // compiler
    BLOCK_USE_STRET =         (1 << 29), // compiler: undefined if !BLOCK_HAS_SIGNATURE
    BLOCK_HAS_SIGNATURE  =    (1 << 30), // compiler
    BLOCK_HAS_EXTENDED_LAYOUT=(1 << 31)  // compiler
};
  • FuncPtr = ft, 也就是main函数里传过来的. __main_block_func_0. 是block被调用时执行的函数指针
  • desc 是 __main_block_desc_0_DATA. 是对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)};

外部变量

  • 那么block是如何捕获外部变量的?
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        // insert code here...
        
        NSInteger a = 10;
        void(^myBlock)(void) = ^() {
            NSLog(@"a = %ld", a);
        };
        
        myBlock();
    }
    return 0;
}
  • clang之后
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSInteger a;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSInteger _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) {
  NSInteger a = __cself->a; // bound by copy

            NSLog((NSString *)&__NSConstantStringImpl__var_folders_pv_z69915_n7y703_tcrj_fsw7h0000gp_T_main_12aee4_mi_0, 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 argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 


        NSInteger a = 10;
        void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

        ((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
    }
    return 0;
}
  • 可以看出我们的局部变量a是传到struct内部作为block的成员变量存储的.
  • 我们都知道在block内部是是不能改变a的值的.如果在block内改变外部变量的值怎么办呢?

__block说明符

  • 有两种方法在block中改变外部变量.第一种:
int global_value = 1;
static int static_global_value = 2;

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        static int static_value = 3;
        void(^blk)(void) = ^() {
            global_value *= 1;
            static_global_value *= 2;
            static_value *= 3;
        };
    }
    return 0;
}

转换后:

int global_value = 1;
static int static_global_value = 2;


struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int *static_value;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int *_static_value, int flags=0) : static_value(_static_value) {
    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_value = __cself->static_value; // bound by copy

            global_value *= 1;
            static_global_value *= 2;
            (*static_value) *= 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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        static int static_value = 3;
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &static_value));
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

  • global_value和static_global_value和转换前的访问完全一样.只有static_value发生了转换.将static_value的指针传递给构造函数并保存在结构体的成员变量中.

  • 在block中改变值的另一种办法就是使用__block存储域类说明符.类似于static, auto等说明符,他们用于指定将变量值设置到哪个存储域中.auto-栈, static-数据区.

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        
        __block int blk_value = 3;
        void(^blk)(void) = ^() {
            blk_value *= 3;
        };
    }
    return 0;
}

转换后:

struct __Block_byref_blk_value_0 {
  void *__isa;
__Block_byref_blk_value_0 *__forwarding;
 int __flags;
 int __size;
 int blk_value;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  __Block_byref_blk_value_0 *blk_value; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_blk_value_0 *_blk_value, int flags=0) : blk_value(_blk_value->__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_blk_value_0 *blk_value = __cself->blk_value; // bound by ref

            (blk_value->__forwarding->blk_value) *= 3;
        }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->blk_value, (void*)src->blk_value, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->blk_value, 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, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 

        __attribute__((__blocks__(byref))) __Block_byref_blk_value_0 blk_value = {(void*)0,(__Block_byref_blk_value_0 *)&blk_value, 0, sizeof(__Block_byref_blk_value_0), 3};
        void(*blk)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, (__Block_byref_blk_value_0 *)&blk_value, 570425344));
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };
  • 加上__block之后代码量激增.首先blk_value变为结构体类型__Block_byref_blk_value_0的自动变量,而真正的值存在该结构体实例的成员变量blk_value中. 这也回答了为什么__block的变量可以在block内部被改变的原因:

    • 为了在block内部访问到blk_value,我们需要把blk_value复制到堆区,所以我们实例化一个__Block_byref_blk_value_0结构体实例.然后将值存到该结构体的成员变量中.然后将该结构体实例的地址传给Block的构造函数.
  • blk_value *= 3 其实取的是__Block_byref_blk_value_0结构体实例内部的blk_value的值

    • 转换:
    blk_value->__forwarding->blk_value) *= 3;
    
  • 为什么会有成员变量__forwarding呢?

Block的存储域

  • 有3种Block.这3种block的存储域如下图所示
设置对象的存储域
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序的数据区 (.data区)
_NSConcreteMallocBlock
  • 怎么知道我们的Block是哪种Block呢?

    • _NSConcreteGlobalBlock

      • 记述全局变量的地方有Block语法时:
      void (^blk)(void) = ^{NSLog(@"Hello World");};
      
      int main {
      	return 0;
      }
      
      • Block语法的表达式中不使用应截获的自动变量时:
      int a = 10;
      void(^blk)(void) = ^() {
          // 这里不使用a
          NSLog(@"hello world");
      };
      blk();
      
    • 除此之外都为_NSConcreteStackBlock.

    • _NSConcreteMallocBlock:

      • 复制到堆上的Block将_NSConcreteMallocBlock类对象写入Block用结构体实例的成员变量isa.
      • 上一节成员变量__forwarding存在的作用:__block变量用结构体成员变量__forwarding可以实现无论__block变量配置在栈上,还是堆上,都能正确的访问__block变量.
  • 什么时候会将Block复制到堆上呢?

    • 调用Block的copy实例方法时
    • Block作为函数返回值返回时
    • 将Block复制给附有__strong修饰id类型的类或Block类型成员变量时.
    • 在方法名中含有suingBlock的Cocoa框架方法或者GCD的API传递Block时
  • 一个Block使用多个__block变量,在该Block复制到堆时,其所有__block变量也会一并复制到堆上

  • 多个Block使用__block变量, 任何一个Block复制到堆时,其Block也一并复制到堆,并被该Block持有.剩下的Block复制到堆时,被复制的Block持有__block变量并增加__block的引用计数.

  • 变量__forwarding可以实现无论__block变量配置在栈上,还是堆上,都能正确的访问__block变量. 重新理解一下这句话: 也就是当Block0复制到堆,但是Block1还没复制到堆的这一时刻.Block1内部的__forwarding会指向已经复制到堆的__block变量结构体. 如图所示:

截获对象

  • 如下代码会打印什么?
typedef void (^blk_t)(id);

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        blk_t blk;
        {
            id array = [NSMutableArray array];
            blk = ^(id obj) {
                [array addObject:obj];
                NSLog(@"array count = %ld", [array count]);
            };
        }
        
        blk([[NSObject alloc] init]);
        blk([[NSObject alloc] init]);
    }
    return 0;
}

答案是:

array count = 1
array count = 2
  • 该代码通过编译器转换后:
typedef void (*blk_t)(id);


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_pv_z69915_n7y703_tcrj_fsw7h0000gp_T_main_7454ed_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};
int main(int argc, const char * argv[]) {
    /* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool; 
        blk_t blk;
        {
            id array = ((NSMutableArray * _Nonnull (*)(id, SEL))(void *)objc_msgSend)((id)objc_getClass("NSMutableArray"), sel_registerName("array"));
            blk = ((void (*)(id))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, array, 570425344));
        }

        ((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")));
    }
    return 0;
}
static struct IMAGE_INFO { unsigned version; unsigned flag; } _OBJC_IMAGE_INFO = { 0, 2 };

在Block复制到堆时,通过__main_block_copy_0 也就是copy方法 使堆Block持有array. 同理在堆Block释放时,通过__main_block_dispose_0方法释放对array的持有.

总结

  • 通过Block的isa我们知道Block是Objective-C对象.有3种Block
    • _NSConcreteStackBlock
    • _NSConcreteGlobalBlock
    • _NSConcreteMallocBlock
  • Block内使用的变量局部变量和静态变量会存在Block结构体成员变量中.
  • __block变量会被变为结构体类型__Block_byref_blk_value_0,被Block持有.
  • 当Block被复制到堆时, 被其持有的__block变量也会被复制到堆.
  • 当Block内使用了带有__strong修饰符的id类型或对象类型变量时, 编译器会自动增加copy和dispose方法,当Block被赋值到堆时被调用.copy和dispose方法是为了使其被堆Block持有和释放.