美文网首页
Block在内存中的位置

Block在内存中的位置

作者: conowen | 来源:发表于2018-04-10 17:37 被阅读40次

Block在内存中的三种位置

首先,在 MRC 下,Block 默认是分配在栈上的,除非进行显式的 copy,但是在ARC的中,一般都是分配在堆中。

@interface SecondViewController ()
typedef void (^blk)(void);
@end

@implementation SecondViewController

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    __block int val = 1;

    //block保存在堆区
    __strong blk heapBlock = ^{
        val = 2;
    };
    NSLog(@"heapBlock: %@", heapBlock);

    //block保存在栈区
    __weak blk stackBlock = ^{
        val = 2;
    };
    NSLog(@"stackBlock: %@", stackBlock);
    
    //block保存在全局区
    blk globalBlock = ^{
    };
    NSLog(@"globalBlock: %@", globalBlock);
}

Log输出

2018-04-09 15:50:14.351940+0800 TestMRC[29483:4870456] heapBlock: <__NSMallocBlock__: 0x60000024c090>
2018-04-09 15:50:14.352184+0800 TestMRC[29483:4870456] stackBlock: <__NSStackBlock__: 0x7ffee57543d0>
2018-04-09 15:50:14.352318+0800 TestMRC[29483:4870456] globalBlock: <__NSGlobalBlock__: 0x10a4a9148>

简单来说分两种情况
A、没有引用外部变量 --- block存放在全局区
B、引用了外部变量----显式声明为weak类型的block则存放在栈区,反之则是存在在堆区的,也就是说block是strong类型的。

内存分区

iOS App的内存分配大概如下所示

image.png
从下到上,是低地址到高地址。
代码区(Code): 存放App代码;
常量区(Const):存放App声明的常量;
全局区/静态区(Global/Static):存放App的全局变量与静态变量;
堆区(heap):一般是存放对象类型,需要手动管理内存,ARC项目的话,编译器接手内存管理工作;堆区是使用链表来存储的空闲内存地址的,是不连续的,而链表的遍历方向是由低地址向高地址。
栈区(stack):存放的局部变量、一旦出了作用域就会被销毁,不需要手动管理,栈区地址从高到低分配,遵循先进后出的,如果申请的栈区内存大小超过剩余的大小,就会反正栈区溢出overflow问题,一般栈区内存大小为2M。

使用block的注意事项

因为在ARC中,block还是有可能为NSStackBlock类型的,也就是说,随着作用域结束,block将会销毁回收。
所以有时候需要对block进行copy操作,手动复制到堆区内存中,防止被回收,导致block无法使用。

自动copy

以下情况,系统会将block自动复制到堆上,自动对block调用copy方法。
1、当 block 作为函数返回值返回时;
2、当 block 被赋值给__strong修饰的 id 类型的对象或 block 对象时;
3、当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。

因为在ARC下,对象默认是用__strong修饰的,所以大部分情况下编译器都会将 block从栈自动复制到堆上。

不自动copy

以下情况,系统不会自动复制到堆上,也就是说,作用域结束,block就被回收销毁。
block 作为方法或函数的参数传递。
block 作为临时变量,没有赋值给其他block

注意,上述不一定是从栈区复制到堆区,也有可能是global 区复制过去的。

__block的探究

ARC项目中,使用Block一般都是NSMallocBlock类型的Block,也就是说Block会存放在堆区内存中,当Block捕获外部变量的时候,如果外部变量是存在栈区内存中,Block只是对其值进行复制操作,所以Block只能读取栈区的内存。简单来说,因为Block其实也可以看做一个函数,是有自己的作用域的,与栈区变量的作用域并不同,所以无发修改栈区的值。

但是对栈区的变量加了前缀__block后,Block就会创建一个带isa指针的构体,里面存放着该变量的内存地址等信息。因为平常使用的Block一般都是存放在堆区的,而且Block里面的变量也是存在堆区,所以,这个原本存放在栈区的变量加了__block后,就变成了一个存放在堆区的Block变量(结构体),这样一来,在Block里面当然就可以修改它的值了。

- (void)viewDidLoad {
    [super viewDidLoad];
    // Do any additional setup after loading the view.
    __block int a = 1;
    NSLog(@"a value = %d, addr = %p",a,&a);
    void (^blk)(void) = ^{
        a = 2;
        NSLog(@"a value = %d, addr = %p",a,&a);
    };
    blk();
    NSLog(@"a value = %d, addr = %p",a,&a);
}

打印的结果如下,地址更改了。

2018-04-10 17:32:02.312106+0800 TestMRC[68224:5848911] a value = 1, addr = 0x7ffeef36a450
2018-04-10 17:32:02.312307+0800 TestMRC[68224:5848911] a value = 2, addr = 0x604000235098
2018-04-10 17:32:02.312410+0800 TestMRC[68224:5848911] a value = 2, addr = 0x604000235098

这样就可以解释,为什么全局变量和属性可以在Block直接修改其值了,因为这些变量的生命周期是随着类的结束而结束的。

相关文章

网友评论

      本文标题:Block在内存中的位置

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