iOS 面试全方位剖析 -- Block篇


  • block 介绍
  • 截获变量
  • __block修饰符
  • Block的内存管理
  • Block的循环引用
  • 为什么 weakSelf 需要配合 strong self 使用

截获变量

先看一个问题

// 全局变量
int global_var = 4;
// 静态全局变量
static int static_global_var = 5;

- (void)method
{
     int multiplier = 6;
    int(^Block)(int) = ^int(int num)
    {

        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d", Block(2));
}


输出是什么? 
 如果 将 int multiplier 改为静态变量 static  int multiplier = 6, 结果又是什么?

带着问题往下看,有这几种类型的变量

  • 局部变量 -- 基本数据类型 , 对象类型 (对于基本数据类型的局部变量截获其值,对于对象类型的局部变量连同 所有权修饰符 一起截获)
  • 静态局部变量 ( 以指针形式截获局部静态变量 )
  • 全局变量 (不截获全局变量)
  • 静态全局变量 (不截获静态全局变量)

从这段代码来深入了解一下

int global_var = 4;
static int static_global_var = 5;

-(void)method1
{
    int var = 1;
    __unsafe_unretained id unsafe_obj = nil;
    __strong id strong_obj = nil;
    static int static_var = 3 ;
    void(^block)(void) = ^{
        
        NSLog(@"局部变量<基本数据类型> var %@",var);
        NSLog(@"局部变量<__unsafe_unretained 对象类型> var %@",unsafe_obj);
        NSLog(@"局部变量< __strong 对象类型> var %@",strong_obj);
        NSLog(@"静态变量 %d",static_var);
        NSLog(@"全局变量 %d",global_var);
        NSLog(@"静态全局变量 %d",global_var);
    }
    
}

使用 clang命令看一下编译后的源码 MCBlock.cpp
看这一段

  • 可以看到,局部变量截获的就是它的值
  • 静态局部变量以指针形式截取的
  • 对象类型的类型连同其修饰符一起截获,理解这个就能更好的理解 Block 循环引用的问题,后续会说
  • 全局和静态全局变量不截获

然后回到问题

int multiplier  = 6 ,block(2)输出的是 12,因为block执行的时候截获的是 6

static int multiplier  = 6 ,block(2) 输出的是8,因为截获以指针形式截获,所以获取到的  multiplier 是最新的值 4

__block修饰符

什么情况下需要用到 __block修饰符 呢?
对被截获变量进行赋值操作的时候 (区分 赋值 和使用)

看一些笔试题

  NSMutableArray *array = [NSMutableArray array];
    void(^block)(void) = ^{
        [array addObject:@123];
    };
    Block();

这里  对 array 只是一个使用,而不是赋值,所以不需要 _ _block 进行修饰

  NSMutableArray *array = nil;
    void(^block)(void) = ^{
            array = [NSMutableArray array];
    };
    Block();

这里就需要在array的声明处添加__block修饰符,不然编译器会报错

总结下,对变量进行赋值的时候,下面这些不需要__block修饰符

  • 静态局部变量
  • 全局变量
  • 静态全局变量
{
    __Block int multiplier = 6;
    int(^Block)(int) = ^int(int num)
    {

        return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d", Block(2));
}
//这里的结果就是 8 了

加了 __block 修饰之后,这个变量就变成了一个对象

在 multiplier 进行赋值的时候

__forwarding指向原来的对象, 通过 __forwarding 指针进行赋值,修改掉 multiplier 的值


Block的内存管理

Block类型

  • _NSConcreteGlobalBlock 全局
  • _NSConcreteStackBlock 栈类型
  • _NSConcreteMallocBlock 堆类型

看一下各个类型的Block在内存上面的分配


Block的copy操作


比如现在声明一个成员变量Block,而在栈上创建这个Block去赋值,如果没有对Block进行Copy操作的话,当我们通过成员变量去访问这个Block的时候,可能会因为栈对应的函数退出之后在内存当中就销毁掉了,继续访问就会引起内存崩溃


Block的循环引用

下面这段代码就会造成循环引用

  _array = [NSMutableArray arrayWithObject:@"block"];
    _strBlk = ^NSString*(NSString*num){
        return [NSString stringWithFormat:@"hello_%@",_array[0]];
    };
    _strBlk(@"hello");

self 持有 Block,而 Block 里有成员变量 array, 持有 self,所以就造成了循环引用,怎么解决呢?

  _array = [NSMutableArray arrayWithObject:@"block"];
  __weak NSArray *weakArray = _array;
    _strBlk = ^NSString*(NSString*num){
        return [NSString stringWithFormat:@"hello_%@",_array[0]];
    };
    _strBlk(@"hello");

为什么用_ _weak 修饰符解决循环引用? 这个其实在截获变量里有讲过,截获对象的时候会连同修饰符一起截获,在外部定义的如果是 _ _weak 修饰符,在 Block 里所产生的结构体里面所持有的成员变量也是 _ _weak 类型

再看一段代码,这样写有什么问题?

__block MCBlock*blockSelf = self;

    _blk = ^int(int num){
          //var = 2
              return num * blockSelf.var ;
    };
        _blk(3);

这样在 ARC 模式下是会产生循环引用,引起内存泄漏的



__block修饰后的指向是原来的对象,会造成循环引用
怎么解决呢,首先想到的当然是断开其中一个环

__block MCBlock*blockSelf = self;

    _blk = ^int(int num){
          //var = 2
      int result = num * blockSelf.var;
      blockSelf = nil;
       return result;
    };
        _blk(3);

在调用完 blockSelf 后将它置为nil,断开其中的一个环,就可以让内存得到释放和销毁
但是这样会有一个弊端,如果长期不调用这个block,这个循环引用的环就会一直存在

为什么 weakSelf 需要配合 strong self 使用

一般解决循环引用问题会这么写

__weak typeof(self) weakSelf = self;
[self doSomeBackgroundJob:^{
    __strong typeof(weakSelf) strongSelf = weakSelf;
    if (strongSelf) {
        ...
    }
}];

为什么 weakSelf 需要配合 strongSelf 使用呢?
在 block 中先写一个 strongSelf,其实是为了避免在 block 的执行过程中,突然出现 self 被释放的尴尬情况。通常情况下,如果不这么做的话,还是很容易出现一些奇怪的逻辑,甚至闪退。

比如下面这样

__weak __typeof__(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    [weakSelf doSomething];
    [weakSelf doOtherThing];

});

doSomething 内,weakSelf 不会被释放.可是在执行完第一个方法后 ,weakSelf可能就已经释放掉,再去执行 doOtherThing,会引起 一些奇怪的逻辑,甚至闪退。
所以需要这么写

__weak __typeof__(self) weakSelf = self;
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    __strong __typeof(self) strongSelf = weakSelf;
    [strongSelf doSomething];
    [strongSelf doOtherThing];
});

Block 面试总结

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

推荐阅读更多精彩内容