美文网首页
Block探索

Block探索

作者: 6ffd6634d577 | 来源:发表于2019-08-01 17:07 被阅读0次

Block内存关系
Block经典问题循环引用&解决
Block底层分析
Block底层HooK

程序占用内存分类

  1. 栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值,基本数据类型等。
  2. 堆区:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 ,OC中用alloc函数生成的对象都是放在堆区。
  3. 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域, 程序结束后有系统释放。
  4. 常量区 :常量字符串就是放在这里的。程序结束后由系统释放。
  5. 程序代码区:存放函数体的二进制代码

Block在内存中的位置

根据Block在内存中的位置分为三种类型:

  • NSGlobalBlock是位于全局区的block,它是设置在程序的数据区域(.data区)中。
  • NSStackBlock是位于栈区,超出变量作用域,栈上的Block以及 __block变量都被销毁。
  • NSMallocBlock是位于堆区,在变量作用域结束时不受影响。

注意:在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlockNSConcreteMallocBlock 类型的 block。

正如它们名字显示得一样,表明了block的三种存储方式:栈、全局、堆。获取block对象中的isa的值,可以得到上面其中一个,下面开始说明哪种block存储在栈、堆、全局。

位于全局区:GlobalBlock

生成在全局区block有两种情况:

1. block内部没有使用任何外部变量
2. 只使用了全局变量

void(^block)(void) = ^ {  NSLog(@"Global Block"); };
 int main() {
}

int(^block)(int count) = ^(int count) {
    return count;
 };
 block(2);

虽然,这个block在循环内,但是blk的地址总是不变的。说明这个block在全局段。注:针对没有捕获自动变量的block来说,虽然用clang的rewrite-objc转化后的代码中仍显示_NSConcretStackBlock,但是实际上不是这样的。

位于栈内存:StackBlock

ARC环境下,栈上的block默认都会被拷贝到堆上,也就是说,在ARC环境下,block只有两种类型:NSGlobalBlock 和 NSMallocBlock

1.如果block内部访问了外部变量,且不是静态变量时

MRC: 在栈区,当Block所在的作用域结束时就会被销毁,所以当在别的地方回掉时可能就会出错,为了避免
这种情况就需要手动调用Block的copy方法copy到堆区,这也是Block当属性时用copy的原因.在MRC下
需要手动管理内存(需要程序猿手动copy到堆区)
ARC:在堆区,不需要手动管理内存

NSInteger i = 10;
block = ^{ NSLog(@"%ld", i);
};
block;

位于堆内存:MallocBlock

堆中的block无法直接创建,其需要由_NSConcreteStackBlock类型的block拷贝而来(也就是说block需要执行copy之后才能存放到堆中)。由于block的拷贝最终都会调用_Block_copy_internal函数。

void(^block)(void); 
int main(int argc, const char * argv[]) { @autoreleasepool {
       __block NSInteger i = 10;
       block = [^{
           ++i;
       } copy];
       ++i;
       block(); NSLog(@"%ld", i);
   } return 0;
} 

我们对这个生成在栈上的block执行了copy操作,Block和__block变量均从栈复制到堆上。上面的代码,有跟没有copy,在非ARC和ARC下一个是stack一个是Malloc。这是因为ARC下默认为Malloc(即使如此,ARC下还是有一些例外,下面会讲)。

block在ARC和非ARC下有巨大差别。多数情况下,ARC下会默认把栈block被会直接拷贝生成到堆上。那么,什么时候栈上的Block会复制到堆上呢?

  • 调用Block的copy实例方法时
  • Block作为函数返回值返回时
  • 将Block赋值给附有__strong修饰符id类型的类或Block类型成员变量时
  • 将方法名中含有usingBlock的Cocoa框架方法或GCD的API中传递Block时

block在ARC和非ARC下的巨大差别

  • ARC 中,捕获外部了变量的 block 的类会是 NSMallocBlock 或者 NSStackBlock,如果 block 被赋值给了某个变量,在这个过程中会执行 _Block_copy 将原有的 NSStackBlock 变成 NSMallocBlock;但是如果 block 没有被赋值给某个变量,那它的类型就是 NSStackBlock;没有捕获外部变量的 block 的类会是 NSGlobalBlock 即不在堆上,也不在栈上,它类似 C 语言函数一样会在代码段中。
  • MRC 中,捕获了外部变量的 block 的类会是 NSStackBlock,放置在栈上,没有捕获外部变量的 block 时与 ARC 环境下情况相同,是 NSGlobalBlock
- (void)viewDidLoad {
    [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. 
    [self testBlockForHeapOfARC];
}
-(void)testBlockForHeapOfARC{ 
    int val =10; 
    typedef void (^blk_t)(void);
    blk_t block = ^{ NSLog(@"blk0:%d",val);
    };
    block();
}
image.png

即使如此,ARC下还是有一些例外:

-(NSArray *)getBlockArray0{ 
  int val =10; 
  return [NSArray arrayWithObjects:^{NSLog(@"blk0:%d",val);},^{NSLog(@"blk1:%d",val);},nil];

//当换成这种写法就不会崩溃
void (^block1)(void) = ^{NSLog(@"blk0:%d",val);};
    void (^block2)(void) = ^{NSLog(@"blk1:%d",val);};
    return [NSArray arrayWithObjects:block1,block2,nil];
}
 
-(void)testBlockForHeap0{  
NSArray *tempArr = [self getBlockArray0]; 
   NSMutableArray *obj = [tempArr mutableCopy]; 
   typedef void (^blk_t)(void);
    blk_t block = (blk_t){
    [obj objectAtIndex:0]};
    block();
}

这段代码在最后一行blk()会异常,因为数组中的block是栈上的。因为val是栈上的。解决办法就是调用copy方法。这种场景,ARC也不会为你添加copy,因为ARC不确定,采取了保守的措施:不添加copy。所以ARC下也是会异常退出。


image.png

我们再看一下上面的数组的初始化函数

+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ... NS_REQUIRES_NIL_TERMINATION;

这个函数是一个可变函数,只有第一个参数被显示的申明为ObjectType类型,也就是id类型,其他的参数并没有被显示的申明为id类型。这也验证了第一种情况下第一个block被分配在堆上,第二个block被分配在栈上。而我们的第二种写法是,先申明一下block,在block到底是什么一文中,我们已经说了,block其实就是一个函数指针,也可以说它是一个id类型,所以在第二种写法下,两个block都被显示的申明为id类型,所以都被分配在堆上,所以第二种情况没有问题。

由此我们可以得出一个结论:block作为函数的参数时,一定要被显示的申明为id类型,才会被分配在堆上

相关文章

  • block分析(下)

    block通过clang分析 带着下面的疑问,我们去探索block原理 探索block底层源码 block在底层是...

  • Block探索

    随着苹果越来越多的API开始提供block的写法,另外加上block在很多时候写起来确实很方便,因此block越来...

  • Block探索

    Block内存关系Block经典问题循环引用&解决Block底层分析Block底层HooK 程序占用内存分类 栈区...

  • Block探索

    一、什么是Block? Block是将函数及其执行上下文封装起来的对象。 例如: eg:clang -rewrit...

  • Block探索

    Block的分类 Block一共有6种类,常见的有三种。 上面这种block的打印结果为<__NSGlobalBl...

  • Block探索

    Block block 类型**1、全局block - **NSGlobalBlock**** **2、栈bloc...

  • iOS底层探索之Block(二)——如何解决Block循环引用问

    Block你知道几种?Block的循环引用你有几种解决办法呢? iOS底层探索之Block(一)——初识Block...

  • Block 对象探索

    Block对象是一段代码,先给出一个Block对象的示例: 看上去和C函数类型,都是在一个花括号内的一套指令,但是...

  • Block原理探索

    Block定义 闭包是一个函数(或指向函数的指针),再加上该函数执行的外部的上下文变量(有时候也称为自由变量)bl...

  • Block的探索

    Block的特性 保存一段代码块 随时随地的持有 匿名函数 —— block -> 对象 -> struct 自动...

网友评论

      本文标题:Block探索

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