Block 的常识

276 阅读4分钟

最近有面试需求,整理一下关于 Block 可能会问到的面试题

Block 是什么?

Block 是将函数及其执行上下文封装起来的对象。 注意是一个 OC 对象,内部也有一个 isa指针,

Block 的变量截取

局部变量截获是值截获

比如,基本的数据类型

NSInteger num = 3;
NSInteger(^block)(NSInteger) = ^NSInteger(NSInteger n){
return n * num;
};
num = 4;
NSInteger result = block(2);
NSLog(@"%ld",result);

这里 result 是 6 不是 8

因为 num 是局部变量,截取的是 值 变量。

在Block里面修改 num 的值也是错误的。,

局部对象变量也是一样,截获的是值,而不是指针,

在外部将其置为 nil,对 block 没有影响,而该对象 调用方法会影响,如下

NSMutableArray *mutabArr = @[@1,@2].mutableCopy;
 void(^innerBlock)(void) = ^{
      NSLog(@"===3%@",mutabArr);
     [mutabArr addObject:@4];
     NSLog(@"===4%@",mutabArr);
 };
 [mutabArr addObject:@3];
 mutabArr = nil;
 NSLog(@"===1%@",mutabArr);
 innerBlock();
 NSLog(@"2%@",mutabArr);

这里的输出 在 3 和 4 处 输出的是数组,外部打印为nil

局部静态变量是指针截取

static NSInteger num = 2;
NSInteger(^numBlock)(NSInteger) = ^NSInteger(NSInteger n){
    return num * n;
};num = 4;
NSInteger result =  numBlock(2);
NSLog(@"%ld",result);
​
​

这里输出的是 8 , 局部静态变量是指针截取。

Block 的类型

Block的形式有三种

  1. 全局 Block(_NSConcreteGlobalBlock)存在于全局内存中, 相当于单例
  2. 栈 Block(_NSConcreteStackBlock) 存在于栈内存中, 超出其作用域则马上被销毁
  3. 堆 Block(_NSConcreteMallocBlock) 存在于堆内存中, 是一个带引用计数的对象, 需要自行管理其内存

堆区、栈区的区别

栈区:是用户存放程序临时创建的局部变量(指针,基本数据类型,值类型都在栈区

一般指针存在栈区,自动分配内存,局部变量作用域结束,系统立即回收;除此以外, 在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值 也会被存放回栈中

堆区:堆是用于存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减(对象,引用类型)

    • 一般对象放在堆上,即alloc出来的对象
    • 在iOS的ARC程序中,系统自动管理计数器,计数器为0的时候,在当次的runloop结束后,释放掉内存。堆中的所有东西都是匿名的,这样不能按名字访问,而只能通过指针访问。
    • 对于堆来讲,频繁的new/delete势必会造成内存空间的不连续性,从而造成大量的碎片 ,使程序效率降低。

最主要要知道,栈区是连续的,操作数据简单,系统自己回收,堆区由引用计数来管理释放,内存空间不连续,会造成碎片化,使程序效率降低

怎样判断该Block 是哪种类型

GlobalBlock ----- block不访问外界变量,也就是不访问栈上和堆上的变量,也就是该block是全局block。(这个好理解)

block访问外界变量,实际上改block放在栈区,但是在ARC情况下,自动拷贝放到了堆上,也就是使用copy修饰符。为了解决栈块在其变量作用域结束之后被废弃(释放)的问题(没有看懂这句话),我们需要把Block复制到堆中,延长其生命周期。开启ARC时,大多数情况下编译器会恰当地进行判断是否有需要将Block从栈复制到堆(所以用Xcode 打印出来的 都是 copy 之后的 _NSConcreteMallocBlock,全局的除外),如果有,自动生成将Block从栈上复制到堆上的代码。Block的复制操作执行的是copy实例方法。Block只要调用了copy方法,栈块就会变成堆块。

## __ Block && __weak

__Block

主要是为了修改外部的变量的值,因为block 对局部变量(引用类型和值类型)是值截取,如果要让 Block 内部的修改对外部起作用,有三种方法

  1. __Block 方法
  2. 写成全局变量
  3. static 静态变量(指针截取)

__weak

主要是解决循环引用的问题,还可以使用 __unsafe_unretained (不会产生强引用,不安全,指向的对象销毁时,指针存储的地址值不变)

小结

以上基本上涵盖了,所有关于Block 可能会问到的面试题。基于iOS目前严重的内卷可能还会更深,可以参考:

深入理解Block