1、 block介绍?
1 | //block |
block的标志就是^,所有的block必须以^开头
returnType表示返回值类型,如果没有返回值一般使用void表示
1 | //block变量 |
必须包含blockName并且以^开头,是block的标志
参数列表可以和声明函数一样,只写出形参类型不需写出形参名称
比如:
1 | void (^printBlock)(void) = ^ void(void) { |
注意:
定义block变量的时候不能省略返回值类型、block名称以及形参列表,如果没有参数则用void占位或者不写,这样就能够定义一个block变量。
定义block的时候如果返回值为void可以省略,如果没有形参可以使用void占位或者整个形参列表都省略不写。
简化后:1
2
3void (^printBlock)() = ^{
NSLog(@"Hello World");
};
定义block类型
1 | typedef returnType (^blockTypeName) (parameter1, parameter2, ...) |
2、block有哪几类?
NSGlobalBlock
如果block不捕获外部变量,那么在ARC环境下就是创建一个全局block。全局block存储在全局内存中,不需要在每次调用的时候都在栈中创建,块所使用的整个内存区在编译期已经确定了,因此这种块是一种单例,不需要多次创建。
NSMallocBlock
如果block捕获外部变量,那么在ARC环境下就是创了一个堆区block。代码中最常用的block也就是堆区block,当堆区block的引用计数为0时也会像普通对象一样被销毁,再也不能使用了。
NSStackBlock
在MRC环境下,默认创建栈区block,一般使用copy函数拷贝到堆区再使用,否则block可能会被释放,在ARC环境下一般不考虑。
3、block捕获变量是怎么回事?
1 | NSUInteger age = 22; |
在执行block之前修改变量的值,并不影响最终block内部代码,这是由于在定义block块的时候编译器已经在编译期将外部变量值赋给了block内部变量(称为“值捕获”),在这时候进行了一次值拷贝,而不是在运行时赋值,因此外部变量的修改不会影响到内部block的输出。
简单理解的话:就是传递了形参给block
变量类型是不可变对象的时候也是一样的,block里捕获的是age当时的指针,所以变量还是指向@”22”,就像函数传了形参进来一样。1
2
3
4
5
6
7
8
9
10
11
12NSString *age = @"22";
void (^printBlock)() = ^ {
NSLog(@"My Age is %@", age);
//age = @"33"; // 这里会报错,因为block内部不允许修改变量的值
};
//输出 Age is 22
NSLog(@"Age is %@", age);
age = @"100";
//输出 Age is 100
NSLog(@"Age is %@", age);
//输出 My Age is 22
printBlock();
但是如果捕获的是一个可变类型,也可以理解为像形参一样传进来,外边修改也会影响到里边
1 | //定义一个NSMutableString类型的变量 |
如果希望block捕获的变量在外部修改后也可以影响block内部,或是想在block内部修改捕获的变量,可以使用__block关键字定义变量。
简单理解,可以理解传递了参数的指针进来
1 | //使用__block关键字定义age |
上述代码使用__block定义变量age,这样定义以后编译器会在block定义的时候捕获变量的引用而不是拷贝一个值
4、block 循环引用是怎么回事?如何解决?
循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。
常见的循环引用场景:
1、NSTimer
NSTimer 的 target 对传入的参数都是强引用, 如果这时候target再持有了 timer 就会循环引用
解决方法:
不要在 target的 dealloc里清理timer,应该单独的清理timer方法,打破引用
可以不要强引用target
1 | #import <Foundation/Foundation.h> |
注意 :千万不要忘记 invalidate
2、delegate
正常声明delegate1
2
3
4
5如果用 strong ,就可能循环引用
A 类有个属性是 delegate B 有个成员 是A的对象, 并且把A的delegate 设置成 B, 这样就会相互依赖,
3、block
@interface ClassA ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, assign) NSInteger tem;
@end
@implementation ClassA
- (void)viewDidLoad {
[super viewDidLoad];
self.block = ^{
};self.tem = 1;
}1
2
3self持有block,block里又用到self的时候,就会相互持有,导致循环引用
一般编译器会提示警告⚠️
一般通过打破循环来解决:
@interface ClassA ()
@property (nonatomic, copy) dispatch_block_t block;
@property (nonatomic, assign) NSInteger tem;
@end
@implementation ClassA
(void)viewDidLoad {
[super viewDidLoad];
__weak typeof(self) weakSelf = self
self.block = ^{weakSelf.tem = 1;
};
}1
2
但是这种解决方法在个别情况下会有问题:__weak typeof(self) weakSelf = self;
self.block = ^{dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ NSLog(@"%@", weakSelf.str); });
};
self.block();1
2
3当10s后执行NSLog的时候weakself内存已经被销毁,所以打印为null
更好的解决方法:
weak typeof(self) weakSelf = self;
self.block = ^{
strong typeof(self) strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(10 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@”%@”, strongSelf.str);
});
};
self.block();1
2
3
4
5
6
7
8
外部的weakSelf是为了打破环,从而使得没有循环引用,而内部的strongSelf仅仅是个局部变量,会在block执行结束销毁,不会有循环引用。
strongSelf 会使self的引用计数+1,所以self不会被立刻销毁,当block执行完self才会被销毁。
这样写很麻烦,当日也可以使用 @weakify和@strongify 来简化
4、类与类之间的引用
比如: 在使用UITableView 的时候,将 UITableView 给 Cell 使用,cell 中的 strong 引用会造成循环引用。
// controller
- (UITableViewCell )tableView:(UITableView )tableView cellForRowAtIndexPath:(NSIndexPath )indexPath {
TestTableViewCell cell =[tableView dequeueReusableCellWithIdentifier:@”UITableViewCellId” forIndexPath:indexPath];
cell.tableView = tableView;
return cell;
}
// cell
@interface TestTableViewCell : UITableViewCell
@property (nonatomic, strong) UITableView *tableView; // strong 造成循环引用
@end1
2
3
4
5
6
7解决方法就是:strong 改为 weak
# 5、为什么一些系统提供的block不需要考虑循环引用?
比如:
```+ (void)animateWithDuration:(NSTimeInterval)duration delay:(NSTimeInterval)delay options:(UIViewAnimationOptions)options animations:(void (^)(void))animations completion:(void (^ __nullable)(BOOL finished))completion NS_AVAILABLE_IOS(4_0);
因为是类方法 block里虽然可能引用self,但是self 并没有引用对象
6、使用系统的某些block api(如UIView的block版本写动画时),是否也考虑引用循环问题?
系统的某些block api中,UIView的block版本写动画时不需要考虑,但也有一些api 需要考虑:
所谓“引用循环”是指双向的强引用,所以那些“单向的强引用”(block 强引用 self )没有问题,比如这些:1
2
3
4
5
6
7[UIView animateWithDuration:duration animations:^{ [self.superview layoutIfNeeded]; }];
[[NSOperationQueue mainQueue] addOperationWithBlock:^{ self.someProperty = xyz; }];
[[NSNotificationCenter defaultCenter] addObserverForName:@"someNotification"
object:nil
queue:[NSOperationQueue mainQueue]
usingBlock:^(NSNotification * notification) {
self.someProperty = xyz; }];
这些情况不需要考虑“引用循环”。
但如果你使用一些参数中可能含有 ivar 的系统 api ,如 GCD 、NSNotificationCenter就要小心一点:比如GCD 内部如果引用了 self,而且 GCD 的其他参数是 ivar,则要考虑到循环引用:1
2
3
4
5
6
7__weak __typeof__(self) weakSelf = self;
dispatch_group_async(_operationsGroup, _operationsQueue, ^
{
__typeof__(self) strongSelf = weakSelf;
[strongSelf doSomething];
[strongSelf doSomethingElse];
} );
类似的:1
2
3
4
5
6
7
8__weak __typeof__(self) weakSelf = self;
_observer = [[NSNotificationCenter defaultCenter] addObserverForName:@"testKey"
object:nil
queue:nil
usingBlock:^(NSNotification *note) {
__typeof__(self) strongSelf = weakSelf;
[strongSelf dismissModalViewControllerAnimated:YES];
}];
self –> _observer –> block –> self 显然这也是一个循环引用。