让我们扒一扒block的裤子

block不管是在我们开发中,还是面试中,都是高频出现的,如果我们不跳出来看它,而是沉浸在API或者单纯的注意一些问题,是很难彻底理解他的。

首先,block是什么?
block本质上就是一个对象,里边封装了函数调用以及函数调用的环境,通俗的理解,block中的所有代码,就是一个函数执行的过程,只不过在执行过程中,会有一些额外需要我们注意的点,这些点就是函数调用环境。

static NSString * b = @"10";
  void(^block)(void) = ^{
       b = @"20";
  };
  block();
基本的一些概念就过了

在上边的block执行过程中,函数调用可以理解就一句代码,给静态变量附一个新的值;另外一部分,函数调用环境是怎么理解的呢,通俗点的讲,就是怎么维护和使用外部的这些变量(b),block是一个独立的代码块,可以在任何时候执行,假如函数调用完了,调用栈都收回了,block怎么办,block引用的外部变量怎么办。解决了这些问题,就等于实现了block,彻底理解了block。

下边是block的cpp(^ ^)代码,容易理解一些

这就是上边的block转换成c++的代码
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  NSString **b;
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, NSString **_b, int flags=0) : b(_b) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

下边是创建这个对象的代码
 void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA, &b, 570425344));

精简一下(伪代码)
block= __main_block_impl_0(__main_block_func_0, __main_block_desc_0_DATA, &b, 570425344));
传入了一个函数指针,和一个结构体指针,来创建上边的block对象,函数指针就是block要调用的函数,__main_block_desc_0_DATA结构体对象,根据不同的外部变量,对block有不同的初始化

block就是这么一个东西,然后就是考虑函数执行环境,也就是外部变量和自己保存的问题了。
对于外部变量,分为三种,静态变量、全局变量、auto变量(函数内的局部变量)。

那我们从设计的角度来看:我要使用外部变量,还要在不同的时候使用(包括当前函数调用栈销毁的时候),那我怎么使用这三种变量呢,直接给结论
1.全局变量,存在于Mach-o文件的_DATA区,通常理解的gloab区,他是不会随着函数栈销毁而销毁的,所以我们在程序任意地方可以随时拿来使用,那么对待这种变量,不需要做任何处理

2.static 修饰的静态变量,也是贯穿程序的一种变量,但是static有局部静态变量和全局静态变量,在用这种变量的时候,是采用指针传递的方式,上边的例子就是static的形式,我们可以看到,在block中有一个指向指针的指针变量,这个地方留一个问题后边回答,就是block改变外部引用变量的值问题,涉及到__block修饰问题。

3.还有就是普通的auto变量,这种直接就是值传递,什么叫值传递,就是在传递的时候,直接将值拷贝一份,传入到里边,如果我们使用外部的 int a类型的变量,就会拷贝一份a的副本在block中,如果是对象类型的变量,就会拷贝一份他的指针放到block中,这个指针的指向和外部指针的指向是同一块内存区域

完成上述操作,我们就可以在任何时候使用外部传入的变量,在block中这叫做捕获操作。

捕获以后是不是还有其他的问题,我们拷贝一份副本放到block中,我在block中改变这个副本,那么外部变量根本不会变啊,给写代码的人造成困扰。
这个时候__block上场,还是讲原理。


全局变量:不需要任何操作,因为我们访问和操作的时候都是同一个变量
static静态变量:我们接收到了传递进来的指针变量,那么可以直接通过 * p = value的方式,直接改变指针对象的指向。
auto变量:值传递是不行的,所以也要改成指针传递的方式,__block帮我们做了这个操作,通过__block修饰的对象,会生成一个新的对象,这个新的对象会对之前的变量进行引用和操作,我们的block会保存这个新生成对象的指针。有点绕。看代码:



通过__block修饰后新生成的对象
struct __Block_byref_age_0 {
  void *__isa;
__Block_byref_age_0 *__forwarding;
 int __flags;
 int __size;
 int age;
};

我们自己的block
struct __main_block_impl_0 {
  struct __block_impl impl;
  struct __main_block_desc_0* Desc;
  int no;
  NSObject *__weak weakObject;
  __Block_byref_age_0 *age; // by ref  通过指针的方式
  __main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int _no, NSObject *__weak _weakObject, __Block_byref_age_0 *_age, int flags=0) : no(_no), weakObject(_weakObject), age(_age->__forwarding) {
    impl.isa = &_NSConcreteStackBlock;
    impl.Flags = flags;
    impl.FuncPtr = fp;
    Desc = desc;
  }
};

其实还是要跳出来看,__block解决的问题其实就是不同指针变量的问题,之前的时候,值捕获,将一个指针变量捕获进来(也就是生成了一个新的指针变量),那么改变这个新的指针变量的指向,与外部无关,现在,我们统一操作__block新生成的对象,通过他来统一改变值,(block外部age再次进行赋值,起始也是操作的这个新生成的对象)。


以上解释都是外部变量的问题,其实还有block自己的问题,当函数调用栈销毁了,block如果也是在这个栈空间,也就被销毁了,所以引出了
NSGlobalBlock
NSStackBlock
NSMallocBlock
这三种block

在MRC时代,区分这三种block,就看有没有引用auto变量和有没有copy操作,如果引用了auto变量,就在stack上,如果stack的block用copy修饰或者进行了copy操作,那么就会到了堆上,还是和引用变量那么理解,这个block,如果我们想让他在以后执行,那么就要把它保存下来,不能调用的时候已经被销毁了。
ARC环境下,系统又做了很多优化,我们将block赋值给一个block的时候、只要引用auto变量、都会被拷贝到堆上

* 作为函数返回值的时候
* 赋值给__strong 指针的时候。(并且访问auto对象)
* block作为Cocoa API 方法参数的时候,含有usingBlock的方法参数。
 NSArray * arr;
    [arr enumerateObjectsUsingBlock:^(id  _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
    }];
* block作为GCD参数的时候。

自己在哪的问题解决了,还有内存管理的问题,这也是面试经常问到的问题,从上述原理上来说:

iOS中,内存管理都是通过引用计数来管理的,在上述讲过的问题中,我们列举一下,哪些对象的内存管理是需要我们了解。
aoto变量的内存管理
__block修饰后生成的新的对象的内存管理

先说结论,block中的内存管理是通过

__main_block_copy_0
__main_block_dispose_0
这两个函数操作的

栈上的block不需要也不会对变量进行强引用,只有block被拷贝到堆上才会。

  1. 先看auto变量,我们可以通过__weak, __strong 修饰,来告诉block,是否对这个变量进行强引用(也就是retainCount + 1),默认是强引用,然后等block销毁,对外部对象也release一次,这样对象正常释放,但是如果有循环引用的情况,可能就需要__weak修饰了(但是也要注意block嵌套的问题,这个放到最后)。

2.通过__block修饰的对象,我们的block对他都是强引用,然后新生成的对象又对之前的auto变量进行内存管理,管理的原则同第一条。

其他:

1.__forwarding指针的问题,通过__block修饰生成的新的对象,里边会有一个这样的指针指向自己,原因就是,block在拷贝的时候,__block修饰对象,也会被拷贝到堆上,这时候__forwarding指针指向的就是堆上的值,一般取值过程_age - > __forwarding - > age
2 block嵌套问题,防止变量比较早的销毁,需要在block内部使用__strong,对变量强引用,等内部大括号执行完,再release一次,下边是实例代码

- (void)doBlock{
    Model *model = [Model new];
   
    __weak typeof(self) weakSelf = self;
    model.dodoBlock = ^(NSString *title) {

        __strong typeof(self) strongSelf = weakSelf;
        strongSelf.text = title;   
    };
    
    self.model = model;
}

其他细节不断补充...

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