Block内容介绍

Block

前前后后看了4、5遍《Objective-C高级编程》的Block模块,对Block相关的内容有一定的理解,为了方便深入理解Block,结合书中的内容按照个人的理解逻辑对Block相关模块进行梳理,对Block的本质和Block截获变量两个模块内容进行深入解析。希望通过阅读本文章能够清楚的了解Block的原理及正确使用Block。

一、Block的本质

1.1 Block结构说明

1.1.1 含Block代码段

下面代码包含设置block并使用。

#include <stdio.h>

int main() {

   int a = 10;

   void(^blk)(void) = ^{

       printf("%d", a);

   };

   blk();

   return 0;

}

1.1.2 LLVM编译后的代码

通过clang -rewrite-objc +文件名查看转化后的内容,去除include相关内容。

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;

  __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) {


       printf("123");

   }

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() {

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

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

   return 0;

}

1.1.3 代码段介绍

本小节中,将前文所涉及的代码部分一一介绍,以了解编译器是如何将Block编译成C++语言,进而对Block有更深入的理解。

__main_block_impl_0结构体:

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;

  }

};​

1. block名称。__main_block_impl_0表示main函数的第一个block,如果main函数中有多个block编译器会以最后的数字进行逐字增加。

2. __main_block_impl_0的初始化。初始化无自动变量的Block,需要传入block所定义的函数、block的描述文件(block的大小)、及可选项flag。通过函数、flags及系统判定的block类别设置__block_impl结构体。

3. 该代码不存在自动变量所以结构体中没有关于变量相关内容,而block持有变量会在第二章节中详细说明。现阶段只需要知道如果存在变量,在初始化阶段也需要将变量传入并设置相关的值,对应的结构体也会出现变化。

__block_impl结构体:

struct __block_impl {

  void *isa;

  int Flags;

  int Reserved;

  void *FuncPtr;

};

1. __block_impl结构体,可以理解为block结构体的基类,任何block会有__block_impl结构体。

__main_block_desc_0结构体:

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的方法:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

    printf("123");

}

1. 函数名。__main_block_func_0代表main函数中block的第一个函数。命名规则类似block。

main函数:

int main() {​

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

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

   return 0;

}​

1. __main_block_impl_0的初始化通过__main_block_func_0+__main_block_desc_0_DATA。

2. 通过blk.FuncPtr调用传入的方法。

1.1.4 总结

从上一小节中我们可以知道iOS的block通过编译器在编辑阶段将block编译成对应的结构体,将对应函数及参数在block初始化阶段传入,使block持有函数及相关的变量。

1.2 Block本质

通过block的初始化函数及block的结构体会发现在初始化block时,系统会自动填充impl.isa指针的值,在上文的例子中impl.isa = &_ NSConcreteStateBlock。对runtime熟悉的小伙伴应该对isa指针不陌生 实例对象的isa指针指向父类,父类的isa指针指向元类,元类的isa指向NSObject。而block就是实例对象,它的isa指针指向了_NSConcreteStateBlock,而 _NSConcreteStateBlock的isa指向了其元类最终指向了NSObject,所以block的实质就是对象。

1.2.1 block类型

1.1节例子中block类型为_NSConcreteStateBlock,实际上block一个有3种类型:

_NSConcreteStateBlock: block存在栈中。

_NSConcreteMallocBlock: block存在堆中。

_NSConcreteGlobalBlock: block存在在data数据段中。

block的类型由编译器决定,编译器会根据block的作用域分配block类型。block是全局变量设置如下所示,其类型就为_NSConcreteGlobalBlock。

#include <stdio.h>

void(^blk)(void) = ^{

   printf("122");

};

int main() {

   blk();

   return 0;

}

_NSConcreteMallocBlock类型的block需要手动对栈类型block进行copy才会修改为堆block,具体堆block的用户及与栈block的关系会在下一节变量的自动截获中有详细的说明。

二、Block截获自动变量

本节将介绍block是如何持有变量,对上面代码进行修改。

#include <stdio.h>

int main() {

   int a = 10;

   void(^blk)(void) = ^{

       printf("%d", a);

   };

   blk();

   return 0;

}

2.1 截获自动变量

根据本届开头的代码段进行clang -rewrite-objc+文件名的操作得到跟1.1.2中类似的部分,不同的内容如下所示:

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

  }

};​

1. 结构体包含a。局部变量a会追加成员变量到block的结构体中,未使用的变量不会被追加到结构体中。

2. 初始化函数。初始化函数增加了int参数。

__main_block_func_0方法:

static void __main_block_func_0(struct __main_block_impl_0 *__cself) {

  int a = __cself->a; // bound by copy

  printf("%d", a);

}

1. 取值。通过__main_block_impl_0结构体对象获取结构体中的值。

2. bound by copy。编译器自带的注释“// bound by copy”表示block对它引用的局部变量做了只读拷贝,也就是说block引用的是局部变量的副本。因此获取的自动变量值无法在block进行修改,且外部改变变量的值也不会改变block中变量的值。

2.2 __block修饰的变量

    2.1节中介绍了block截获局部变量的原理,然而其对引用的局部变量做的是只读拷贝,因此无法修改截获的局部变量值。为了能够修改截获的自动变量,可以使用__block修饰符修饰变量。

2.2.1 __block导致block结构体变化

main函数修改为:

#include <stdio.h>

int main() {

   __block int a = 10;

   void(^blk)(void) = ^{

       printf("%d", a);

   };

   blk();

   return 0;

}

通过clang编译,再来看看block结构体。

__main_block_impl_0结构体:

struct __main_block_impl_0 {

  struct __block_impl impl;

  struct __main_block_desc_0* Desc;

  __Block_byref_a_0 *a; // by ref

  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, __Block_byref_a_0 *_a, int flags=0) : a(_a->__forwarding) {

   impl.isa = &_NSConcreteStackBlock;

   impl.Flags = flags;

   impl.FuncPtr = fp;

   Desc = desc;

  }

};

1. __Block_byref_a_0。变量a增加了__block修饰符变为__Block_byref_a_0,关于结构体内容之后会介绍。

2. 初始化函数。在设置a的值是将a.forwording赋值给block的成员变量a。思考如此设计的原因。

__Block_byref_a_0 结构体:

struct __Block_byref_a_0 {

  void *__isa;

__Block_byref_a_0 *__forwarding;

int __flags;

int __size;

int a;

};​

1. *__forwarding。指向自己的指针,通过__Block_byref_a_0->__forwarding->a的方式获取a的值。

__main_block_desc_0结构体:

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

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*/);}

1. __main_block_desc_0。__main_block_desc_0增加了copy和dispose方法。

2. __main_block_copy_0。_Block_object_assign将对象赋值给对象结构体的成员变量中,相当于retain操作。

3. __main_block_dispose_0。_Block_object_dispose释放赋值在结构体的成员变量,相当于release操作。

4. 通过查看main函数,并没有主动调用copy和dispose函数

copy的调用时机:栈上的block复制到堆上。

1. 通过调用block的copy实例方法。

2. block作为返回值。

3. 将block赋值给__strong修饰的block类型的变量。

dispose的调用时机:堆上的block释放。

1. 没有对象持有block调用dispose。

2.2.2 __Block_byref_a_0结构体设计

    本节将介绍__Block_byref_a_0结构体中的 _ _forwarding指向自己的指针设计的意义。通过copy可以变量复制到堆上,正常情况下栈上的变量修改不会对堆上的对象有影响,然而这对于使用 _ _block修饰的变量而言是个灾难,也背离了 _ _block设计的初衷,所以引入 _ _forwarding,在变量复制到堆上其指针指向堆上的地址。

如下图所示:

三、更多

3.1 Block的循环引用

在block中使用__strong修饰的对象,当block从栈复制到堆,该对象就会被block所持有,就会造成循环引用。

伪代码:

class V: ViewController {

   var i: Int = 0

   override func viewDidLoad() {

       let block = {

           print("输出\(self.i)")

       }

       block.copy()

       block()

}

   deinit() {

       print("deinit")

   }

}

通过对block的拷贝,堆上block持有self对象,而self又持有block,所以造成了循环引用。使用weak修饰符修饰self,使block不持有self即可。

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

推荐阅读更多精彩内容

  • 摘要block是2010年WWDC苹果为Objective-C提供的一个新特性,它为我们开发提供了便利,比如GCD...
    西门吹雪123阅读 907评论 0 4
  • Blocks Blocks Blocks 是带有局部变量的匿名函数 截取自动变量值 int main(){ ...
    南京小伙阅读 916评论 1 3
  • Block概要 Block:带有自动变量的匿名函数。 匿名函数:没有函数名的函数,一对{}包裹的内容是匿名函数的作...
    zweic阅读 502评论 0 2
  • block写法: 普通写法: int (^blk)(int) = ^(int count) { retur...
    一川烟草i蓑衣阅读 311评论 0 0
  • 日精进打卡,姓名:孙维洁六项精进第364期乐2学员,403期乐2志工,456期乐2志工,496服务和带队志工,50...
    孙维洁阅读 242评论 0 0