频道栏目
首页 > 程序开发 > 移动开发 > 其他 > 正文
Block的使用与实现原理
2017-01-07 10:41:00      个评论      
收藏   我要投稿

目录

目录 什么是block block语法 block 类型变量 截获自动变量 Block的实现原理 截获自动变量的机制 __block说明符 Block存储域

什么是block?

Block是C语言的扩充,是一个自动包含局部变量的匿名函数。在C语言中所有的函数都要有名字,声明一个C函数:

int fun(int par1);

使用C函数:

int count = fun(2);

可以通过函数指针使用函数

int (*funptr)(int) = &fun;
int count = (*funptr)(2);

block在相当程度上和函数是十分相似的,但是他是一个匿名的函数。

block语法

我们知道了block是一个匿名的函数,除此之外block具有一个显著的特点就是block总有一个^ ,我们看一个定义block的栗子。

clang -rewrite-objc filename

我们试着将下面的oc代码翻译成C++

void (^blk)(void) = ^{};

转换后变为:

blk = &__main_block_impl_0(__main_block_func_0,&__main_block_desc_0_DATA)

由此我们知道blk被转换成一个指向__main_block_impl_0结构体,并且结构体在初始化时把函数指针和DATA传入了。

同样的,最初代码的第二行:

blk();

转换后变为:

 ( (void (*)(__block_impl *)) ((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

简化后:

(*blk->FuncPtr)(blk);

十分明了对吧。

截获自动变量的机制

通过上面的分析我们大概明白了block是个神马东东,也自动代码最后转换成什么样子,以及他们之间是怎么调用生效的。现在只剩下如何捕获自动变量,以及自动变量怎么使用的。

同样通过一个栗子来说明:

void add(int par1,int par2){
   int tmp = par1+par2;
}
int main()
{
    int a = 10;
    int b = 20;
    void(^blk)() = ^void (){
        add(a,b);
    };
    blk();
    return 0;
}

clang -rewrite-objc filename翻译得到:

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;
  int b;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int _b, int flags=0) : a(_a), b(_b) {
    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
  int b = __cself->b; // bound by copy

  add(a,b);
 }

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 = 10;
 int b = 20;

 void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a, b));
 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

 return 0;
}

仔细看一下就觉得不可思议的简单,就是给__main_block_impl_0增加两个变量,并且在初始化的时候把当前值传入__main_block_impl_0的初始化函数中。在__main_block_func_0使用时就可以从self中自己取出来了。原来所谓自动捕获就是赋值呀,擦,要不要这么吓唬人!!如何我们不考虑其他的细节,Block也就如此而已。but,现实是,其他的细节恐怖的让人绝望。下面来分析一些。

__block说明符

我们将刚刚的栗子稍微修改一下:

void add(int par1,int par2){
   int tmp = par1+par2;
}
int main()
{
    __block int a = 10;
    int b = 20;
    void(^blk)() = ^void (){
        add(a,b);
    };
    a = 30;
    blk();
    return 0;
}

实际上上面的这样需求十分常见,想要在Block中修改变量在Block外面也能使用。这是一个传值的利器,我们来看看编译器做了什么。同样使用clang -rewrite-objc filename:

struct __block_impl {
  void *isa;
  int Flags;
  int Reserved;
  void *FuncPtr;
};
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int b;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, __Block_byref_a_0 *_a, int flags=0) : b(_b), a(_a->__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_a_0 *a = __cself->a; // bound by ref
  int b = __cself->b; // bound by copy

  add((a->__forwarding->a),b);
 }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
 int b = 20;

 void(*blk)() = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b, (__Block_byref_a_0 *)&a, 570425344));
 (a.__forwarding->a) = 30;
 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);

 return 0;
}

仅仅只是加了一个__block就导致代码有很大的增加,具体来说就是原来的int a变成了一个__Block_byref_a_0。这样就可以把时间变量放入结构体中从而达到修改可以保留的目的。

Block存储域

回顾一下刚刚写的内容,block被转换为一个结构体局部变量,__block 修饰的临时变量也被转换为一个结构体局部变量。Block同时也是一个objective-c对象,它的类型为_NSConcreteStackBlock。但我们还不知道_NSConcreteStackBlock是什么,实际上通过查看源码我们知道类似的类型还有:

_NSConcreteStackBlock _NSConcreteGlobalBlock _NSConcreteMallocBlock

通过单词中的stack、global、malloc,我们可以猜测:

block类 存储位置
_NSConcreteStackBlock
_NSConcreteGlobalBlock 程序数据区
_NSConcreteMallocBlock

我们观察下面的代码:

void print(){}

void(^blk)() = ^void (){print();};

int main()
{

    blk();
    return 0;
}

转换后的代码为:

struct __blk_block_impl_0 {
  struct __block_impl impl;
  struct __blk_block_desc_0* Desc;
  __blk_block_impl_0(void *fp, struct __blk_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteGlobalBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static void __blk_block_func_0(struct __blk_block_impl_0 *__cself) {
print();}

static struct __blk_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __blk_block_desc_0_DATA = { 0, sizeof(struct __blk_block_impl_0)};
static __blk_block_impl_0 __global_blk_block_impl_0((void *)__blk_block_func_0, &__blk_block_desc_0_DATA);
void(*blk)() = ((void (*)())&__global_blk_block_impl_0);

int main()
{

 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
 return 0;
}

从上面看到通过什么全局变量blk,转换后的Block对象类型是_NSConcreteGlobalBlock ,并用static声明表示存储在数据区。不存在捕获变量的问题。

那么现在还有一个_NSConcreteMallocBlock悬而未决,我们不着急用clang -rewrite-objc filename 生产一个栗子。我们先考虑一个问题:局部的Block在超出其作用域后会如何

类型为_NSConcreteGlobalBlock的block,也就是全局Block变量,在定义后的任何地方都可以使用。但案例栈上的Block,如果其所属的变量作用域结束,该Block也就被废弃了。同样由__block修饰的变量在超出作用域的时候也会被废弃.

如果想在超出Block定义范围以外的地方使用Block就必须先把Block赋值到堆上,再把指针传过去供别人使用。回忆一下,一般是函数参数,函数返回值,赋值类的Block变量,这些地方是符合超出Block定义范围以外的地方使用Block。

那为题来了,我们如何把一个Block赋值的推上呢?复杂吗?平时使用他没有感觉到赋值到堆上的操作呀。这是因为一切都是自动的,或者手动很简单就赋值好了。

原因是因为在大多数情况下(函数参数,函数返回值,赋值类的Block变量等),编译器会判断我们很可能在作用域以外的地方还要使用这个Block,因此自动的帮我们赋值到堆上去了,并且把指针传出。

很遗憾没能获得中间自动转换后的代码

但一个block从栈上复制到堆上后,使用__block修饰的变量也会发生变化。变量会复制到堆上并被Block持有。

现在可以讨论一下刚刚遇到的由__block修饰的变量生成的结构体。

struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

回顾之前代码,发生所有访问a的地方都是通过__forwarding指针进行,并且这个指针指向自己。之所有这么设计是因为当 _block修饰的变量复制到堆上后,要保证栈上的变量和堆上的变量指向同一个地方。这时候__forwarding就起作用了。__block修饰的变量在赋值到堆上时,大概的步骤是:

malloc 创建内存区 memcopy赋值 修改原对象的__forwarding指针指向目标对象

为了验证,我们可以修改之前代码,加上copy让Block赋值到堆上:

void add(int par1,int par2){
   int tmp = par1+par2;
}
int main()
{
    __block int a = 10;
    int b = 20;
    void(^blk)() = [^void (){
        add(a,b);
    } copy];
    a = 30;
    blk();
    return 0;
}

转换后:

void add(int par1,int par2){
   int tmp = par1+par2;
}
struct __Block_byref_a_0 {
  void *__isa;
__Block_byref_a_0 *__forwarding;
 int __flags;
 int __size;
 int a;
};

struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int b;
  __Block_byref_a_0 *a; // by ref
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _b, __Block_byref_a_0 *_a, int flags=0) : b(_b), a(_a->__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_a_0 *a = __cself->a; // bound by ref
  int b = __cself->b; // bound by copy

  add((a->__forwarding->a),b);
 }
static void __main_block_copy_0(struct __main_block_impl_0*dst, struct __main_block_impl_0*src) {_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __main_block_dispose_0(struct __main_block_impl_0*src) {_Block_object_dispose((void*)src->a, 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_a_0 a = {(void*)0,(__Block_byref_a_0 *)&a, 0, sizeof(__Block_byref_a_0), 10};
 int b = 20;
 void(*blk)() = (void (*)())((id (*)(id, SEL, ...))(void *)objc_msgSend)((id)((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, b, (__Block_byref_a_0 *)&a, 570425344)), sel_registerName("copy"));
 (a.__forwarding->a) = 30;
 ((void (*)(__block_impl *))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk);
 return 0;
}

其中__main_block_copy_0会被Block对象在调用copy时调用。真正起作用的是_Block_object_assign((void*)&dst->a, (void*)src->a, 8);,代码很直白如下:

/*
 * When Blocks or Block_byrefs hold objects then their copy routine helpers use this entry point
 * to do the assignment.
 */
void _Block_object_assign(void *destAddr, const void *object, const int flags) {
    //printf("_Block_object_assign(*%p, %p, %x)\n", destAddr, object, flags);
    if ((flags & BLOCK_BYREF_CALLER) == BLOCK_BYREF_CALLER) {
        if ((flags & BLOCK_FIELD_IS_WEAK) == BLOCK_FIELD_IS_WEAK) {
            _Block_assign_weak(object, destAddr);
        }
        else {
            // do *not* retain or *copy* __block variables whatever they are
            _Block_assign((void *)object, destAddr);
        }
    }
    else if ((flags & BLOCK_FIELD_IS_BYREF) == BLOCK_FIELD_IS_BYREF)  {
        // copying a __block reference from the stack Block to the heap
        // flags will indicate if it holds a __weak reference and needs a special isa
        _Block_byref_assign_copy(destAddr, object, flags);
    }
    // (this test must be before next one)
    else if ((flags & BLOCK_FIELD_IS_BLOCK) == BLOCK_FIELD_IS_BLOCK) {
        // copying a Block declared variable from the stack Block to the heap
        _Block_assign(_Block_copy_internal(object, flags), destAddr);
    }
    // (this test must be after previous one)
    else if ((flags & BLOCK_FIELD_IS_OBJECT) == BLOCK_FIELD_IS_OBJECT) {
        //printf("retaining object at %p\n", object);
        _Block_retain_object(object);
        //printf("done retaining object at %p\n", object);
        _Block_assign((void *)object, destAddr);
    }
}

这里说明一下,声明时候Block会被复制到堆上:

调用Block的copy实例方法 Block作为函数的返回值时 将Block赋值给带有__strong修饰符修饰的(id类型或Block类型)成员变量时
点击复制链接 与好友分享!回本站首页
上一篇:从0开始学习 GitHub 系列之「05.Git 进阶」
下一篇:使用多张图片做帧动画的性能优化
相关文章
图文推荐
点击排行

关于我们 | 联系我们 | 广告服务 | 投资合作 | 版权申明 | 在线帮助 | 网站地图 | 作品发布 | Vip技术培训 | 举报中心

版权所有: 红黑联盟--致力于做实用的IT技术学习网站