Blocks深入理解和详解

介绍

Block是C级语法和运行时特性。它们类似于标准C函数,但是除了可执行代码之外,它们还可能包含对自动(堆栈)或托管(堆)内存的变量绑定。因此,Block可以维护一组状态(数据),它可以用来在执行时影响行为。

您可以使用Blocks来组合函数表达式,这些表达式可以被传递给API,可选地存储,并由多个线程使用。Block对于回调来说特别有用,因为块包含在回调上执行的代码和执行过程中需要的数据。

可以在GCC和Clang中使用OS X v10.6 Xcode开发工具。您可以使用OS X v10.6和之后的模块,以及随后的iOS 4.0。Block运行时是开源的,可以在LLVM的编译器-rt子项目存储库中找到。block也被提交给C标准工作组作为N1370:苹果对C的扩展,因为Objective-C和c++都是从C派生出来的,block被设计用于与所有三种语言(以及Objective-C++)一起工作。语法反映了这个目标。

概念普及

  • Block本质是Objective-C的对象,不是函数指针(这个有点混淆,网上普遍都说是函数指针)
  • Block类型变量本质是函数指针(如下声明其实在编译成C语言源码的时候就是一个函数指针)
  • 截获自动变量 所有的变量都会截获吗?比如全局变量、静态变量。其实截获的只是局部变量而已
  • __block变量加了它为啥就能被修改了?很多人也许会说是传入了地址,这种说法很片面的,如果是这样的其实完全不用加__block,苹果帮你做了就行了呀。其实__block之后翻译成C语言源码之后,变量会变成一个对象,该对象存储了原来变量的地址,函数传入的是一个对象。

探寻Block本质

通过LLVM的编译器-rt子项目存储库中找到runtime下的Block源码

struct Block_layout {
    void *isa;
    int flags;
    int reserved;
    void (*invoke)(void *, ...);
    struct Block_descriptor *descriptor;
    /* Imported variables. */
};

当我们声明一个Block的时候,编译器其实会将block转换成以上struct结构体。
其中isa指向的是Block具体的类。有如下6中,不过其中StackBlockMallocBlockGlobalBlock是比较常见的

/* the raw data space for runtime classes for blocks */
/* class+meta used for stack, malloc, and collectable based blocks */
BLOCK_EXPORT void * _NSConcreteStackBlock[32];
BLOCK_EXPORT void * _NSConcreteMallocBlock[32];
BLOCK_EXPORT void * _NSConcreteAutoBlock[32];
BLOCK_EXPORT void * _NSConcreteFinalizingBlock[32];
BLOCK_EXPORT void * _NSConcreteGlobalBlock[32];
BLOCK_EXPORT void * _NSConcreteWeakBlockVariable[32];

invoke函数指针则是对应Objective-C中代码的具体实现

看到这里不知大家有没有想到runtime中objc_object的isa呢?
其实两个原理是一样的。这里就不具体介绍objc_object

所以Block即为Objective-C的对象

Block类型变量

Block的类型变量声明如下:

int (^blk) (int);

我们声明一个Block类型变量并且赋值

int (^blk) (int) = ^(int count){return count+1};

然后我们通过如下

xcrun -sdk iphonesimulator11.2 clang -rewrite-objc -F /Applications/Xcode\ 2.app/Contents/Developer/Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS11.2.sdk/System/Library/Frameworks ViewController.m

我们可以得到以下源码:

//对应的Block具体struct结构体
struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int flags=0) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
//Block方法的具体实现
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {

        return count+1;
    }

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA));
    ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
}

其中int (*blk) (int)则就是对应我们的Block类型变量,这里就就可以看到了Block类型变量的本质了,其实就是C语言的函数指针

截获自动变量

说截获自动变量之前我们先看以下代码

int tmp = 2;
int (^blk) (int) = ^(int count){
    return count+tmp;
};
tmp = 3;
int result = blk(2);
NSLog(@"%d",result);

以上代码会打印出多少呢?5还是4??
正确答案是4

为什么是4呢?其实就是因为Block截获自动变量的原因

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  int tmp;
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, int _tmp, int flags=0) : tmp(_tmp) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {
  int tmp = __cself->tmp; // bound by copy

        return count+tmp;
    }

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0)};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    int tmp = 2;
    int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, tmp));
    tmp = 3;
    int result = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6n_mdf6rn0d5f5cw6r1h2620h3w0000gn_T_ViewController_b80e84_mi_0,result);
}`__ViewController__viewDidLoad_block_impl_0`

通过以下代码可以看出,当我们在给Block类型变量赋值的时候,tmp变量同时被传入,并且被保存到了__ViewController__viewDidLoad_block_impl_0的struct中。这时候其实就是截获了自动变量,由于已经在struct类中保存了一份,即使后边更改,也不会影响Block截获的值。

int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, tmp));

为什么要对局部变量进行截获呢?而全局变量和静态变量不需要截获,并且修改的的时候也不需要加__block呢?

主要原因就是变量的生命周期。局部变量在代码块执行结束之后就会被释放,但是Block不一定在此时释放。所以就会出现变量超过生命周期的现象,此时对局部变量进行截获,即使局部变量被释放,但是Block同样还是可以正常使用的。因为全局变量和静态变量的释放时间肯定不会在Block之前,所以不必对他们进行截获。

全局变量和静态变量存储在全局数据区;局部变量存储在栈中

__block变量存储域探究

不知道大家有没有想过一个问题,为什么需要__block呢?如果没有__block难道就修改不了变量了吗?

我们先看一下加了__block编译器给我们做了啥

struct __Block_byref_tmpBlock_0 {
  void *__isa;
__Block_byref_tmpBlock_0 *__forwarding;
 int __flags;
 int __size;
 int tmpBlock;
};

struct __ViewController__viewDidLoad_block_impl_0 {
  struct __block_impl impl;
  struct __ViewController__viewDidLoad_block_desc_0* Desc;
  __Block_byref_tmpBlock_0 *tmpBlock; // by ref
  __ViewController__viewDidLoad_block_impl_0(void *fp, struct __ViewController__viewDidLoad_block_desc_0 *desc, __Block_byref_tmpBlock_0 *_tmpBlock, int flags=0) : tmpBlock(_tmpBlock->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};
static int __ViewController__viewDidLoad_block_func_0(struct __ViewController__viewDidLoad_block_impl_0 *__cself, int count) {
  __Block_byref_tmpBlock_0 *tmpBlock = __cself->tmpBlock; // bound by ref

        (tmpBlock->__forwarding->tmpBlock) = 100;
        return count+(tmpBlock->__forwarding->tmpBlock);
    }
static void __ViewController__viewDidLoad_block_copy_0(struct __ViewController__viewDidLoad_block_impl_0*dst, struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_assign((void*)&dst->tmpBlock, (void*)src->tmpBlock, 8/*BLOCK_FIELD_IS_BYREF*/);}

static void __ViewController__viewDidLoad_block_dispose_0(struct __ViewController__viewDidLoad_block_impl_0*src) {_Block_object_dispose((void*)src->tmpBlock, 8/*BLOCK_FIELD_IS_BYREF*/);}

static struct __ViewController__viewDidLoad_block_desc_0 {
  size_t reserved;
  size_t Block_size;
  void (*copy)(struct __ViewController__viewDidLoad_block_impl_0*, struct __ViewController__viewDidLoad_block_impl_0*);
  void (*dispose)(struct __ViewController__viewDidLoad_block_impl_0*);
} __ViewController__viewDidLoad_block_desc_0_DATA = { 0, sizeof(struct __ViewController__viewDidLoad_block_impl_0), __ViewController__viewDidLoad_block_copy_0, __ViewController__viewDidLoad_block_dispose_0};

static void _I_ViewController_viewDidLoad(ViewController * self, SEL _cmd) {
    ((void (*)(__rw_objc_super *, SEL))(void *)objc_msgSendSuper)((__rw_objc_super){(id)self, (id)class_getSuperclass(objc_getClass("ViewController"))}, sel_registerName("viewDidLoad"));
    __attribute__((__blocks__(byref))) __Block_byref_tmpBlock_0 tmpBlock = {(void*)0,(__Block_byref_tmpBlock_0 *)&tmpBlock, 0, sizeof(__Block_byref_tmpBlock_0), 2};
    int (*blk) (int) = ((int (*)(int))&__ViewController__viewDidLoad_block_impl_0((void *)__ViewController__viewDidLoad_block_func_0, &__ViewController__viewDidLoad_block_desc_0_DATA, (__Block_byref_tmpBlock_0 *)&tmpBlock, 570425344));
    int result = ((int (*)(__block_impl *, int))((__block_impl *)blk)->FuncPtr)((__block_impl *)blk, 2);
    NSLog((NSString *)&__NSConstantStringImpl__var_folders_6n_mdf6rn0d5f5cw6r1h2620h3w0000gn_T_ViewController_a1c583_mi_0,result);
}

这时候大家可以与前边没有加__block的代码进行比较,两者的差别在哪里。
其实大家应该很容易发现加了__block之后,变量形成了一个struct,这个struct中保存了变量的值,同时还有一个__forwarding。这个__forwarding其实就是为什么需要__block的关键。

Block从栈复制到堆的时候,__block变量也会受到影响。如下:

__block变量的配置存储域 Block从栈到堆时的影响
从栈复制到堆并被Block持有
被Block持有

ARC下,Block如果是栈的话,默认会copy到堆上。此时所使用的__block变量同时也会从栈被复制到堆上如下图


存储

那如果Block在堆上了,我们在Block中修改了变量,怎么让栈上的变量也同时能正确访问呢?这其实就是__forwarding功劳了。

__block变量初始化的时候__forwarding是指向本身自己的。当__block变量从栈复制到堆上的时候,此时会将__forwarding的值替换为复制到目标堆上的__block变量用结构体实例的地址。如下图:

image

通过此功能,无论是在Block语法中、Block语法外使用__block变量,还是__block变量配置在栈上或堆上,都可以顺利地访问一个__block变量。
到这里大家应该明白了"__block加了之后,是把变量的地址传入Block"的说法是很片面的吧啦

我的博客

FlyOceanFish

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

推荐阅读更多精彩内容