1.概述
定义: Block是将函数及其执行上下文封装起来的对象。 特点:
- 可以嵌套定义,定义Block的方法和定义函数的方法相似;
- Block可以定义在方法内部或外部;
- 只有调用Block时,才会执行{}中的代码;
- 本质是对象,是代码高聚合;
优点:捕获外部变量和降低代码分散程度。 缺点:引起循环引用造成内存泄漏。
使用:
代码语言:javascript复制声明
typedef int (^myBlock)(int , int);
定义属性
@property (nonatomic,copy) myBlock myOneBlock;
使用
self.myOneBlock = ^int(int a , int b) {
return a b;
};
2.Block内存管理
(1).block种类 全局块(NSGlobalBlock):存在于全局内存中, 相当于单例. 栈块(NSStackBlock):存在于栈内存中, 超出其作用域则马上被销毁. 堆块(NSMallocBlock):在于堆内存中, 是带引用计数的对象, 需要自行管理其内存.
(2).判断block储存位置
NSGlobalBlock:不访问外界变量,block就位于全局区,此时对NSGlobalBlock的retain、copy操作都无效。
代码语言:javascript复制void(^myBlock)(void) = ^{
NSLog(@"没有访问外部变量,在全局区");
};
myBlock();
NSLog(@"%@",[myBlock class]);
//__NSGlobalBlock__
NSStackBlock: 在ARC环境下,当我们声明并且定义了一个block,当该block访问外界变量时,系统帮我们完成了copy操作:NSStackBlock转变到NSMallocBlock的过程,将栈区的block移到堆区来延长block的生命周期。但使用了__weak或者__unsafe__unretained的修饰符,那么系统就不会为我们做copy的操作,不会将其迁移到堆区。
代码语言:javascript复制 int a = 10;
__weak void(^myBlock)(void) = ^{
NSLog(@"访问外部变量,用__weak修饰,在栈区%d",a);
};
myBlock();
NSLog(@"%@",[myBlock class]);
//__NSStackBlock__
NSMallocBlock: 在ARC环境下,__strong修饰的(默认)block只要捕获了外部变量就会位于堆区。
代码语言:javascript复制 int a = 10;
void(^myBlock)(void) = ^{
NSLog(@"访问外部变量,用默认__strong修饰,在堆区%d",a);
};
myBlock();
NSLog(@"%@",[myBlock class]);
//__NSMallocBlock__
3.block访问外部变量
1.截获局部变量 (1).默认情况
代码语言:javascript复制 int c= 10;
self.MyBlock = ^(int a , int b) {
return a b c;
};
c= 15;
NSLog(@"%d",self.MyBlock(10,10));
//结果:30
(2).用__block 或static
代码语言:javascript复制static int c= 10; 或
__block int c= 10;
self.MyBlock = ^(int a , int b) {
return a b c;
};
c= 15;
NSLog(@"%d",self.MyBlock(10,10));
//结果:35
总结:局部变量放在堆区,默认情况下block只copy变量的数值,所以只能访问不能修改。对于__block ,block是复制引用地址来实现访问,所以即可访问也可修改局部变量的值,用static修饰的静态变量也是以指针形式访问。
代码语言:javascript复制-------------------------默认----------------
int c= 10; //默认
NSLog(@"block定义前c地址:%p",&c);
self.MyBlock = ^(int a , int b) {
NSLog(@"block定义内部c地址:%p",&c);
return a b c;
};
c= 15;
NSLog(@"block定义后c地址:%p",&c);
NSLog(@"%d",self.MyBlock(10,10));
结果:
block定义前c地址=0x16ef938ac
block定义后c地址=0x16ef938ac
block定义内部c地址=0x2834343e0
总结:
block定义前:c在栈区
block定义内部:里面的c是根据外面的c拷贝到堆中的,不是一个c
block定义后:c在栈区
--------------------用__block 或static---------------------
__block int c= 10;
NSLog(@"block定义前c的地址:%p",&c);
self.MyBlock = ^(int a , int b) {
NSLog(@"block定义内部c的地址:%p",&c);
return a b c;
};
c= 15;
NSLog(@"block定义后c的地址:%p",&c);
NSLog(@"%d",self.MyBlock(10,10));
结果:
block定义前c的地址:0x16ed838a8
block定义后c的地址:0x282438d58
block定义内部c的地址:0x282438d58
总结:
声明 c 为 __block (__block 所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。)
block定义前:c在栈中。
block定义内部: 将外面的c拷贝到堆中,并且使外面的c和里面的c是一个。
block定义后:外面的c和里面的c是一个。
block调用前:c的值还未被修改。
block调用后:c的值在block内部被修改。
2.全局变量 全局变量和静态全局变量都可访问和修改,不被截获。
4.循环引用
对象强引用Block,而Block又持有这个对象,这样就会产生循环引用。打破循环引用的方法:持有对象的属性进行一个弱引用。
解决方法: (1). weak strong 强弱引用(也是最常用的方法)
代码语言:javascript复制 __weak typeof(self) weakSelf = self;
self.MyBlock = ^(int a , int b) {
NSLog(@"%@",weakSelf.str);
};
(2). __block nil 临时变量
代码语言:javascript复制 __block TestViewController*weakSelf = self;
self.MyBlock = ^(int a , int b) {
NSLog(@"%@",weakSelf.str);
weakSelf = nil;//一定要把临时变量销毁
};
self.MyBlock(10, 10);
(3). block加参
代码语言:javascript复制 self.MyBlock = ^(TestViewController*weakSelf) {
NSLog(@"%@",weakSelf.str);
};
self.MyBlock(self);
6.面试问题点
(1).什么情况使用__block修饰符? 一般情况下,对被截获变量进行赋值操作需要添加__block修饰符 赋值 != 使用。
使用:
代码语言:javascript复制NSMutableArray *array = [[NSMutableArray alloc]init];
void(^myBlock)(void) = ^{
[array addObject:@"123"];
};
myBlock();
总结:block 里只是对数组的操作,不是赋值,不需要用__block修饰.
赋值:
代码语言:javascript复制__block NSMutableArray *array = nil;
void(^myBlock)(void) = ^{
array = [[NSMutableArray alloc]init];
};
myBlock();
总结:要用__block修饰,因为数组是赋值操作。
(2).为什么修饰block要用copy? 当我们使用block作为一个对象的属性,需要用copy来修饰block,因为它在栈区,函数执行完会立即释放,block只有经过copy才会从栈区移到堆区,我们就可以自己控制block的生命周期。 Block copy操作之后
(3).gcd的block什么时候销毁?
代码语言:javascript复制Person *person = [[Person alloc]init];
person.name = @"hello";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",person.name);
});
Person.m
-(void)dealloc{
NSLog(@"person被销毁");
}
hello
person被销毁
总结:gcd的block默认会做copy操作,即dispatch_after的block是堆block,block会对Person强引用,block销毁时候Person才会被释放。
代码语言:javascript复制 Person *person = [[Person alloc]init];
person.name = @"hello";
__weak Person*weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",weakPerson.name);
});
person被销毁
(null)
总结:使用__weak修饰过后的对象,堆block会采用弱引用,所以在函数结束后,Person就会被释放,5秒后gcd就无法捕捉到Person。
代码语言:javascript复制Person *person = [[Person alloc]init];
person.name = @"hello";
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"1-------%@",person.name);
__weak Person*weakPerson = person;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3.0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"2——————————%@",weakPerson.name);
});
});
1-------hello
person被销毁
2——————————(null)
总结:gcd内部只要有强引用Person,Person就会等待执行完再销毁,不会等待后执行的弱引用,会直接释放的。
(4).为什么UIView 的 animation动画块使用了Block,内部使用self不会循环引?
代码语言:javascript复制 [UIView animateWithDuration:2.0 animations:^{
NSLog(@"%@",self.str);
}];
因为UIView 动画块是类方法,不被self持有,所以不会循环引用。