前言
本篇将会介绍block
的类型、循环引用
和一些相关的面试题,并在下一篇文章对这些上层表现用底层源码进行验证和分析。
一、block 的类型
GlobalBlock
- 位于全局区
- 在
Block
内部不使用外部变量,或者只使用静态变量和全局变量
MallocBlock
- 位于堆区
- 在
Block
内部使用局部变量或者OC
属性,并且赋值给强引用或者Copy
修饰的变量
StackBlock
- 位于栈区
- 与
MallocBlock
一样,可以在内部使用局部变量或者OC
属性,但是不能赋值给强引用或者Copy
修饰的变量
GlobalBlock
示例:
- (void)viewDidLoad {
void (^block)(void) = ^{
};
NSLog(@"%@",block);
}
打印结果:
<__NSGlobalBlock__: 0x104171100>
MallocBlock
示例:
- (void)viewDidLoad {
int a = 18;
void (^block)(void) = ^{ // 默认强引用
NSLog(@"ssl - %d",a);
};
NSLog(@"%@",block);
}
打印结果:
<__NSMallocBlock__: 0x6000012b5ce0>
StackBlock
示例:
- (void)viewDidLoad {
int a = 18;
void (^__weak block)(void) = ^{ // 弱引用
NSLog(@"ssl - %d",a);
};
NSLog(@"%@",block);
}
打印结果:
<__NSStackBlock__: 0x7ffee5569408>
二、block 面试题
1)
- (void)blockDemo {
// 第一次
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
// 第二次
void(^strongBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
// 第三次
void(^__weak weakBlock)(void) = ^{
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
// 第四次
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
}
执行结果:
---1
---3
---4
---5
- 第一次打印,只是初始化,引用计数为
1
。 - 第二次打印,栈
block
对objc
进行捕获引用计数+1
,栈内存赋值到堆内存引用计数再+1
,所以这时引用计数为3
。 - 第三次打印,因为是弱引用,栈
block
对objc
进行捕获引用计数+1
,所以这时引用计数为4
。 - 第四次打印,栈
block
赋值到堆block
引用计数+1
,所以这时引用计数为5
。
2)
- (void)blockDemo {
int a = 18;
void(^__weak block1)(void) = nil;
{
void(^__weak block2)(void) = ^{
NSLog(@"---%d", a);
};
block1 = block2;
NSLog(@"1 - %@ - %@",block1,block2);
}
block1();
}
执行结果:
1 - <__NSStackBlock__: 0x7ffeeb3b33e0> - <__NSStackBlock__: 0x7ffeeb3b33e0>
---18
block2
和block1
都是是栈block
,作用域在blockDemo3
函数内都是有效的,所以block1()
也就可以正常执行。
3),将上面代码中block2
的__weak
修饰符去掉:
block2
和block1
都是是堆block
,block2
在代码块内创建,只在代码块中有效,出了代码块就会销毁,block1
又指向block2
,所以调用block1()
会崩溃。
三、block 循环引用
循环引用 示例代码
typedef void(^SSLBlock)(void);
- (void)viewDidLoad {
[super viewDidLoad];
// 循环引用
self.name = @"ssl";
self.block = ^(void) {
NSLog(@"%@",self.name);
};
// 不循环引用
[UIView animateWithDuration:1 animations:^{
NSLog(@"%@",self.name);
}];
}
- 第一段代码因为
block
和self
相互持有,所以产生了循环引用。 - 第二段代码
self
并没有持有view
,所以不会产生循环应用。
__weak 解决循环引用
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"ssl";
__weak typeof(self) weakSelf = self;
self.block = ^(void) {
NSLog(@"%@",weakSelf.name);
};
}
weakSelf
弱引用self
,引用计数不会加1
,所以即使weakSelf
被block
持有,也不会增加self
的引用计数不会产生循环引用。
如果block
中有异步操作,我们又想让页面返回以后操作还能正常执行,那么就需要加入strongSelf
:
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"ssl";
__weak typeof(self) weakSelf = self;
self.block = ^(void){
__strong __typeof(weakSelf)strongSelf = weakSelf;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",strongSelf.name);
});
};
self.block();
}
- 这里有个疑问的点是
strongSelf
是强引用,它是不是又会引发循环引用呢,不会的因为它是个临时变量,作用域结束就释放了。
手动置 nil 解决循环引用
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"ssl";
__block ViewController *vc = self;
self.block = ^(void) {
NSLog(@"%@",vc.name);
vc = nil;
};
self.block();
}
- 这种方式也可以解决循环引用,但是有个缺点是
block
必须被调用,只有block
被调用了vc
才能被置为nil
,否则还是会产生循环引用。 - 这里必须要使用
__block
否则会报错,下一篇文章将会进行解析。
传参数 解决循环引用
typedef void(^SSLBlock)(ViewController *vc);
- (void)viewDidLoad {
[super viewDidLoad];
self.name = @"ssl";
self.block = ^(ViewController *vc) {
NSLog(@"%@",vc.name);
};
self.block(self);
}
block
会对外部变量进行捕获,这里通过参数传进来,参数不需要进行捕获,所以可以解决循环引用。
四、block 循环引用的面试题
1)
static ViewController *staticSelf_;
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self blockWeak_static];
}
- (void)blockWeak_static {
__weak typeof(self) weakSelf = self;
staticSelf_ = weakSelf;
}
@end
- 这段代码也是内存泄漏的,
weakSelf
是弱引用,但它也是指向了self
的内存空间的只是引用计数不加1
而已,通过staticSelf_ = weakSelf
这段代码,staticSelf_
也是指向了self
的内存空间,这会使引用计数加1
,staticSelf_
是全局静态变量并不会释放,self
也就不会释放因此产生了内存泄漏。
2)
typedef void(^SSLBlock)(void);
@interface ViewController ()
@property (nonatomic, copy) SSLBlock doWork;
@property (nonatomic, copy) SSLBlock doStudent;
@end
@implementation ViewController
- (void)viewDidLoad {
[super viewDidLoad];
[self block_weak_strong];
}
- (void)block_weak_strong {
__weak typeof(self) weakSelf = self;
self.doWork = ^{
__strong typeof(self) strongSelf = weakSelf;
weakSelf.doStudent = ^{
NSLog(@"%@", strongSelf);
};
weakSelf.doStudent();
};
self.doWork();
}
@end
- 这段代码会循环引用,
doWork
内的strongSelf
是当前作用域下的临时变量不会产生循环引用,doStudent
会对strongSelf
进行捕获,因此产生了循环引用,注意删掉weakSelf.doStudent()
一样会循环引用。