面试题
1、什么是Block
Block是将函数及其执行上下文封装起来的对象······ d
2、截获变量
对象型的局部变量连通所有修饰符一起截获
对全局变量、静态全局变量、不截获
3、__block 作用
赋值的时候才会使用,使用不需要
全局 静态 变量 也不需要修饰
3.1 __forwarding 指针是用来干什么的
不论在任何内存位置,都可以顺利的访问一个__block变量
4、Block 分为多少类型
系统把Block分为3类:NSGlobalBlock,NSStackBlock, NSMallocBlock; NSGlobalBlock :位于内存全局区NSMallocBlock :位于内存堆区 NSStackBlock :位于内存栈区
一、Block的本质及结构
Block 本质上也是一个OC对象,内部也有isa指针 Block 是封装了函数调用以及函数调用环境的OC对象
Block 是一个结构体:至少有两个成员 struct __block_impl imp1; 描述信息:struct __main_block_desc_0* Desc;
FuncPtr:指向执行代码的地址
Block_size:block占用多少内存
事例:
int main(){
void(^block)(void) = ^{
printf("CJL");
};
return 0;
}
执行命令,将文件编译成c++代码:
xcrun -sdk iphonesimulator clang -S -rewrite-objc -fobjc-arc -fobjc-runtime=ios-14.2 main.m
c++代码解析:
__main_block_func_:封装了block里面执行的代码
__main_block_desc:结构体的内存空间大小
__main_block_impl_0:方法名字
int main(){
// block 是 __main_block_impl_0 结构体的实例
//定义block变量 类似初始化
void(*block)(void) = ((void (*)())&__main_block_impl_0((void *)__main_block_func_0, &__main_block_desc_0_DATA));
// block代码块中要执行的代码,被存储在FuncPtr成员变量中,
((void (*)(__block_impl *))((__block_impl *)block)->FuncPtr)((__block_impl *)block);
return 0;
}
//**block代码块的结构体类型**
struct __main_block_impl_0 {
struct __block_impl impl; // block 实现
struct __main_block_desc_0* Desc; // block 描述
__main_block_impl_0(void *fp, struct __main_block_desc_0 *desc, int flags=0) {
impl.isa = &_NSConcreteStackBlock;
impl.Flags = flags;
impl.FuncPtr = fp;
Desc = desc;
}
};
//**block的结构体类型**
struct __block_impl {
void *isa; // 指向它的类型
int Flags; // 标志位
int Reserved; // 预留位
void *FuncPtr; // 执行函数
};
static void __main_block_func_0(struct __main_block_impl_0 *__cself) {
printf("CJL");
}
static void __main_block_copy_0(struct __main_block_impl_0 *dst,struct __main_block_impl_0 *src) {
}
总结:
1、将整个block 生成一个结构体 __main_block_impl_0 结构体中,结构体中有两个成员上面有
2、调用结构体的构造函数,参数:(函数地址,函数描述)创造结构体对象,把(printf("CJL"); 代码块)赋值给 FuncPtr
二、block 变量捕获
1、局部变量&补获__block/__strong/__weak原理例子
我们知道,如果 block 内部使用一个局部变量,如果想要修改的话,需要使用__block 来修饰,为什么不修饰就不能修改呢?从源码来看一下:
int main(){
__block int param1 = 0;
__block NSNumber *param6 = @3;
__weak NSNumber *param4 = @3;
__strong NSNumber *param5 = @3;
NSNumber *param7 = @3;
int param3 = 3;
int *param2 = ¶m3;
void(^block)(void) = ^{
printf("CJL-%p", param2);
printf("CJL-%p", param4);
printf("CJL-%p", param5);
printf("CJL-%p", param7);
param6 = @2;
param1 = 10;
};
block();
return 0;
}
生成c++文件:
2、静态变量的捕获:
静态局部变量,捕获的也是地址,但是没有被包装成__block 的内部类型!
3、全局变量捕获
全局变量没有捕获,全局变量一直存在内存中,销毁都销毁了呢!block内部也是直接使用全局变量
4、如果block内部使用 self,会被捕获。__main_block_func_0 会把self作为一个参数传递进去,参数是一个局部变量(self )
总结:
5、为什么要捕获
自动变量可能会被销毁,内存可能会消失释放,block为了安全考虑,而static则不会 static 一直保存在内存中,...
void (^block)(void)
void test() {
int age = 10;
static int height = 10;
//age的值捕获进来(capture)
block = ^ {
NSLog(@"age is %d,height is %d",age ,height)
};
age = 20;
height = 20;
}
int main(int argc,const char *argh) {
@autoreleasepool {
//调用完之后 test 方法中局部变量释放!
test();
block();
}
}
age is 10,height is 20
三、block的三种类型
扩展:内存分段:
代码段:内存最小...
数据段:全局变量、类对象...
堆:allock等,动态分配内存...
栈:局部变量,系统自动分配内存,离开作用域自动释放...
●__NSGlobalBlock__: 全局block, 存储在全局区。
○block 内部使用的 block 外部的变量,只能是(局部或者全局)静态变量和全局变量,不能是外部的局部变量或者是 OC 属性
●__NSMallocBlock__: 堆区block
○block 内部使用了外部的局部变量或者是 OC 属性,并且将这个 block 赋值给了强引用或者用 copy 修饰的变量。
●__NSStackBlock__: 栈区block
○block 内部使用了外部的局部变量或者是 OC 属性,但是没有将这个 block 赋值给强引用或者用 copy 修饰的变量
●一个 block 默认存储在全局区,这时它的内部没有引用任何外部变量或者是只引用了(局部或者全局)静态变量和全局变量
●如果这个 block 内部使用了外部的局部变量或者是 OC 属性, 它会被拷贝到栈区
●在使用了局部变量或者oc属性的前提下,如果这个 block 又被赋值给了一个强引用或者用 copy 修饰的变量 ,那它会被拷贝到堆区。
四、面试题
1、什么场景会触发block copy?
●手动copy
●block 作为返回值,
●接收的变量被 __strong 修饰或者被 copy 修饰
●Cocoa API中方法包含有usingBlock的方法参数时
●block作为GCD API的方法参数时
typedef void (^MYBlock)();
MYBlock myblock() {
return ^{
};
}
NSArray *array = [NSArray new];
[array enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
}];
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
});
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 1.0*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
});
2、为什么要把 block copy 到堆上?
●因为block在栈区的话不会对 访问的对象类型的 auto 变量 产生强引用!只能copy到堆上才能产生强引用
●copy 到堆上block会自动调用内部的_main_block_copy_0 中的 Block_object_assign 函数 该函数来处理auto变量的修饰符(__strong、__week、__unsafe_unretained)做出相应的操作。
●如果block从堆上移除,会调用block内部的dispose函数;该函数内部会调用 _Block_object_dispose函数;该函数会自动释放引用的auto变量,类似于release
MRC
Block block;
{
Person *person = [Person new];
person.age = 10;
block = ^ {
NSLog(@"%ld",person.age)
}
//MRC下 当程序执行到这里的时候 Person 已经被释放
//ARC下 当程序执行到这里的时候 Person 不会被释放,原因 block会自动执行 copy操作
}
程序走到这里 block释放 都销毁 全释放
3、block,如何修改外部变量
__block 来修改变量,或者把变量改成 static 修饰变量!static 修饰变量,会造成变量不会被释放(不建议使用)
原因:__block 是地址传递
以下情况可不用 __block来修饰 也不会报错
4、 什么场景下会产生循环引用
copy到堆上的block,会强引用进入到该block中的外部变量!
如果在block中访问了属性,那么block就会retain住self 如果在block中访问了一个局部变量,那么block就会对该变强引用
对象对block拥有一个强引用,而block内部又对外部对象又一个强引用,形成了闭环,发生内存泄漏
5、如何解决block循环引用
产生的原因:
●block 定义成 VC 的属性,VC 是强持有 block 的。
●因为 block 捕获的 self 变量赋值给了 block 对应的成员变量,所以 block 是强持有 self 的.
并不是局部变量,代码块执行完就能释放。
方案1:weak - strong
// __weak 会使 block 捕获的变量也使用__weak修饰,进而不会对引用计数+1
__weak typeof(self) weakSelf = self;
self.doWork = ^{
// 如果不判断self, 这个回调过了一段时间才会执行的话,
// 假如 self 被释放,后续代码中出现self->_name 方式使用成员变量会崩溃,
// 但是使用 self 的方法调用没问题,因为向 nil 发送消息,不会报错
if (!weakSelf) return;
// 强制有self, 强引用一下,防止在后续时间内,self 被释放。
// 可以保证在接下来的代码中,self 有值
// 而且strongSelf 是局部变量,出了代码块,就释放了,引用计数会再减1,self 可以正常释放
__strong typeof(self) strongSelf = weakSelf;
...
};
方案2:把self 当做参数传给block
把 self 当做参数传给 block ,就相当于是局部变量,block 执行完会自动释放。 这个不涉及到捕获self, 将其保存为成员变量的事情。
OC源码:
self.doWork = ^(ViewController *vccc){
NSLog(@"%@", vccc);
};
self.doWork(self);
生成 C++ 文件的源码:
6、ARC 下 GlobalBlock 和 NSStackBlock。strong 修饰 block 的话是什么样的?
●strong 可以修饰 block目前我看到的是可以,和copy是一样的效果!因为block的retain就是用copy来实现的
●weex 修饰的block 在栈上
GlobalBlock
MyTestBlock *testBlock = [[MyTestBlock alloc] init];
testBlock.block();
@property (nonatomic, copy) NSString *str;
- (void)viewDidLoad {
NSMallocBlock
void(^block)(void);//ARC 这是一个变量声明,对象声明默认强引用
/*void(^block)(void)*/ block = ^ {
NSLog(@"%d",a);
};
NSStackBlock
//这个block没有被强引用 不可能是堆block,此时是栈block,即便是访问量 str
^ {
NSLog(@"%d",a);
NSLog(@"%d",str);
};
}
7、值类型与引用类型
栈区空间释放规则,栈区嵌套(内部有代码块)栈空间释放规则?
下面的代码会有什么问题?
void(^__weak weakBlock)(void) = nil;
- (void)viewDidLoad {
[super viewDidLoad];
[self blockStack_Stack];
// 崩溃
weakBlock();
}
- (void)blockStack_Stack {
int a;
{
int b = 2;
// stack block 存在栈区
void(^ __weak weakBlock1)(void) = ^{
NSLog(@"-----%d", b);
};
a = b;
// block 是引用类型,赋值是多了个指针指向栈block
weakBlock = weakBlock1;
// 打印结果: <__NSStackBlock__: 0x7ffeebd5eb08> --- <__NSStackBlock__: 0x7ffeebd5eb08>
NSLog(@"%@ --- %@", weakBlock, weakBlock1);
// 打印结果: 0x103ea8c70 --- 0x7ffeebd5eb30
NSLog(@"%p --- %p", &weakBlock, &weakBlock1);
}
// 栈区的数据出了代码块依然存在,出了函数的作用域,会被释放
// 可正常打印
weakBlock();
}
8、block访问了宏会循环引用吗?
不会!算是传值。
宏是预编译的时候替换用,不是全局静态变量!
宏越多就会造成编译时间变长?
9、Masonry布局,MJRefresh刷新等self引用?,使用self 会不会产生强引用?
Masonry不会,MJRefresh 调用内部方法不会 内部使用了弱引用
@property (nonatomic, weak) MAS_VIEW *view;
Masonry
//执行1
[self.nameView mas_makeConstraints:^(MASConstraintMaker *make) {
make.left.mas_equalTo(self.view.mas_left);
}];
//执行2
- (NSArray *)mas_makeConstraints:(void(^)(MASConstraintMaker *))block {
self.translatesAutoresizingMaskIntoConstraints = NO;
MASConstraintMaker *constraintMaker = [[MASConstraintMaker alloc] initWithView:self];
block(constraintMaker);
return [constraintMaker install];
}
@interface MASConstraintMaker () <MASConstraintDelegate>
@property (nonatomic, weak) MAS_VIEW *view;
@end
@implementation MASConstraintMaker
//执行3
- (id)initWithView:(MAS_VIEW *)view {
self = [super init];
if (!self) return nil;
//执行4
self.view = view;
self.constraints = NSMutableArray.new;
return self;
}
[self.mj_header removeFromSuperview];
_scrollView = (UIScrollView *)newSuperview;
__weak UIScrollView *_scrollView;
MJRefresh
//执行1
_tableView.mj_header = header;
- (void)setMj_header:(MJRefreshHeader *)mj_header {
if (mj_header != self.mj_header) {
// 执行2
[self.mj_header removeFromSuperview];
}
}
//执行3
- (void)willMoveToSuperview:(UIView *)newSuperview {
[super willMoveToSuperview:newSuperview];
// 执行4
_scrollView = (UIScrollView *)newSuperview;
}
/** 刷新控件的基类 */
@interface MJRefreshComponent : UIView {
/** 记录scrollView刚开始的inset */
UIEdgeInsets _scrollViewOriginalInset;
/** 父控件 */
__weak UIScrollView *_scrollView;
}