Block本质
- block本质也是一个OC对象,内部也有一个
isa
指针 - block是封装了函数调用以及函数调用环境的OC对象
函数调用: 函数地址,函数通过函数地址调用.
函数调用环境: 函数参数,返回值.
image.png
int age = 10;//局部变量
void (^block)(void) = ^{
NSLog(@"age is %d",age);//打印结果为10
}
age = 20;
block();
Block变量捕获
变量类型 | 是否捕获到Block内部 | 访问方式 |
---|---|---|
局部变量 auto | YES | 值传递 |
局部变量 static | YES | 指针传递 |
全局变量 | NO | 直接访问 |
局部变量:在函数内部定义的变量
全局变量:在函数外部定义的变量
宏是字符串替换.
auto变量:离开作用域会自动销毁
static变量:全局变量 不会销毁.
//全局变量 不会捕获 直接访问
static number = 10;
//局部auto变量,值传递,局部变量会随时销毁,所以捕获到block内部.
int age = 10;//等价代码auto int age = 10;
//局部static变量;地址传递.局部变量会随时销毁,所以捕获到block内部.
static height = 10;
void (^block)(void) = ^{
NSLog(@"age is %d",age);//打印结果为10,值传递.捕获
NSLog(@"height is %d", height);//打印结果为20.地址传递,捕获
NSLog(@"number is %d", number);//打印结果为20,直接访问,不捕获
}
age = 20;
height = 20;
number = 20;
block();
局部变量:栈段
全局变量:数据段
对象:堆
类对象:数据段
实例:
//LDPerson类中的test方法的实现
- (void) test{
//会捕获self对象,因为self是函数参数,函数
//参数是局部变量,局部变量会捕获到block中
//并且以值传递的方式捕获
void (^block)(void) = ^{
NSLog(@"%@",self);
}
block();
}
//LDPerson有一个name属性
- (void) test{
void (^block)(void) = ^{
//会捕获self对象,因为self是函数参数,函数
//参数是局部变量,局部变量会捕获到block中
//并且以值传递的方式捕获.捕获self之后,通过self->name来访问name属性
NSLog(@"%@",_name);
}
block();
}
self是局部变量不是全局变量.
Block类型:
void (^block)(void) = ^{
NSLog(@"111");
};
NSLog(@"%@",[block class]);
NSLog(@"%@",[[block class] superclass]);
NSLog(@"%@",[[[block class] superclass] superclass]);
NSLog(@"%@",[[[[block class] superclass] superclass] superclass]);
//打印结果:
2018-08-30 16:09:50.907192+0800 全局变量[27597:5794698] __NSGlobalBlock__
2018-08-30 16:09:50.907403+0800 全局变量[27597:5794698] __NSGlobalBlock
2018-08-30 16:09:50.907422+0800 全局变量[27597:5794698] NSBlock
2018-08-30 16:09:50.907436+0800 全局变量[27597:5794698] NSObject
可以看到block存在继承结构.是一个OC对象.
- NSGlobalBlock ( _NSConcreteGlobalBlock )//数据段
- NSStackBlock ( _NSConcreteStackBlock )//栈
- NSMallocBlock ( _NSConcreteMallocBlock )//堆
内存分区
应用程序内存分配 |
---|
代码段 |
数据段 |
堆 |
栈 |
Block分类:
block类型 | 环境 |
---|---|
NSGlobalBlock | 没有访问auto变量 |
NSStackBlock | 访问了aotu变量 |
NSMallocBlock | NSStackBlock 调用了copy |
每一种类型的block调用copy后的结果如下所示
block类型 | 副本源的存储区 | 复制效果 |
---|---|---|
NSGlobalBlock | 数据段 | nothing |
NSStackBlock | 栈 | 从栈复制到堆 |
NSMallocBlock | 堆 | 引用计数增加 |
ARC模式下编译器对Block自动进行copy操作的情况
- block作为函数返回值时
- block有强指针引用时会自动进行
copy
操作(strong修饰时) - block作为
Cocoa
API中方法名含有usingBlock
的方法参数时 - block作为
GCD
API的方法参数时
编译器会自动对block进行copy(堆blockMallocBlock
)
block属性建议写法
- MRC
@property (copy,nonatomic) void (^block) void
; - ARC
@property (copy,nonatomic) void (^block) void
;
@property (strong,nonatomic) void (^block) void
;
对象类型的auto变量
当block内部访问了对象类型的auto变量时:
- 如果是栈block,不会对auto变量产生强引用.
- 如果是堆block,会对Strong引用的auto变量产生强引用.
如果block被拷贝到堆上,会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数,_Block_object_assign函数会根据auto变量的修饰符(__strong、__weak、__unsafe_unretained)做出相应的操作,形成强引用(retain)或者弱引用
3.如果block从堆上移除,会调用block内部的dispose函数,dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的auto变量(release)
函数 | 调用时机 |
---|---|
copy函数 | 栈Block复制到堆时 |
dispose函数 | 堆上的Block被废弃时 |
代码示例:
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
LDPerson * person = [[LDPerson alloc] init];
NSLog(@"1111");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after 三秒后 执行的 代码");
NSLog(@"%@",person);
});
NSLog(@"touchesBegan 执行完成");
}
- GCD的代码中含有Block,会对该block进行
copy
操作.所以该Block是堆block - 堆block 引用了局部变量
Person
,且该Person
对象被强指针修饰,所以block会对该对象进行强引用. - 所以现在的情况是,堆block对
person
对象是强引用.当block销毁时,person
对象才会销毁(release操作)
.
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event{
LDPerson * person = [[LDPerson alloc] init];
__weak LDPerson *weakPerson = person;
NSLog(@"1111");
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"dispatch_after 三秒后 执行的 代码");
NSLog(@"%@", weakPerson);
});
NSLog(@"touchesBegan 执行完成");
}
- 堆block不会堆用weak修饰的person产生强引用,person会立刻释放.
__block内存管理
__Block
-
__block可以用于解决block内部无法修改auto变量值的问题
-
__block不能修饰全局变量、静态变量(static)
-
编译器会将__block变量包装成一个对象
image.png -
当block在栈上时,并不会对__block变量产生强引用
-
当block被copy到堆时
会调用block内部的copy函数,copy函数内部会调用_Block_object_assign函数. _Block_object_assign函数会对__block变量形成强引用(retain)
image.png -
当block从堆中移除时
会调用block内部的dispose函数.dispose函数内部会调用_Block_object_dispose函数, _Block_object_dispose函数会自动释放引用的__block变量(release).
image.png
__block的__forwarding指针
image.png代码示例:
/**
ARC下 copy/strong 修饰都可以,
MRC下 copy
*/
@property (nonatomic,copy) void (^block) (void);
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
//block引用.局部变量,会捕获到结构体变量里面.值传递.不会对a引用
auto int a = 10;
//block引用.局部变量.且是OC对象类型会捕获到结构体变量里面.地址传递
//该obj对象为强指针修饰.block对obj强引用,[obj retain]
NSObject * obj = [[NSObject alloc] init];
//弱指针局部对象变量.会捕获到结构体变量里面.地址传递
//block对obj弱引用
__weak NSObject * weakObj = obj;
//__block 修饰的局部变量,在block内部才能被修改.
//__block int b 会将b包装成一个OC对象.
//__block 修饰的变量,block都会对其产生强引用
__block int b = 10;
//强引用block,会进行copy操作,所以该block是堆block
NSMutableArray * arr = [NSMutableArray array];
__block NSMutableArray * array = [NSMutableArray array];
self.block = ^{
b = 30;
NSLog(@"%d",a);
NSLog(@"%@",obj);
NSLog(@"%@",weakObj);
//此时没有修改arr指针的值,所以不需要__block修饰
[arr addObject:@""];
//此时修改了block外部变量,需要用__block
//Variable is not assignable (missing __block type specifier)
array = nil;
};
self.block();
}
对象类型的auto变量、__block变量
-
当block在栈上时,对它们都不会产生强引用
-
当block拷贝到堆上时,都会通过copy函数来处理它们
__block变量(假设变量名叫做a)
_Block_object_assign((void*)&dst->a, (void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
-
对象类型的auto变量(假设变量名叫做p)
_Block_object_assign((void*)&dst->p, (void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
-
当block从堆上移除时,都会通过dispose函数来释放它们
__block变量(假设变量名叫做a)
_Block_object_dispose((void*)src->a, 8/*BLOCK_FIELD_IS_BYREF*/);
-
对象类型的auto变量(假设变量名叫做p)
_Block_object_dispose((void*)src->p, 3/*BLOCK_FIELD_IS_OBJECT*/);
block内部访问变量类型 | 修饰关键字 |
---|---|
对像 | BLOCK-FIELD_ISOBJECT |
__block 变量 | BLOCK_FIELD_IS-BYREF |
被__block修饰的对象类型
image.png此时person为局部变量,代码29行之后person释放.
image.png
此时person为局部变量,block对
person
强引用.
网友评论