美文网首页ios tips
block对外部变量的内存管理

block对外部变量的内存管理

作者: Gyuanhang | 来源:发表于2017-12-12 23:29 被阅读0次

    代码块在ios中通常用于回调,本文主要介绍block对外部变量的管理机制。我们知道如果要在block中使用block外面的变量,如果该变量是局部变量,就要先将其申明为__block类型。为什么呢?这就涉及到block对外部变量的内存管理

    一、基本数据类型

    先看下面测试代码://局部变量

    - (void)localDataTest

    {

    int localData = 100;

    NSLog(@"localData --%p",&localData);

    int (^sum)() = ^(int a,int b){

    NSLog(@"localData --%p",&localData);

    return localData + a + b;

    };

    localData = 0;

    int c = sum(1,2);

    NSLog(@"localDataTest -- c= %d",c);

    }

    打印结果:

    2015-03-21 12:04:11.161 test1[837:31981] localData --0xbfff4ed0

    2015-03-21 12:04:11.162 test1[837:31981] localData --0x7c18bb44

    2015-03-21 12:04:11.162 test1[837:31981] localDataTest --c = 103

    //静态变量

    - (void)staticDataTest

    {

    static int staticData = 100;

    NSLog(@"staticData --%p",&staticData);

    int (^sum)() = ^(int a,int b){

    NSLog(@"staticData --%p",&staticData);

    return staticData + a + b;

    };

    staticData = 0;

    int c = sum(1,2);

    NSLog(@"staticDataTest --%d",c);

    }

    打印结果:

    2015-03-21 12:04:11.162 test1[837:31981] staticData --0xc7b8

    2015-03-21 12:04:11.162 test1[837:31981] staticData --0xc7b8

    2015-03-21 12:04:11.162 test1[837:31981] staticDataTest-- 3

    //全局变量

    - (void)globalDataTest

    {

    NSLog(@"globalData --%p",&globalData);

    int (^sum)() = ^(int a,int b){

    NSLog(@"globalData --%p",&globalData);

    return globalData + a + b;

    };

    globalData = 0;

    int c = sum(1,2);

    NSLog(@"globalData --%d",c);

    }

    打印结果:

    2015-03-21 12:04:11.162 test1[837:31981] globalData --0xc7b4

    2015-03-21 12:04:11.162 test1[837:31981] globalData --0xc7b4

    2015-03-21 12:04:11.163 test1[837:31981] globalData -- 3

    //block变量

    - (void)blockDataTest

    {

    __block int blockData = 100;

    NSLog(@"blockData --%p",&blockData);

    int (^sum)() = ^(int a,int b){

    NSLog(@"blockData --%p",&blockData);

    return blockData + a + b;

    };

    blockData = 0;

    int c = sum(1,2);

    NSLog(@"blockDataTest -- c= %d",c);

    }

    打印结果:

    2015-03-21 12:04:11.163 test1[837:31981] blockData --0xbfff4ed8

    2015-03-21 12:04:11.163 test1[837:31981] blockData --0x7c2411c0

    2015-03-21 12:04:11.163 test1[837:31981] blockDataTest --c = 3

    上面4段代码是分别对block中用到的局部变量、静态变量、全局变量、block变量的测试。测试结果显示:

    对于block中用到的外部变量,若是静态类型或者全局类型,block中该变量的地址没有发生变化。由于静态变量和全局变量其地址是固定的,因此block在定义的时候并没有复制该变量的值,而是直接从其所在内存中读出。

    再来看局部(本地)变量和block变量,在block中它们两个的地址都发生了变化,第一反应是block在定义的时候拷贝了这两种类型的变量(即开辟了新的内存空间),但是两者sum()的结果却是不同的。事实上并不是都被拷贝了:

    对于局部变量,block在定义的时候复制了它,它在block中是作为常量使用的,其值不受外面的影响,因此在测试代码中,blockData的值始终为100。

    对于block变量,可以看到其地址发生了变化,但是blockData却受到外界影响。这是因为,blockData在定义变量本身的时候是位于stack上的,而在定义block的时候,该变量并不是被复制了一份,而是编译器将其转移到了heap上。这一点通过下面代码可以得到验证:

    - (void)blockDataTest

    {

    __block int blockData = 100;

    NSLog(@"blockData -- %p",&blockData);

    int (^sum)() = ^(int a,int b){

    NSLog(@"blockData -- %p",&blockData);

    return blockData + a + b;

    };

    NSLog(@"blockData -- %p",&blockData);

    blockData = 0;

    int c = sum(1,2);

    NSLog(@"blockData -- %d",c);

    }

    打印结果:

    2015-03-21 13:22:07.350 test1[1180:68166] blockData --0xbff47ed8

    2015-03-21 13:22:07.350 test1[1180:68166] blockData --0x7c452680

    2015-03-21 13:22:07.350 test1[1180:68166] blockData --0x7c452680

    2015-03-21 13:22:07.350test1[1180:68166] blockData – 3

    可以看到block定义之后,blockData的地址就发生了变化。这样就能理解为何打印出来的blockData值是3了。

    二、对象类型

    //本地对象

    - (void)localObjTest

    {

    UILabel *localObj = [[UILabel alloc]init];

    NSLog(@"localObj adress --%p", & localObj);

    void (^test)() = ^{

    NSLog(@"localObj adress --%p", & localObj);

    NSLog(@"localObj --%@",localObj);

    };

    localObj = nil;

    test();

    }

    打印结果:

    2015-03-21 13:44:25.848 test1[1332:79267] localObj adress-- 0xbff7ced4

    2015-03-21 13:44:25.848 test1[1332:79267] localObj adress-- 0x7b63f8b4

    2015-03-21 13:44:25.848 test1[1332:79267] localObj --

    //静态对象

    - (void)staticObjTest

    {

    static UILabel *staticObj ;

    staticObj = [[UILabel alloc]init];

    NSLog(@"staticObj adress --%p", & staticObj);

    void (^test)() = ^{

    NSLog(@"staticObj adress --%p", & staticObj);

    NSLog(@"staticObj --%@",staticObj);

    };

    staticObj = nil;

    test();

    }

    打印结果:

    2015-03-21 13:44:25.917 test1[1332:79267] staticObjadress -- 0x85a48

    2015-03-21 13:44:25.917 test1[1332:79267] staticObjadress -- 0x85a48

    2015-03-21 13:44:25.917 test1[1332:79267] staticObj --(null)

    //全局对象

    - (void)globalObjTest

    {

    globalObj = [[UILabel alloc]init];

    NSLog(@"staticObj adress --%p", & globalObj);

    void (^test)() = ^{

    NSLog(@"globalObj adress --%p", & globalObj);

    NSLog(@"globalObj --%@",globalObj);

    };

    globalObj = nil;

    test();

    }

    打印结果:

    2015-03-21 13:44:25.917 test1[1332:79267] staticObjadress -- 0x85a4c

    2015-03-21 13:44:25.917 test1[1332:79267] globalObjadress -- 0x85a4c

    2015-03-21 13:44:25.917 test1[1332:79267] globalObj --(null)

    //block对象

    - (void)blockObjTest

    {

    __block UILabel *blockObj = [[UILabel alloc]init];

    NSLog(@"blockObj adress --%p", & blockObj);

    void (^test)() = ^{

    NSLog(@"blockObj adress --%p", & blockObj);

    NSLog(@"blockObj --%@",blockObj);

    };

    blockObj = nil;

    test();

    }

    打印结果:

    2015-03-21 13:44:25.917 test1[1332:79267] blockObj adress-- 0xbff7ced0

    2015-03-21 13:44:25.918 test1[1332:79267] blockObj adress-- 0x7c835598

    2015-03-21 13:44:25.918 test1[1332:79267] blockObj --(null)

    总结:从测试结果可以看到

    *对于静态对象变量和全局对象变量,其地址同样不是固定的。

    *对于局部(本地)对象变量,定义block的时候同样复制了(指针)变量,其在block中是作为(指针)常量使用的,不会受外界影响。

    *对于block对象变量,定义block的时候同样将变量从栈转移到了堆上,这一点用刚才的方法同样可以验证。因此block变量是受外界影响的,导致输出结果为空。

    三、循环引用

    block对于其中的任何一个对象都会形成强引用,倘若其中的某个对象同同样强引用了block,就会造成两个对象相互强引用了对方,也就是循环引用,从而造成彼此之间谁都无法释放谁的尴尬局面,也就造成了内存泄露。解决这一问题的方法是在block外面对对象做弱引用处理。以self为例:

    由于self对于block是强引用,因此如果block中需要用到self,如果不对self做事先处理,就会形成循环引用。处理的方法是:在block前加这样一句:

    __weak typeof(self) weakSelf =self;

    这句话的作用是重新创建了一个weak类型的self对象,block对其不会形成强引用,即在引用weakSelf的时候不会对其retain,从而避免了循环引用的发生。__block typeof(self) weakSelf=self;

    [selfmethodThatTakesABlock:^ {

    [weakSelfdoSomething];

    }

    通过下图方便理解,实箭头表示强引用,虚箭头表示弱引用。

    当可能发生循环引用的时候,系统会给出警告:capturing self strongly in this block is likely to lead to a retain cycle循环引用是因为self引用block变量,而block又引用self,两者互不相让,谁都无法释放谁。在ARC下通过在block外建立一个对self的弱引用变量weakSelf后,在block使用weakSelf,就不会retain self了,从而有效得避免了循环引用。在非ARC下使用__block,self同样不会被retain。

    相关文章

      网友评论

        本文标题:block对外部变量的内存管理

        本文链接:https://www.haomeiwen.com/subject/uauoixtx.html