iOS学习之block不为人知的一面(附:面试题)

Posted Mr_yu__

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了iOS学习之block不为人知的一面(附:面试题)相关的知识,希望对你有一定的参考价值。

前言

说起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();
}

解析:打印结果是1345,首先一点每一个block里面都有引用objc,所以每个地方都会是引用计数+1,这是没问题的,但是为什么strongBlock里面会是引用计数+2呢?在这里我们要明白,block本身是栈block,只不过它的代码块当中引用了objc,所以才使得objc引用计数+1,但是因为栈block又被strongBlock所持有,也就是所谓的copy修饰的变量持有,意味着又拷贝了一份到了堆中,也就是堆block,因此使得引用计数再+1,而weakBlock本身是栈block,但是下面的mallocBlock又对weakBlock进行了一次copy,所以计数再+1,其实strongBlock完全可以看成下面2步操作的结合,通过这题也就充分说明了堆栈block的区别了

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

解析:会,首先weakSelfself是映射关系,弱引用,指向同一片内存空间,然而全局静态变量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.doStudentblock中,再次引用了strongSelf,所以引用计数再次+1,所以最终self还是多引用了一次,导致最终无法释放。

今天的分享就到次为止了,当然了后续如果有更多比较有趣的block题目也会补充进来,下一篇会对block的底层源码进行分析,敬请期待吧😄

以上是关于iOS学习之block不为人知的一面(附:面试题)的主要内容,如果未能解决你的问题,请参考以下文章

iOS学习之代码块(Block)

Java学习之面试题整理

美团优选大数据开发岗面试真题-附答案详细解析

Spark Core学习之常用算子(含经典面试题)

贝壳找房测试开发一面凉经--北京现场面(附面试答案)

贝壳找房测试开发一面凉经--北京现场面(附面试答案)