博客以总结为主,随时更新
带有自动变量(局部变量)的匿名函数
int(^sumBlk)(int num1, int num2) = ^int(int a, int b) {
return a + b;
};
Block变量类似于函数指针,
截获自动变量:带有自动变量值在Block中表现为“截获自动变量值”。
值得注意的点: 在现在的Blocks中,截获自动变量的方法并没有实现对C语言数组的截获
block可以截获变量,但是在块里不能修改变量的值。
此时我们使用__block
修饰符修饰变量,对需要在block
内进行赋值的变量,使用修饰符修饰,保证可以对变量进行赋值。
// blk不允许修改截获变量的值,需要的时候加上__block修饰符即可
id tstArray = @[@"blk", @"不允许赋值", @"给截获的变量"];
__block id array = [[NSMutableArray alloc] init];
void (^blk)(void) = ^{
id obj = [[NSObject alloc] init];
// [array addObject:obj];
array = tstArray;
};
block
分为全局 block
、堆 block
、栈 block
Block分类简单总结如下
block
存放在全局区weak
类型的block
则存放在栈区,反之则是存在在堆区的,也就是说block
是strong
类型的。NSGlobalBlock
:NSStackBlock
:NSMallocBlock
:循环引用就是对方面持有导致对象不能正常释放,会发生内存泄漏
在BLock里互相的强引用可能造成循环引用。
typedef void(^TBlock)(void);
@interface ViewController ()
@property (nonatomic, strong) TBlock block;
@property (nonatomic, copy) NSString *name;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"Hello";
self.block = ^(){
NSLog(@"%@", self.name);
};
self.block();
}
这里self持有了block,block持有了self,导致循环引用。
只要有一方没有进行强引用就可以解除循环引用
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"Hello";
void(^block)(void);
block = ^(){
NSLog(@"%@", self.name);
};
block();
}
为什么这个案例就没有出现循环引用的状况呢?因为当前self,也就是ViewController并没有对block进行强持有,block的生命周期只在viewDidLoad方法内,viewDidLoad方法执行完,block就会释放。
// 循环引用
self.name = @"Hello";
__weak typeof(self) weakSelf = self;
self.block = ^(){
NSLog(@"%@", weakSelf.name);
};
self.block();
此时self持有block,block弱引用self,弱引用会自动变为nil,强持有中断,所以不会引起循环引用。但该方法可能存在中途就释放掉的问题(手动延迟,可能需要调用self.name的时候name已经被释放了)如果self被销毁,那么block则无法获取name。
self.name = @"Hello";
__weak typeof(self) weakSelf = self;
self.block = ^(){
__strong __typeof(weakSelf)strongWeak = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", strongWeak.name);
});
};
self.block();
完全解决了以上self中途被释放的问题。
原理:在完成block中的操作之后,才调用了dealloc方法。添加strongWeak之后,持有关系为:self -> block -> strongWeak -> weakSelf -> self。
weakSelf被强引用了就不会自动释放,因为strongWeak只是一个临时变量,它的声明周期只在block内部,block执行完毕后,strongWeak就会释放,而弱引用weakSelf也会自动释放。
self.name = @"Hello";
__block ViewController * ctrl = self;
self.block = ^(){
__strong __typeof(weakSelf)strongWeak = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", ctrl.name);
ctrl = nil;
});
};
self.block();
使用ctrl之后,持有关系为: self -> block -> ctrl -> self,ctrl在block使用完成后,被置空,至此block对self持有就解除,不构成循环引用
// 循环引用
self.name = @"Hello";
self.block = ^(ViewController * ctrl){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@", ctrl.name);
});
};
self.block(self);
将self作为参数参入block中,进行指针拷贝,并没有对self进行持有
静态变量持有
// staticSelf_定义:
static ViewController *staticSelf_;
- (void)blockWeak_static {
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
}
weakSelf虽然是弱引用,但是staticSelf_静态变量,并对weakSelf进行了持有,staticSelf_释放不掉,所以weakSelf也释放不掉!导致循环引用!
BLock的实现是基于指针和函数指针,Block属性是指向结构体的指针
//这是正常的block流程
void(^myBlock)(void) = ^{
printf("myBlock");
};
myBlock();
得到Block的结构的源码定义(C++):
void(*myBlock)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
初始化定义部分block语法变成了:&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
调用block的过程变成了:(__block_impl *)myBlock)->FuncPtr
初始化部分就是Block结构体
//Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
impl.isa = &_NSConcreteStackBlock; //
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__main_block_impl_0结构体也就是Block结构体包含了三个部分:
struct __block_impl结构:包含Block实际函数指针的结构体
struct __block_impl {
void *isa;//用于保存Block结构体的实例指针
int Flags;//标志位
int Reserved;//今后版本升级所需的区域大小
void *FuncPtr;//函数指针
};
_block_impl
包含了Block实际函数指针FuncPtr,FuncPtr指针指向Block的主体部分,也就是Block对应OC代码中的^{…}的部分Reserved
__block_impl
结构体的实例指针isa
struct __main_block_desc_0结构:
Block附加信息结构体:包含今后版本升级所需区域的大小,Block的大小
static struct __main_block_desc_0 {
size_t reserved;//今后版本升级所需区域大小
size_t Block_size;//Block大小
} __main_block_desc_0_DATA = { 0, sizeof(struct __main_block_impl_0)};
Block构造函数__main_block_impl_0 :
作为构造函数注意和Block结构体是一个名字。
负责初始化__main_block_impl_0结构体(也就是Block结构体struct __block_impl
)的成员变量
//可以看到里面都是一些赋值操作
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock);
逐步解析这段代码:
((__block_impl *)myBlock)->FuncPtr
:这部分将 myBlock
转换为 __block_impl
指针类型,并访问 FuncPtr
成员。它获取了块实现内部存储的函数指针。((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)
:在这里,函数指针被转换为一个函数类型,该函数接受一个类型为 __block_impl*
的参数,并返回 void
。它将函数指针转换为可以调用的实际函数类型。((void (*)(__block_impl *))((__block_impl *)myBlock)->FuncPtr)((__block_impl *)myBlock)
:最后,使用 myBlock
作为参数,调用了所得到的函数指针。它使用块实现对象调用该函数。总结一下,该代码获取了存储在块实现内部的函数指针,然后调用该函数,将块实现对象自身作为参数传递进去。
Block捕获变量的时候 结构体里面多了 *NSObject obj; 以便Block去捕获。
变量
对象
BLOCK 可以捕获对象,其中需要知道两个方法。
在捕获对象的时候代码出现了_main_block_copy_0
和 _main_block_depose_0
。
__main_block_copy_0
作用就是调用_Block_object_assign
,相当于retain
,将对象赋值在对象类型的结构体变量__main_block_impl_0
中。在栈上的Block复制到堆时会进行调用。__main_block_dispose_0
调用_Block_object_dispose
,相当于release
,释放赋值在对象类型的结构体变量中的对象。在堆上的Block被废弃时会被调用。Block内存分析
block 在使用的时候,堆block注意作用域的问题,弱引用指针赋值,出了作用域就释放了,可以通过强引用解决
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSMutableArray *array = [NSMutableArray array];
Block block = ^{
[array addObject: @"20"];
[array addObject: @"30"];
NSLog(@"%@",array);
};
block();
}
return 0;
可以正确执行,因为在block
块中仅仅是使用了array的内存地址,往内存地址中添加内容,并没有修改arry的内存地址,因此array不需要使用__block修饰也可以正确编译。
‼️ 因此当仅仅是使用局部变量的内存地址,而不是修改的时候,尽量不要添加__block,通过上述分析我们知道一旦添加了__block修饰符,系统会自动创建相应的结构体,占用不必要的内存空间。
原理: 首先被__block修饰的age变量声明变为名为age的__Block_byref_age_0结构体,也就是说加上__block修饰的话捕获到的block内的变量为__Block_byref_age_0类型的结构体
接着将__Block_byref_age_0结构体age存入__main_block_impl_0结构体中,并赋值给__Block_byref_age_0 *age;
之后调用block,首先取出__main_block_impl_0中的age,通过age结构体拿到__forwarding指针,__forwarding中保存的就是__Block_byref_age_0结构体本身,这里也就是age(__Block_byref_age_0),在通过__forwarding拿到结构体中的age(10)变量并修改其值。
总结:__block为什么能够修改变量的值是因为__block把变量包装成了一个带有指针的对象,然后把age封装在结构体里面,block内部存储的变量为结构体指针,也可以通过指针找到内存地址修改变量的值。
Block本质是一个对象,使用结构体实现。
//Block结构体
struct __main_block_impl_0 {
struct __block_impl impl;//impl:Block的实际函数指针,就是指向包含Block主体部分的__main_block_func_0结构体
struct __main_block_desc_0* Desc;//Desc指针,指向包含Block附加信息的__main_block_desc_0()结构体
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {//__main_block_impl_0:Block构造函数(可以看到都是对上方两个成员变量的赋值操作)
impl.isa = &_NSConcreteStackBlock; //
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
__block作用:可以获取对应变量的指针,使其可以在block内部被修改。
__block的数据结构如下
struct __Block_byref_a_0 {
void *__isa;//有isa,是一个对象
__Block_byref_a_0 *__forwarding;//指向自身类型对象的指针
int __flags;//不用关心
int __size;//自己所占大小
int a;//被封装的 基本数据类型变量
};
因为Block的内存地址显示在栈区,栈区的特点就是创建的对象随时销毁,一旦销毁后续再次调用空对象就会造成程序崩溃。
对Block进行copy操作之后,block存在堆区,所以在使用Block属性的时候Copy修饰。
堆中的block,也就是copy修饰的block。他的生命周期就是随着对象的销毁而结束的。只要对象不销毁,我们就可以调用的到在堆中的block。
这就是为什么我们要用copy来修饰block。因为不用copy修饰的访问外部变量的block,只在他所在的函数被调用的那一瞬间可以使用。之后就消失了。
注意:ARC的Block一般都是在堆上,因为系统默认堆Block进行copy操作。 不论ARC还是MRC 用copy Strong修饰Block都是堆Block。