iOS Block面试题零碎整理

面试题
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 = &param3;

    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;
}

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值