我们都知道block在oc中是很常用的,随处可见,越来越多的原先delegate的实现现在都用block去处理了,可见block的重要性。
那么什么是block呢?block在使用的时候要注意什么呢?
直接show code,看看底层block到底是变成了啥数据结果的
/*
block 被编译成了结构体,根据block捕获变量的情况,会有不同的情况。
struct Block_layout和struct Block_descriptor_1是一定存在的,struct Block_descriptor_2和struct Block_descriptor_3是可选的。
当block是全局block的时候,descriptor_2和descriptor_3是没有的。当block有捕获变量的时候,他两会存在,并会对block和捕获的变量做一下内存的操作。比如会把捕获的变量copy到堆上。
*/
struct Block_layout {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
BlockInvokeFunction invoke;
struct Block_descriptor_1 *descriptor; //
// imported variables
};
struct Block_descriptor_1 {
uintptr_t reserved;
uintptr_t size;
};
struct Block_descriptor_2 {
// requires BLOCK_HAS_COPY_DISPOSE
BlockCopyFunction copy;
BlockDisposeFunction dispose;
};
struct Block_descriptor_3 {
// requires BLOCK_HAS_SIGNATURE
const char *signature;
const char *layout; // contents depend on BLOCK_HAS_EXTENDED_LAYOUT
};
/* block 总共有6中,我们开发常用的有3种,_NSConcreteStackBlock、
_NSConcreteMallocBlock、_NSConcreteGlobalBlock。另外的3种多在系统级别才会被使用到。
*/
void * _NSConcreteStackBlock[32] = { 0 };
void * _NSConcreteMallocBlock[32] = { 0 };
void * _NSConcreteAutoBlock[32] = { 0 };
void * _NSConcreteFinalizingBlock[32] = { 0 };
void * _NSConcreteGlobalBlock[32] = { 0 };
void * _NSConcreteWeakBlockVariable[32] = { 0 };
So,NSGlobalBlock、NSStackBlock、NSMallocBlock这3种又有啥区别呢
在MRC下:
NSGlobalBlock:没有访问(捕获)auto变量(局部变量)的block (数据区)
NSStackBlock:访问(捕获)auto变量(局部变量)的block (栈区)
NSMallocBlock: 对NSStackBlock做了一次copy操作后得到的block。(堆区)
在ARC下:
被强指针引用的block且引用了外部变量,那么会自动做一次copy操作,即把NSStackBlock上的block copy到NSMallocBlock上。即被strong,copy修饰的block且用了外部变量就是NSMallocBlock。
判断捕获对象释放:
NSStackBlock(栈上的block)会对捕获对象进行强引用。(在arc模式下,block作为函数的参数传递,此时的block是NSStackBlock)
NSMallocBlock(堆上的block)会对捕获的对象进行引用,捕获的对象也会被copy到堆空间上。(如果捕获的对象是strong类型的,就会强引用,如果是__weak 修饰的weak类型,就会弱引用)。
typedef void(^MyBlock)(void);
@interface ViewController2 ()
@property(nonatomic,copy)MyBlock myBlock;
@end
@implementation ViewController2
- (void)viewDidLoad {
[super viewDidLoad];
self.view.backgroundColor = [UIColor whiteColor];
int a = 10;
self.myBlock = ^{ // 被强指针引用且捕获了外部变量,会被copy到堆上,是NSMallocBlock
a;
// NSLog(@"%@",self);
//__NSMallocBlock__
};
/*block 作为函数的参数来传递,是NSStackBlock,此时即便捕获了外部变量也不会被copy到堆上,
依然是NSStackBlock,也会对捕获的外部变量强引用。*/
[self getMyhahahahBlock:^{
// __NSStackBlock__
a;
NSLog(@"%@",self);
}];
}
-(void)getMyhahahahBlock:(MyBlock)block{
//self.myBlock = block;
block();
NSLog(@"%@",block);
}
block访问外部变量有几种方式呢?
- __block修饰变量(__block 只能修饰auto的局部变量,不能修饰static变量,也不能修饰全局变量)
- static修饰变量
- 全局变量
那么__block 做了啥呢?
__block 修饰后,底层会把捕获的局部变量包装成一个对象,通过捕获这个变量来修改局部变量的值。
//__block修饰的变量会被封装成如下的结构体
struct Block_byref {
void *isa;
struct Block_byref *forwarding;
volatile int32_t flags; // contains ref count
uint32_t size;
};
struct Block_byref_2 {
// requires BLOCK_BYREF_HAS_COPY_DISPOSE
BlockByrefKeepFunction byref_keep; // 对捕获的变量copy到内存。
BlockByrefDestroyFunction byref_destroy;
};
struct Block_byref_3 {
// requires BLOCK_BYREF_LAYOUT_EXTENDED
const char *layout;
};
NSMutableArray *array = [NSMutableArray alloc]init];
MyBlock block = ^{
[array addObject: @"hello"];
/* 这个会报错么?
被捕获的局部变量不是会被做一次copy操作,copy到堆上么?
NSMutableArray对象被copy后,不是变成NSArray对象了么?
NSArray对象能用addObject方法?
*/
/* 对于这个疑惑,在看了底层源码之后发现,苹果baba不是简单的调了一次[array copy]函数。
它其实是去堆上开辟了一个和array一样大的内存空间,
然后把array的内存都搬到新的array上。所以不会存在上面说的情况。
*/
// 先开辟空间,然后做内存移动。
struct Block_byref *copy = (struct Block_byref *)malloc(src->size);
memmove(copy+1, src+1, src->size - sizeof(*src));
}
总结来说,如果是__block修饰的变量,在block内部被修改了,这个过程中存在了3层copy操作:
- block被从栈copy到了堆上。
- __block修饰的变量被封装成一个对象,该对象也被copy到堆上了
- __block修饰的变量被封装成一个对象,该对象内部捕获的变量也被copy到堆上了。
网友评论