前言
说起block,想必作为一名iOS开发人员,不可能没有接触过,但是用的多不代表你就真正懂了,本篇的目的也就是巩固一下对于block的学习,以及一些坑点和面试题进行分析,看我们到底有多懂block😄
一.block的分类
结合对于block的分类,我们分别把三种block展示出来,代码如下
NSGlobalBlock
void (^block)(void) = ^{
NSLog(@"YCX");
};
NSLog(@"%@",block);
打印结果
NSMallocBlock
int a = 10;
void (^block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
NSLog(@"%@",block);
打印结果
NSStackBlock
int a = 10;
void (^ __weak block)(void) = ^{
NSLog(@"Cooci - %d",a);
};
NSLog(@"%@",block);
打印结果
说明:前面也说了堆栈block区别在于是否被强引用或者赋值给copy修饰的属性变量,void (^ block)(void) =不就是一个引用的过程吗,而下面第三个例子之所以是栈block,主要是用__weak修饰了,弱引用没有增加引用计数而已
二.block的引用问题
1.block拷贝到堆block
- 1.手动copy
- 2.block作为返回值
- 3.被强引用或者copy修饰
- 4.系统API包含usingBlock
2.循环引用
正常情况下
为什么会产生循环引用,如下图所示
3.解决循环引用的方案
案例:常见的循环引用 self持有block block持有self
self.name = @"ycx";
self.block = ^{
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",self.name);
});
};
解决方案1
- (void)block1{
__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();
}
解析:这种方式也是最常见的方式,俗称强弱共舞,通过__weak和__strong,从而达到改变引用关系,同时改变作用域,使得只有在block内容执行完毕后,才可以释放self
解决方案2
__block ViewController *vc = self;
self.block = ^(void){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
vc = nil;
});
};
self.block();
解析:通过__block修饰的临时变量vc去引用self,所以只有当block里面执行完毕,并且vc置空,才可以正常释放self
解决方案3
self.block = ^(ViewController *vc){
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
NSLog(@"%@",vc.name);
});
};
self.block(self);
解析:通过传参的方式,其实和方案2原理有些相似,只不过通过传参的方式,那么vc的作用域默认就是block代码块的范围,执行完毕后,vc置空,自然就可以正常释放self了
当然了,还有其他的方案可以解决,后续会补上的
三.面试题分析
例题1
- (void)blockDemo2{
NSObject *objc = [NSObject new];
NSLog(@"%ld",CFGetRetainCount((__bridge CFTypeRef)(objc))); // 1
void(^strongBlock)(void) = ^{ // 1 - block -> objc 捕获 + 1 = 2
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
strongBlock();
void(^__weak weakBlock)(void) = ^{ // + 1
NSLog(@"---%ld",CFGetRetainCount((__bridge CFTypeRef)(objc)));
};
weakBlock();
void(^mallocBlock)(void) = [weakBlock copy];
mallocBlock();
void(^mBlock)(void) = [strongBlock copy];
mBlock();
}
解析:打印结果是13455,首先一点每一个block里面都有引用objc,所以每个地方都会是引用计数+1,这是没问题的,但是为什么strongBlock里面会是引用计数+2呢?在这里我们要明白,block本身是栈block,只不过它的代码块当中引用了objc,所以才使得objc引用计数+1,但是因为栈block又被strongBlock所持有,也就是所谓的copy修饰的变量持有,意味着又拷贝了一份到了堆中,也就是堆block,因此使得引用计数再+1,而weakBlock本身是栈block,但是下面的mallocBlock又对weakBlock进行了一次copy,所以计数再+1,其实strongBlock完全可以看成下面2步操作的结合,通过这题也就充分说明了堆栈block的区别了,而最后的strongBlock copy为什么还是5呢,因为此时strongBlock在堆和栈都有一份了,而block内容不可变,所以本质上copy其实没有真正的再去copy一份代码块,只是简单的引用同一份代码块,也就是说strongBlock和mBlock指向的代码块地址是相通的,可以说是系统做得优化,当然你也可以不可变字符串的copy方式去理解
例题2
// 底层 参考底层仿写的block
struct _LGBlock {
void *isa;
volatile int32_t flags; // contains ref count
int32_t reserved;
// 函数指针
LGBlockInvokeFunction invoke;
struct _LGBlockDescriptor1 *descriptor;
};
- (void)blockDemo1{
int a = 0;
void(^ __weak weakBlock)(void) = ^{
NSLog(@"-----%d", a);
};
struct _LGBlock *blc = (__bridge struct _LGBlock *)weakBlock;
// 深浅拷贝
id __strong strongBlock = weakBlock;
//id __strong strongBlock = [weakBlock copy];
blc->invoke = nil;
void(^strongBlock1)(void) = strongBlock;
strongBlock1();
}
解析:这题主要是为了区分block的深浅拷贝问题,_LGBlock只是参考block底层源码,写的一个自定义block,为的是便于修改block的一些属性,上面demo方法运行会崩溃,因为strongBlock1引用的strongBlock,而strongBlock引用weakBlock,也就是说他们持有的是同一个block,而blc->invoke = nil,是把block中的一个属性置空了,自然就会运行block崩溃,如果该用注释的copy方法,就可以正常运行
例题3
问题:a能不能打印出来,weakBlock,strongBlock分别是什么类型block
- (void)blockDemo3{
int a= 10;
void(^__weak weakBlock)(void) = nil;
{
void(^__weak strongBlock)(void) = ^{
NSLog(@"---%d", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
解析:可以,首先weakBlock,局部变量的作用域为blockDemo3方法中,而strongBlock也是一个栈block,而weakBlock弱引用了strongBlock,所以两者都是栈block,虽然出了括号,但是因为weakBlock还没出作用域,所以可以执行
例题4
问题同例题3
- (void)blockDemo3{
int a= 10;
void(^__weak weakBlock)(void) = nil;
{
void(^ strongBlock)(void) = ^{
NSLog(@"---%d", a);
};
weakBlock = strongBlock;
NSLog(@"1 - %@ - %@",weakBlock,strongBlock);
}
weakBlock();
}
解析:首先可以确定weakBlock和strongBlock都是堆block,而堆block的作用域就是在括号内,所以出了括号就释放了,因此执行堆weakBlock()是会崩溃,当然从这里也可以看出堆栈的释放内存的差异
例题5
问题:是否产生内存泄露
static ViewController *staticSelf;
- (void)viewDidLoad {
[self blockWeak_static];
}
- (void)blockWeak_static {
__weak typeof(self) weakSelf = self;
staticSelf = weakSelf;
}
解析:会,首先weakSelf和self是映射关系,弱引用,指向同一片内存空间,然而全局静态变量staticSelf引用了weakSelf,其实也就是引用了这块内存空间,但是全局静态变量没办法释放,所以self也释放不掉
例题6
问题:是否产生内存泄露
@property (nonatomic, copy) YCXBlock doWork;
@property (nonatomic, copy) YCXBlock doStudent;
- (void)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();
}
解析:会,可以把这个block代码拆分看,首先按照我们的理解__weak和__strong,延迟了self的释放是没问题的,但是在weakSelf.doStudent的block中,再次引用了strongSelf,所以引用计数再次+1,所以最终self还是多引用了一次,导致最终无法释放。
今天的分享就到次为止了,当然了后续如果有更多比较有趣的block题目也会补充进来,下一篇会对block的底层源码进行分析,敬请期待吧😄