美文网首页iOS知识点
iOS-Block知识点整理

iOS-Block知识点整理

作者: 木子奕 | 来源:发表于2019-01-27 23:13 被阅读0次
Block.png
  • block 介绍

  • 截获变量

  • __block修饰符

  • Block的内存管理

  • Block的循环引用

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


Block介绍

Block是将函数及其执行上下文封装起来的对象
Block调用即是函数调用

image.png image.png

截获变量

先看一个问题

// 全局变量
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 看这一段

904629-35c3cf4c20d2a16d.png
  • 可以看到,局部变量截获的就是它的值

  • 静态局部变量以指针形式截取的

  • 对象类型的类型连同其修饰符一起截获,理解这个就能更好的理解 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修饰符,不然编译器会报错</pre>

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

*   静态局部变量

*   全局变量

*   静态全局变量

    { __Block int multiplier = 6; 
      int(^Block)(int) = ^int(int num) {
          return num * multiplier;
    };
    multiplier = 4;
    NSLog(@"result is %d", Block(2));</pre>

    }//这里的结果就是 8 了

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

904629-f4061883e1459a77.png

在 multiplier 进行赋值的时候

904629-21223beb414c009e.png

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


Block的内存管理

Block类型

image.png
  • _NSConcreteGlobalBlock 全局

  • _NSConcreteStackBlock 栈类型

  • _NSConcreteMallocBlock 堆类型

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

904629-bca640812ca4a13e.png

Block的copy操作

904629-cd5cf1d781f5dbb1.png

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

image.png image.png

Block对象中的__forwarding指针当Block在栈的时候指向自身,当Block被copy到堆中的时候栈中的Blokc的__forwarding指向堆中的__block变量,而堆中的Block的__forwarding指向他自身,这样就保证了无论Block被存储在内存的哪个位置__forwarding都可以访问到同一个__block变量。


image.png image.png

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 模式下是会产生循环引用,引起内存泄漏的

904629-941a61c6451be1d2.png

__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 截获变量的特性?

  • 你都遇到过哪些循环引用?怎么解决的?

相关文章

  • iOS-Block知识点整理

    block 介绍 截获变量 __block修饰符 Block的内存管理 Block的循环引用 为什么 weakSe...

  • iOS-Block本质

    iOS-Block本质 参考篇:iOS-Block浅谈[https://www.jianshu.com/p/25a...

  • Objective-C的本质(6)——Block本质

    参考:iOS-Block本质iOS底层原理总结 - 探寻block的本质(一)iOS底层原理总结 - 探寻bloc...

  • emmm

    关于安排 接下来事情非常多。非常多 明天一天继续整理知识点归纳,有了知识点后,再根据课表拓展、整理知识点。 然后就...

  • Markdown使用精要

    整理了Markdown常用的知识点,如下:

  • 教师资格证考试《教育知识与能力》98 条知识点,42分到手!

    考试知识点整理我们先整理《教育知识与能力》的选择题常考知识点 总共有98条知识点,让你轻松搞定选择题! 教育基础知...

  • 知识点整理

    以下完全为个人总结——若发现问题请下方评论,定回 I/O 主机主频 50MHz (50M个时钟周期)/s CPI ...

  • 知识点整理

    JSON与JSONP跨域cors详解(阮一峰)flex弹性盒子Jquery怎么获取元素文档大小、偏移、位置和滚动条...

  • 知识点整理

    redis redis为什么高效,及应用场景 锁 死锁产生条件,及避免死锁 悲观锁与乐观锁 数据库 事务 事务特性...

  • iOS-block

    一. 查看block内部实现 1.编写block代码void (^DemoBlock)(int, int) = ^...

网友评论

    本文标题:iOS-Block知识点整理

    本文链接:https://www.haomeiwen.com/subject/ayuijqtx.html