最近的面试中,被问到各种各样的问题,有的问题真的是一脸懵逼。Block的调用,有的时候回产生循环引用,与及如何解除循环引用,做iOS开发的,想必大家都知道。然而最近被问到这样一个问题,在block内部申明使用static变量,会造成循环引用吗?第一反应是,自己没有这么做过,也没有见别人这样写过,哪怕是开源的框架里,目前也没有这么写的。于是和小伙伴们讨论了一下,也敲代码验证了一下。

循环引用是什么?

当两个不同的对象各有一个强引用指向对方,那么循环引用变产生了,当然一个对象产生的环也是一样的。

从引用计数来说,如果两个对象互相引用的时候,双方的引用计数都是+1的,导致如何时候引用计数都不为0,始终无法释放他们的内存,即使已经没有变量持有它。

static变量

static关键字声明的变量必须放在implementation外面,或者方法中,如果不为它赋值默认为0,它只在程序开机初始化一次。

先得说下内存中和变量有关的分区:堆、栈、静态区。其中,栈和静态区是操作系统自己管理的,对程序员来说相对透明,所以,一般我们只需要关注堆的内存分配,而循环引用的产生,也和其息息相关,即循环引用会导致堆里的内存无法正常回收。

代码分析如下

- (void)testBlockWithStaticVariable {
    
    static NSInteger num1 = 100;
    __block NSInteger num2 = 5;
    void (^block)(void) = ^{
      
        static NSInteger num3 = 10;
        NSInteger num4 = 0;
        NSLog(@"the current value is num1 = %@, num2 = %@, num3 = %@, num4 = %@",@(num1++),@(num2++),@(num3++),@(num4++));
        //[self testRecycle]; //可以正常调用,不会发生循环引用
    };
    for(NSInteger i = 0; i < 10; i++){
        
        block();
    }
}

block内部申明static变量

从结果看num1、num2、num3都进行了累加操作,只有内部申请的普通变量num4没有进行叠加,因为它是放在栈上的,每次都初始化。num1、num3都是static的,无论是在block内部调用外部的static变量,还是在block内部申请使用static变量,都不造成循环引用,也不会发生内存泄漏。
在block内部调用当前类的方法,不会发生循环引用。

- (void)testRecycle {
    
    static NSInteger num1 = 100;
    __block NSInteger num2 = 5;
    self.testBlock = ^{
        
        static NSInteger num3 = 10;
        NSInteger num4 = 0;
        NSLog(@"the current value is num1 = %@, num2 = %@, num3 = %@, num4 = %@",@(num1++),@(num2++),@(num3++),@(num4++));
        NSLog(@"----------------");
        //[self testBlockWithStaticVariable]; //会发生循环引用 Capturing 'self' strongly in this block is likely to lead to a retain cycle
    };
    for(NSInteger i = 0; i < 10; i++){
        
        self.testBlock();
    }
}

打印结果和上图一样,唯一不同的是,在该block内部调用当前类的方法,系统会报Capturing 'self' strongly in this block is likely to lead to a retain cycle的错误。

总结

block内部申明static变量,不会内存泄露,也不会循环引用。


蓝光95
210 声望16 粉丝

一名从业多年的软件开发者,做过5年的iOS开发,做过一年的react-native开发,有iOS性能优化经验,IM开发经验,会小程序的开发,现在在昆明从事移动前端开发的工作。