Block内存关系
Block经典问题循环引用&解决
Block底层分析
Block底层HooK
程序占用内存分类
- 栈区:由编译器自动分配释放 ,存放函数的参数值,局部变量的值,基本数据类型等。
- 堆区:一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 ,OC中用alloc函数生成的对象都是放在堆区。
- 全局区(静态区)(static):全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域, 程序结束后有系统释放。
- 常量区 :常量字符串就是放在这里的。程序结束后由系统释放。
- 程序代码区:存放函数体的二进制代码
Block在内存中的位置
根据Block在内存中的位置分为三种类型:
-
NSGlobalBlock
是位于全局区的block,它是设置在程序的数据区域(.data区)中。 -
NSStackBlock
是位于栈区,超出变量作用域,栈上的Block以及 __block变量都被销毁。 -
NSMallocBlock
是位于堆区,在变量作用域结束时不受影响。
注意:在 ARC 开启的情况下,将只会有 NSConcreteGlobalBlock
和 NSConcreteMallocBlock
类型的 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();
}

即使如此,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下也是会异常退出。

我们再看一下上面的数组的初始化函数
+ (instancetype)arrayWithObjects:(ObjectType)firstObj, ... NS_REQUIRES_NIL_TERMINATION;
这个函数是一个可变函数,只有第一个参数被显示的申明为ObjectType
类型,也就是id类型,其他的参数并没有被显示的申明为id类型。这也验证了第一种情况下第一个block被分配在堆上,第二个block被分配在栈上。而我们的第二种写法是,先申明一下block,在block到底是什么一文中,我们已经说了,block其实就是一个函数指针,也可以说它是一个id类型,所以在第二种写法下,两个block都被显示的申明为id类型,所以都被分配在堆上,所以第二种情况没有问题。
由此我们可以得出一个结论:block作为函数的参数时,一定要被显示的申明为id类型,才会被分配在堆上
网友评论