OC中的Block
1.Block的本质
- Block是从C语言的匿名函数发展而来,是通过函数的指针对函数进行调用的一种方式。
- Block本质上也是一个OC对象,它内部也有个isa指针。
- Runtime中Block的本质与类相同;底层代码中,Block会被解释为函数调用以及函数调用环境的OC对象。
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __showBlockBase_block_impl_0 {
struct __block_impl impl;
struct __showBlockBase_block_desc_0* Desc;
__showBlockBase_block_impl_0(void *fp, struct __showBlockBase_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
static void __showBlockBase_block_func_0(struct __showBlockBase_block_impl_0 *__cself) {
printf("hello word!\n");
}
static struct __showBlockBase_block_desc_0 {
size_t reserved;
size_t Block_size;
} __showBlockBase_block_desc_0_DATA = { 0, sizeof(struct __showBlockBase_block_impl_0)};
// Block的定义和调用
int main(int argc, const char * argv[]) {
/* @autoreleasepool */ { __AtAutoreleasePool __autoreleasepool;
// 定义Block
void (*blockOne)(void) = ((void (*)())&__showBlockBase_block_impl_0((void *)__showBlockBase_block_func_0, &__showBlockBase_block_desc_0_DATA));
// 调用Block
((void (*)(__block_impl *))((__block_impl *)blockOne)->FuncPtr)((__block_impl *)blockOne);
}
}
由于现在的环境基本上都是ARC环境,所以这篇文章默认是在ARC环境下的情况。
2.Block捕获变量
OC中变量有5种:自动变量(局部变量)、函数的参数(函数形参)、静态变量(静态局部变量)、静态全局变量、全局变量;对于Block捕获自动变量时,还有可能被__block修饰的自动变量。
当Block捕获静态全局变量、全局变量和未捕获外部的情况相同,底层代码如下;
struct __block_impl {
void *isa;
int Flags;
int Reserved;
void *FuncPtr;
};
struct __showBlockBase_block_impl_0 {
struct __block_impl impl;
struct __showBlockBase_block_desc_0* Desc;
__showBlockBase_block_impl_0(void *fp, struct __showBlockBase_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
当Block捕获静态变量时,Block在底层解释为相关类时会增加一个指针类型的属性变量,该指针类型的值类型与该静态变量的值类型相对应;
// 局部静态变量
static int staticValue = 0;
struct __showBlockBase_block_impl_3 {
struct __block_impl impl;
struct __showBlockBase_block_desc_3* Desc;
int *staticValue; // 捕获的外界局部静态变量
__showBlockBase_block_impl_3(void *fp, struct __showBlockBase_block_desc_3 *desc, int *_staticValue, int flags=0) : staticValue(_staticValue) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
当Block捕获自动变量时,Block在底层解释为相关类时会增加一个的属性变量与该自动变量相对应;
// 自动变量
int value = 0;
struct __showBlockBase_block_impl_4 {
struct __block_impl impl;
struct __showBlockBase_block_desc_4* Desc;
int value; // 捕获的外界自动变量
__showBlockBase_block_impl_4(void *fp, struct __showBlockBase_block_desc_4 *desc, int _value, int flags=0) : value(_value) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
当Block捕获__block修饰的自动变量时,Block在底层解释为相关类时会增加一个指针类型的属性变量,且该属性变量的类型是该类型对应的一个类,类中的一个属性变量的值与该自动变量相对应。
// block变量
__block int i = 0;
struct __Block_byref_i_0 {
void *__isa;
__Block_byref_i_0 *__forwarding;
int __flags;
int __size;
int i;
};
struct __showBlockBase_block_impl_5 {
struct __block_impl impl;
struct __showBlockBase_block_desc_5* Desc;
__Block_byref_i_0 *i; // 捕获的外界__block修饰的自动变量
__showBlockBase_block_impl_5(void *fp, struct __showBlockBase_block_desc_5 *desc, __Block_byref_i_0 *_i, int flags=0) : i(_i->__forwarding) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
3.Block的类型
Block根据在内存分区的位置,可以分为三类:全局Block(NSGlobalBlock)即在静态全局区的Block、栈Block(NSStackBlock)即在栈上的Block、堆Block(NSMallocBlock)即在堆上的Block。
当Block在捕获静态变量、静态全局变量、全局变量、未捕获外部变量时,为全局Block。
当Block定义后被赋值给了某个变量或者某个类的属性时,在这个过程中会执行 Block_copy将原有的NSStakeBlock变成NSMallocBlock;
当Block定义后没有赋值给某个变量,那它的类型就是NSStakeBlock。
4.Block的内存管理
Block首次定义是在栈上,将声明的Block赋值给变量或者某个类的属性时,会调用objc_retainBlock方法,在该方法中会调用_Block_copy将栈上的Block复制到堆上;因此声明一个Block的属性一般使用copy关键字。
栈上的Block在执行完毕后会立即释放,而堆上的Block通过ARC机制管理自己的内存。
5.Block的循环引用
栈上的Block在执行完毕后就会释放,一般不会导致循环引用;我们所说的循环引用是堆上的Block形成。
Block循环引用的原因:对象间接或者直接持有Block,Block中间接或者直接持有该对象,就会造成循环引用,导致对象和Block内存无法释放。
避免Block循环引用有三种方法:
1.创建一个__weak修饰的指针变量,将造成循环引用的对象的赋值该指针变量,在Block中使用该指针变量,打破循环引用。代码如下:
__weak typeof(self) weakSelf = self;
self.myBlock3 = ^ {
dispatch_after(2, dispatch_get_main_queue(), ^{
NSLog(@"%@", weakSelf);
});
};
// 调用
self.myBlock3();
2.在Block可执行完毕后将所持有对象设置为空,但是如果该Block没有执行,依旧不能打破循环引用。代码如下:
__block UIViewController * vc = self;
self.myBlock = ^(int sendValue) {
dispatch_after(2, dispatch_get_main_queue(), ^{
NSLog(@"%@", vc);
vc = nil;
});
};
// 调用
self.myBlock(2);
3.将持有对象作为Block的参数传入。代码如下:
self.myBlock2 = ^(UIViewController *vc) {
dispatch_after(2, dispatch_get_main_queue(), ^{
NSLog(@"%@", vc);
});
};
// 调用
self.myBlock2(self);
6.Block的弱引用和多线程的问题
在Block内部使用外部用__weak 修饰的弱引用。我们知道弱引用指向的对象在没有强引用引用的时候就会自动销毁,同时弱引用的指针赋值为 nil。那么如果在Block里面使用了多线程来访问弱引用,因多线程执行的时间顺序不定,会造成在多线程访问对象的时候这个弱引用的对象已经销毁了。
Person * person = [[Person alloc] init];
// 弱引用对象
__weak typeof(Person) *weakPerson = person;
person.name = @"zhou";
person.block = ^int(int a, int b) {
// 开辟一条线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 访问弱引用的对象
NSLog(@"name = %@", weakPerson.name);
});
return 1;
};
person.block(1, 2);
在Block中创建一个__strong修饰的指针变量,将外部的__weak的指针变量复制给该变量。
Person * person = [[Person alloc] init];
// 弱引用对象
__weak typeof(Person) *weakPerson = person;
person.name = @"zhou";
person.block = ^int(int a, int b) {
__strong typeof(Person) * strongPerson = weakPerson;
// 开辟一条线程
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 访问弱引用的对象
NSLog(@"name = %@", strongPerson.name);
});
return 1;
};
person.block(1, 2);