Listed


Block内存存放探究

Block简介 #

block可以说是一种匿名函数,但是又以对象的形式存在,内部使用结构体实现,我们可以将一个block作为参数传给其它的函数或者block. 一个简单的block如下:

1
2
3
^(NSInteger param) {
     NSLog(@"%d", param);
};

其它block写法参考: fuckingblocksyntax.com

Block类型 #

我们都知道block有三种类型

  1. NSGlobalBlock, 全局block
  2. NSStackBlock, 栈区block
  3. NSMallocBlock, 堆区block

那么是什么条件决定了block的种类呢? 也就是说是什么条件决定了block存在的内存区域呢? 下面就通过实践来探索下结果.

:::tip iOS的内存存储区域: 栈区、堆区、全局/静态区、常量区、代码区 :::

Block实例 #

我们声明一个block,什么都不关联,看看默认情况下是什么类型

1
2
3
4
NSLog(@"%@", ^(NSInteger a) {
    
});
// TestBlockMemory[4885:374356] <__NSGlobalBlock__: 0x1000010c0>

通过实例我们可以看出,最最简单的block是NSGlobalBlock类型,存在全局区.

当block引用了外部变量

1
2
3
4
5
NSInteger num = 10;
NSLog(@"%@", ^(NSInteger a) {
    return a + num;
});
// TestBlockMemory[4918:378440] <__NSStackBlock__: 0x7ffeefbff4b8>

首先测试的是引用了一个局部变量,结果为NSStackBlock, 在栈区, 如果引用全局变量呢?

1
2
3
4
5
static NSInteger num = 10;
NSLog(@"%@", ^(NSInteger a) {
    return a + num;
});
// TestBlockMemory[4941:379770] <__NSGlobalBlock__: 0x1000010d8>

看的出,引用全局变量后并没有像局部变量那样改变了block原始类型,依然是NSGlobalBlock类型. 在全局区.

block赋值操作

1
2
3
4
5
6
NSInteger num = 10;
NSInteger (^addNum)(NSInteger a) = ^NSInteger(NSInteger a) {
    return a + num;
};
NSLog(@"%@", addNum);
// TestBlockMemory[5002:387216] <__NSMallocBlock__: 0x10055a710>

终于出现了堆区

1
2
3
4
5
6
static NSInteger num = 10;
NSInteger (^addNum)(NSInteger a) = ^NSInteger(NSInteger a) {
    return a + num;
};
NSLog(@"%@", addNum);
// TestBlockMemory[5025:388490] <__NSGlobalBlock__: 0x100001058>

虽然有了赋值操作,但是引用的是全局变量,block又变回了全局类型.

总结 #

通过以上这些示例,可以得出结论

  1. 当block刚创建好时,不引用变量或者引用的是全局变量,不进行赋值时是NSGlobalBlock类型,全局区block.
  2. 当block引用了外部局部变量,不进行赋值操作时是NSStackBlock类型,栈区block.
  3. 当block引用了外部局部变量,且进行了赋值操作时是NSMallocBlock类型,堆区block.

其实block在哪个区都是有它特定的意义的,当系统不知道你怎么使用时,就把它放到全局区;系统能明确管理的时候,就把它放到栈区; 当你进行了赋值操作,系统可能认为你在将来某个点会用到这个block,于是就放到堆区,延长它的生命周期.

接下来

如果想更深入的了解block,可以通过clang命令来将oc代码转为c++代码,看内部是如何实现的,执行命令之后会在当前目录产生一个cpp源代码文件

1
clang -rewrite-objc main.m