Block 之 Block内存管理

前言

block的内存管理相对比较复杂,分ARC和MRC两种情况考虑,虽然现在开发基本是用ARC,编译器已经帮我们做了很多内存管理的优化,但我们不仅要知其然,也要知其所以然。

Block的类型

根据Block的存储方式,分为以下三种: . NSConcretStackBlock
.
NSConcretGlobalBlock
. _NSConcretMallocBlock
这三种类型表明了Block的三种存储方式:栈、全局、堆。
注意: 在ARC模式下,Block只存在全局和堆之中,对于栈上的Block,编译器帮我们拷贝到堆上,这也是为什么我们声明Block的时候即使不声明为copy,Block也是拷贝到堆上。显示地声明不会与编译器相冲突,是为了我们更好地理解是编译器做了copy这一步使得Block拷贝到堆上。

全局Block

位于全局存储区的Block有两种情况:
. 定义在函数外面的Block;

1
2
3
void (^myBlock)(void) = ^(){
    NSLog(@"hello");
};

. 不引用局部变量的Block;

1
2
3
4
5
6
7
8
9
10
int main(int argc, const char * argv[]) {
    @autoreleasepool {
        void (^myBlock)(void) = ^(){
            NSLog(@"hello");
        };
        myBlock();

    }
    return 0;
}

栈Block

1
2
3
4
5
6
typedef void (^stackBlock)() ;

stackBlock returnBlock(){
    int a =10;
    return ^{printf("a=%d\n",a);};
}

上面的例子在MRC下无法编译,而在ARC下可以编译
这是因为返回block的时候是返回局部变量的指针,而这一点恰是编译器已经断定了。在ARC下可以编译过,是因为ARC使用autorelease了

堆Block

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
id getBlockArray(){
    int val =10;
    return [NSArray arrayWithObjects:
            ^{NSLog(@"blk0:%d",val);},
            ^{NSLog(@"blk1:%d",val);},nil];
}

int main(int argc, const char * argv[]) {
    @autoreleasepool {

        id obj = getBlockArray();
        typedef void (^blk_t)(void);
        blk_t blk = (blk_t)[obj objectAtIndex:0];
        blk();

    }
    return 0;
}

在MRC模式下,会发生异常,因为数组中的block是在栈上的,到blk指向的block已经释放内存了,访问blk就会造成崩溃,解决方法就是在block后面加上copy,使它变成堆上的block。

copy的使用

不管block配置在何处,用copy方法复制都不会引起任何问题。
在ARC环境下,如果不确定是否要copy block尽管copy即可。ARC会自动处理的。
【注意】:
● 在栈上调用copy那么复制到堆上
● 在全局block调用copy什么也不做
● 在堆上调用block 引用计数增加

Comments