block有三种类型:NSGlobalBlock,NSStackBlock,NSMallocBlock。
1. NSGlobalBlock:类似函数,未引用外部变量,位于text段;
float (^ss)(float a) = ^ (float a) {
return a;
};
NSLog(@"global block %@", ss);
2. NSStackBlock:位于栈内存,函数返回后Block将无效;
3. NSMallocBlock:位于堆内存。
对于在堆中的block和栈中的block有的时候在使用过程中出现问题,举例分析一下:
- (void)main
int num = 888;
NSMutableArray* array = [self getBlockArray:num];
for (int i = 0; i < array.count; i++) {
NSLog(@"aBlock %@",array[i]);
void(^bcc)() = array[i];
bcc();
}
- (NSMutableArray*) getBlockArray:(int)num
{
return [[NSMutableArray alloc] initWithObjects:
^{ NSLog(@"this is block 0:%i", num); },
^{ NSLog(@"this is block 1:%i", num); },
^{ NSLog(@"this is block 2:%i", num); },
nil];
}
上段程序做了这几件事,得到一个含有block的数组,并进行打印。但在程序运行到 i=1 时会出现crash,原因就是EXC_BAD_ACCESS错误,出现野指针,这是为什么呢?
block是object c 语言中的对象,只是是分配在栈上的,一般类的实例对象是分配在堆上的。就如苹果在block的文档中所说的那样: “As an optimization, block storage starts out on the stack—just like blocks themselves do.”
但为什么数组中的第一个block可以运行通过呢?打印后你会发现,第一个block是NSMallocBlock, 是因为malloc创造的数组是在堆上的,后来的2个block还是NSStackBlock。你如果把代码改成这样,就能通过了:
- (NSMutableArray*) getBlockArray:(int)num
{
return [[NSMutableArray alloc] initWithObjects:
^{ NSLog(@"this is block 0:%i", num); },
[^{ NSLog(@"this is block 1:%i", num); } copy],
[^{ NSLog(@"this is block 2:%i", num); } copy],
nil];
}
因为在使用copy方法后,block会被copy到堆上,你会发现,后面的两个block都变成了NSMallocBlock对象。根本原因就是,Block对象在栈上分配,block的引用指向栈帧内存,而当方法调用过后,指针指向的内存上写的是什么数据就不确定了。但是,如果你把NSMutableArray* array = [self getBlockArray:num]; 改成
NSMutableArray* array = [[NSMutableArray alloc] initWithObjects:
^{ NSLog(@"this is block 0:%i", num); },
^{ NSLog(@"this is block 1:%i", num); },
^{ NSLog(@"this is block 2:%i", num); },
nil];
此时,不用函数体来返回array也能通过的。因为,当一个函数调用完返回后它会释放该函数中所有的栈空间,所以函数体内的block就变成了野指针。而直接使用initWithObjects此时block还没有进行释放,换句话说,就是block还在进行引用。
所以说,你要知道如何使用block的引用,参考以下几点:
1 Block_copy与copy等效,Block_release与release等效;
2 对Block不管是retain、copy、release都不会改变引用计数retainCount,retainCount始终是1;
3 NSGlobalBlock:retain、copy、release操作都无效;
4 NSStackBlock:retain、release操作无效,必须注意的是,NSStackBlock在函数返回后,Block内存将被回收。即使retain也没用。容易犯的错误是[[mutableAarry addObject:stackBlock],(补:在arc中不用担心此问题,因为arc中会默认将实例化的block拷贝到堆上)在函数出栈后,从mutableAarry中取到的stackBlock已经被回收,变成了野指针。正确的做法是先将stackBlockcopy到堆上,然后加入数组:[mutableAarry addObject:[[stackBlock copy] autorelease]]。支持copy,copy之后生成新的NSMallocBlock类型对象。
5 NSMallocBlock支持retain、release,虽然retainCount始终是1,但内存管理器中仍然会增加、减少计数。copy之后不会生成新的对象,只是增加了一次引用,类似retain;
6 尽量不要对Block使用retain操作, 尽量使用copy。
如何理解好block的内存管理,我会在使用中慢慢理解以及消化,此次深入只是一次探讨,希望对大家有用吧。