block系列2-block的转C++实现

1.block的转C++实现

(1).栈区block示例

我们通过clang指令将我们的OCblock代码转为.cpp观察如何实现这一匿名函数。

clang -rewrite-objc test.m
#import <Foundation/Foundation.h>

int main(){
    int a = 0;
    int (^blk)(int) = ^(int b){return a+b;};
    a++;
    int c = blk(5);
    return 0;
}

执行命令后得到test.cpp文件,拉到底部查看main

int main(){
    int a = 0;
    int (*blk)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));
    a++;
    int c = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 5);
    return 0;
}

我们看到blk被定义为一个函数实现指针,指向一个__main_block_impl_0结构体或者说对象地址,参数为(void *,&data,a)这里可以发现传入了a,即传入了一个复制的临时变量,但是这个临时变量是readonly的,不可赋值,尝试赋值编译器会报错。

查看__main_block_impl_0

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;
  }
};

显然这就是block块的实现对象了,我们知道C++中struct与class都可以定义对象,区别在于默认属性(public还是private的不同),

里面包含一个对象impl和一个静态对象指针Desc,一个int a变量。这里的int a与main中的无关,只是同名变量而已,在下面的__ main_block_impl_0构造函数块实现中,用初始化列表对block块中的a进行初始化,从main传参来看即main中的a初始化block块内的a变量。

构造函数还有一个fp指针和flags初始化 __ main_block_impl_0对象内impl对象的FuncPtr和Flags,对象指针desc初始化 __main_block_impl_0对象的Desc对象指针。

查看__ block_impl和__main_block_desc_0

struct __block_impl {
  void *isa;   //对象的ISA指针
  int Flags;   //相关标志位,包含引用计数值,上篇文章有
  int Reserved;
  void *FuncPtr; //匿名函数实现地址
};

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实现的块大小结构体

还差一个__main_block_func_0

static int __main_block_func_0(struct __main_block_impl_0 *__cself, int b) {
  int a = __cself->a; // bound by copy
return a+b;}

这下就很清晰了。

<1>定义block块时

int (*blk)(int) = ((int (*)(int))&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, a));

定义block块具体流程如下

  1. 定义用以表示block块的 __ main_block_impl_0对象及构造函数

    struct __main_block_impl_0 {
      struct __block_impl impl;    //ISA指针、是否global、引用计数、函数实现地址
      struct __main_block_desc_0* Desc; //指向记录块大小的静态结构体指针
      int a;   //copy自外部变量的a
      __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _a, int flags=0) : a(_a) {   //初始化impl结构体和Desc指针
        impl.isa = &_NSConcreteStackBlock;
        impl.Flags = flags;
        impl.FuncPtr = fp;
        Desc = desc;
      }
    };
    
  2. 传参构造用以表示block块的 __ main_block_impl_0对象,并取得对象地址给blk

    int (*blk)(int) = ((int (*)(int))&__main_block_impl_0((void *)main_block_func_0/*匿名函数地址*/, &main_block_desc_0_DATA/*定义的静态结构体地址*/, a/*传参机制实现所谓的捕获外部变量*/));
    

这里我们已经通过对象构造函数传参构造出一个有具体实现的block对象,其地址赋值给了blk指针,值得注意的是我们没有使用new/alloc,显然这个对象存活在栈区。

<2>调用block块

int c = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 5);

流程非常清晰

通过blk->FuncPtr(blk,5)调用匿名函数,传入blk即block对象的self指针,5即匿名函数的参量

static int __main_block_func_0(struct __main_block_impl_0 *__cself, int b) {
  int a = __cself->a; // bound by copy
return a+b;}

我们可以看到,block匿名函数的整个构造和使用中,通过在block对象中定义一个同名变量,构造时传参赋值初始化实现所谓的截获自动变量,在实现函数中又定义了同名变量,同过传入的blk指针初始化使用。

<3>block内使用外部变量

如我们上边的代码所示,block对于自动变量的处理是通过传参复制了自动变量的值,并没有对自动变量进行操作。那我们想在block内部修改变量该怎么办?

  1. 全局变量和静态全局变量直接通过变量名使用
  2. 静态局部变量block捕获其地址,通过地址访问
  3. __block修饰符修饰的自动变量,将会构造一个结构体储存其地址与值。后面会讲到处理步骤。

(2).global的block对象

两种情况将会把block对象储存于程序的data区

  1. 配置在全局变量区域的block对象
  2. block表达式不使用截获的临时变量

(3).堆区的block对象

我们经常在函数内定义block对象,这种对象常常是存活于栈的block对象,作用域结束后block对象也消亡

那我们返回生成的block对象时,为了保持其生命周期,需要将其复制到堆上,ARC机制下将会调用_Block_copy(x);将其复制到堆区。

//
// The -fobjc-arc flag causes the compiler to issue calls to objc_{retain/release/autorelease/retain_block}
//

id objc_retainBlock(id x) {
    return (id)_Block_copy(x);
}

// Create a heap based copy of a Block or simply add a reference to an existing one.
// This must be paired with Block_release to recover memory, even when running
// under Objective-C Garbage Collection.
BLOCK_EXPORT void *_Block_copy(const void *aBlock)
    __OSX_AVAILABLE_STARTING(__MAC_10_6, __IPHONE_3_2);

ARC有效时,编译器在大多数情况下会判断进行栈区block对象到堆区block对象的复制,即调用_Block_copy(x);

唐巧在其博客谈Objective-C block的实现

在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock 和 NSConcreteMallocBlock 类型的 block。

首先OC高阶编程中提到的栈区BLOCK导致的异常例子(P114)编译与运行并未出现异常,也的确显示为NSConcreteMallocBlock。我们在方法内创建的带有截获自动变量的Block对象也是NSConcreteMallocBlock 类型,所以唐巧结论兴许是正确的,但是我没有在官方文档中找到说明,暂时做个标记。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,558评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,002评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,024评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,144评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,255评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,295评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,068评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,478评论 1 305
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,789评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,965评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,649评论 4 336
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,267评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,982评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,800评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,847评论 2 351

推荐阅读更多精彩内容