block

1、 block介绍?

block是C语言的扩展

1
2
3
4
5
//block
^ returnType (parameter1, parameter2, parameter3, ...)
{
//block code
}

block的标志就是^,所有的block必须以^开头
returnType表示返回值类型,如果没有返回值一般使用void表示

1
2
//block变量
returnType (^blockName) (parameter1, parameter2, ...);

必须包含blockName并且以^开头,是block的标志
参数列表可以和声明函数一样,只写出形参类型不需写出形参名称

比如:

1
2
3
void (^printBlock)(void) = ^ void(void) {
NSLog(@"Hello World");
};

注意:
定义block变量的时候不能省略返回值类型、block名称以及形参列表,如果没有参数则用void占位或者不写,这样就能够定义一个block变量。

定义block的时候如果返回值为void可以省略,如果没有形参可以使用void占位或者整个形参列表都省略不写。

简化后:

1
2
3
void (^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
2
3
4
5
6
7
8
9
10
11
12
NSUInteger age = 22;
void (^printBlock)() = ^ {
NSLog(@"My Age is %ld", age);
//age = 33; // 这里会报错,因为block内部不允许修改变量的值
};
//输出 Age is 22
NSLog(@"Age is %ld", age);
age = 100;
//输出 Age is 100
NSLog(@"Age is %ld", age);
//输出 My Age is 22
printBlock();

在执行block之前修改变量的值,并不影响最终block内部代码,这是由于在定义block块的时候编译器已经在编译期将外部变量值赋给了block内部变量(称为“值捕获”),在这时候进行了一次值拷贝,而不是在运行时赋值,因此外部变量的修改不会影响到内部block的输出。

简单理解的话:就是传递了形参给block

变量类型是不可变对象的时候也是一样的,block里捕获的是age当时的指针,所以变量还是指向@”22”,就像函数传了形参进来一样。

1
2
3
4
5
6
7
8
9
10
11
12
NSString *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
2
3
4
5
6
7
8
9
10
//定义一个NSMutableString类型的变量
NSMutableString *content = [[NSMutableString alloc] initWithString:@"Jiaming Chen"];
void (^printBlock)() = ^ {
NSLog(@"%@", content);
};
//输出 Jiaming Chen
NSLog(@"%@", content);
[content appendString:@" is a good guy"];
//输出 Jiaming Chen is a good guy
printBlock();

如果希望block捕获的变量在外部修改后也可以影响block内部,或是想在block内部修改捕获的变量,可以使用__block关键字定义变量。
简单理解,可以理解传递了参数的指针进来

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//使用__block关键字定义age
__block NSUInteger age = 22;
void (^printBlock)() = ^ {
NSLog(@"My Age is %ld", age);
age = 200;
};
//输出 Age is 22
NSLog(@"Age is %ld", age);
age = 100;
//输出 Age is 100
NSLog(@"Age is %ld", age);
//输出 My Age is 100
printBlock();
//输出 Age is 200
NSLog(@"Age is %ld", age);

上述代码使用__block定义变量age,这样定义以后编译器会在block定义的时候捕获变量的引用而不是拷贝一个值

4、block 循环引用是怎么回事?如何解决?

循环引用可以简单理解为A引用了B,而B又引用了A,双方都同时保持对方的一个引用,导致任何时候引用计数都不为0,始终无法释放。

常见的循环引用场景:
1、NSTimer
NSTimer 的 target 对传入的参数都是强引用, 如果这时候target再持有了 timer 就会循环引用
解决方法:

不要在 target的 dealloc里清理timer,应该单独的清理timer方法,打破引用

可以不要强引用target

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#import <Foundation/Foundation.h>

@interface NSTimer (YPQBlocksSupport)

+ (NSTimer *)ypq_scheduledTimeWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats;

@end


#import "NSTimer+YPQBlocksSupport.h"

@implementation NSTimer (YPQBlocksSupport)


+ (NSTimer *)ypq_scheduledTimeWithTimeInterval:(NSTimeInterval)interval
block:(void(^)())block
repeats:(BOOL)repeats
{
return [self scheduledTimerWithTimeInterval:interval
target:self
selector:@selector(ypq_blockInvoke:) userInfo:[block copy]
repeats:repeats];
}

- (void)ypq_blockInvoke:(NSTimer *)timer
{
void (^block)() = timer.userInfo;
if(block)
{
block();
}
}

@end

//调用

__weak ViewController * weakSelf = self;
[NSTimer ypq_scheduledTimeWithTimeInterval:4.0f
block:^{
ViewController * strongSelf = weakSelf;
[strongSelf afterThreeSecondBeginAction];
}
repeats:YES];

注意 :千万不要忘记 invalidate

2、delegate

正常声明delegate

(nonatomic, weak) id delegate; ```
1
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
    3
    self持有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 造成循环引用
@end

1
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 显然这也是一个循环引用。

参考资料

iOS block探究(一): 基础详解
iOS block探究(二): 深入理解
iOS中的循环引用
循环引用,看我就对了