zoukankan      html  css  js  c++  java
  • Block与内存

    Block

    Block内引用外部变量的问题

    // 强引用

      #define BLog(prefix,obj) {NSLog(@"位置和指针变量名:%@ ,指针内存地址:%p, 指针值:%p ,指向的对象:%@ ",prefix,&obj,obj,obj);}

    - (void)blockVariableStrongReferenceTest
    
    {
    
        NSLog(@"
    ");
    
        NSObject *obj = [[NSObject alloc] init];
    
        BLog(@"StrongRef obj",obj);
    
        void(^testBlock)()= ^(){
    
            BLog(@"StrongRef in block",obj);
    
        };
    
        testBlock();
    
        // Block外部尝试将obj置为nil
    
        obj = nil;
    
        testBlock();  // 第二次调用block
    
    }

    运行结果 

    位置和指针变量名:StrongRef obj ,指针内存地址:0x7fff543d0c98, 指针值:0x7fcb1bd22390 ,指向的对象:<NSObject: 0x7fcb1bd22390> 
    
    位置和指针变量名:StrongRef in block ,指针内存地址:0x7fcb1c903fb0, 指针值:0x7fcb1bd22390 ,指向的对象:<NSObject: 0x7fcb1bd22390> 
    
    位置和指针变量名:StrongRef in block ,指针内存地址:0x7fcb1c903fb0, 指针值:0x7fcb1bd22390 ,指向的对象:<NSObject: 0x7fcb1bd22390

    分析

    • 方法内部的obj变量在栈中,变量内存地址0x7fff543d0c98,所指向的对象<NSObject: 0x7fcb1bd22390>在堆中,内存地址0x7fcb1bd22390,它的引用计数+1。
    • Block中obj指针已经不是外部的obj指针了,它是外部变量obj的拷贝,它的内存地址是0x7fcb1c903fb0,跟外部obj不一样,但是所指向的对象也是0x7fcb1bd22390,0x7fcb1bd22390对象引用计数再+1=2。
    • block中的obj指针(内存地址是0x7fcb1c903fb0)对对象(<NSObject: 0x7fcb1bd22390>)的引用是强引用,在外部将obj(地址0x7fff543d0c98)置为nil后,外部的obj不再指向<NSObject: 0x7fcb1bd22390>对象,0x7fcb1bd22390对象的引用计数-1,引用计数为1,ARC不会回收0x7fcb1bd22390对象内存,所以第二次调用block,0x7fcb1bd22390对象还在内存中。
    // 弱引用
    
    - (void)blockVariableWeakReferenceTest
    
    {
    
        NSLog(@"
    ");
    
        NSObject *obj = [[NSObject alloc] init];
    
        BLog(@"StrongRef obj",obj);  
    
        __weak NSObject *weakObj = obj;
    
        BLog(@"WeakRef weakObj", weakObj);
    
        void(^testBlock)()= ^(){
    
            BLog(@"weakObj in block",weakObj);
    
        };
    
        testBlock();
    
        obj = nil; 
    
        testBlock();
    
    }

    运行结果

    位置和指针变量名:StrongRef obj ,指针内存地址:0x7fff543d0c98, 指针值:0x7fcb1bd2fee0 ,指向的对象:<NSObject: 0x7fcb1bd2fee0> 
    
    位置和指针变量名:WeakRef weakObj ,指针内存地址:0x7fff543d0c90, 指针值:0x7fcb1bd2fee0 ,指向的对象:<NSObject: 0x7fcb1bd2fee0> 
    
    位置和指针变量名:weakObj in block ,指针内存地址:0x7fcb1bc072b0, 指针值:0x7fcb1bd2fee0 ,指向的对象:<NSObject: 0x7fcb1bd2fee0> 
    
    位置和指针变量名:weakObj in block ,指针内存地址:0x7fcb1bc072b0, 指针值:0x0 ,指向的对象:(null) 

    分析:

    • 方法内部的weakObj变量在栈中,变量内存地址0x7fff543d0c98,所指向的对象<NSObject: 0x7fcb1bd2fee0>在堆中,内存地址0x7fcb1bd2fee0,它的引用计数+1。
    • weakObj变量也在栈中,内存为0x7fff543d0c90,所指向的对象也是<NSObject: 0x7fcb1bd2fee0>,弱引用,所以0x7fcb1bd2fee0对象的引用计数不增加,仍然为1.
    • Block中weakObj它的内存地址是0x7fcb1bc072b0,跟外部的weakObj不同,但是所指向的对象也是0x7fcb1bd2fee0,弱引用,0x7fcb1bd2fee0对象引用计数还是不增加,仍然是1。 -在外部将obj(地址0x7fff543d0c98)置为nil后,外部的obj不再指向<NSObject: 0x7fcb1bd2fee0>对象,0x7fcb1bd2fee0对象的引用计数-1,引用计数为0,ARC回收0x7fcb1bd2fee0对象内存,并将指向它的弱引用指针赋值为nil,所以第二次调用block,0x7fcb1bd2fee0对象不在在内存中。

    Block生命周期内的对象安全

    在block中__weak声明的指针去引用对象 可以避免循环引用的问题,但是当外部对象被释放了,block 内部会访问不到这个对象. 这种问题如何解决呢?先来看一段代码:

    //多线程时Block生命周期内对象安全
    
    - (void)blockVariableMutiThreadTest
    
    {
    
        NSObject *obj = [[NSObject alloc]init]; //obj强引用,<NSObject: 0x7f9413c1c040>对象引用计数+1,=1
    
        BLog(@"obj", obj);
    
        __weak NSObject *weakObj = obj;//weakObj弱引用,<NSObject: 0x7f9413c1c040>对象引用计数不变,=1
    
        BLog(@"weakObj-0", weakObj);
    
        dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    
            __strong NSObject *strongObj = weakObj; //strongObj强引用,<NSObject: 0x7f9413c1c040>对象引用计数+1,=2
    
            sleep(3);
    
            BLog(@"weakObj - block", weakObj);
    
            BLog(@"strongObj - block", strongObj);
    
        });
    
        sleep(1);
    
        obj = nil; //obj被置为nil,<NSObject: 0x7f9413c1c040>对象引用计数-1,=1
    
        BLog(@"weakObj-1", weakObj);  //没被释放
    
        sleep(4); //block在异步线程中执行完毕(在另一块内存中执行),block内存被释放,<NSObject: 0x7f9413c1c040>对象引用计数-1,=0;ARC开始把0x7f9413c1c040对象内存回收,把弱引用weakObj置为nil
    
        BLog(@"weakObj-2", weakObj);
    
    }

    执行结果如下:

    位置和指针变量名:obj ,指针内存地址:0x7fff51888c98, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 
    
    位置和指针变量名:weakObj-0 ,指针内存地址:0x7fff51888c90, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 
    
    位置和指针变量名:weakObj-1 ,指针内存地址:0x7fff51888c90, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 
    
    位置和指针变量名:weakObj - block ,指针内存地址:0x7f9413d9a880, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 
    
    位置和指针变量名:strongObj - block ,指针内存地址:0x1187e2e08, 指针值:0x7f9413c1c040 ,指向的对象:<NSObject: 0x7f9413c1c040> 
    
    位置和指针变量名:weakObj-2 ,指针内存地址:0x7fff51888c90, 指针值:0x0 ,指向的对象:(null) 

    总结:

    多线程的时候,在 block 外部__weak声明的变量指向一个对象, 通过把__weak声明的变量值赋值给block内部__strong变量```,实现在block内对该对象进行强引用,这样可以在block生命周期内保留该对象不被释放,在block生命周期结束后,对象内存被释放。

    block修改外部变量 __block变量

    - (void)blockVariable
    
    {
    
        NSObject *obj = [[NSObject alloc]init]; //指向的对象:<NSObject: 0x7fb4039063b0>
    
        BLog(@"obj",obj);
    
        __block NSObject *blockObj = obj;  //blockObj指向对象:0x7fb4039063b0
    
        obj = nil;
    
        BLog(@"blockObj -1",blockObj);  //blockObj,0x7fff52365c90
    
        void(^testBlock)() = ^(){
    
            BLog(@"blockObj - block",blockObj);   //blockObj,0x7fb401c7d7f8,指向的对象:<NSObject: 0x7fb4039063b0>
    
            NSObject *obj2 = [[NSObject alloc]init]; // obj2 ,0x7fff52365bc8,指向的对象:<NSObject: 0x7fb401c83c40>
    
            BLog(@"obj2",obj2);
    
            blockObj = obj2;
    
            BLog(@"blockObj - block",blockObj); //blockObj,0x7fb401c7d7f8,指向对象:<NSObject: 0x7fb401c83c40>
    
        };
    
        NSLog(@"%@",testBlock);         //block 的地址  <__NSMallocBlock__: 0x7fb401c07d20>
    
        BLog(@"blockObj -2",blockObj);  //blockObj地址发生变化,0x7fb401c7d7f8,/指向的对象:<NSObject: 0x7fb4039063b0>
    
        testBlock();
    
        BLog(@"blockObj -3",blockObj);  //blockObj,0x7fb401c7d7f8,指向对象:<NSObject: 0x7fb401c83c40>
    
     
    
    }

    分析:

    • 代码中打印了一个testBlock的声明,声明前和声明后,blockObj的地址发生变化。
    • 在block外部声明__block变量后,在block里可以改变 该变量的作用域。

    关于 block访问外部变量原理

    block实现原理(一)

    block和变量的内存管理(二)

    在oc,在block中直接访问外部变量,访问的是外部变量的copy。用clang后将 .m翻译为.cpp文件后发现,外部函数是通过传值方式将变量值传给block(block结构体、block最终要执行的函数代码). 使用了__block后,外部函数是通过指针传递,将变量传递到 block 内,所以可以修改变量值.

    Block在内存中的位置

    Block作为C语言的扩展,并不是高新技术,和其他语言的闭包或lambda表达式是一回事。需要注意的是由于Objective-C在iOS中不支持GC机制,使用Block必须自己管理内存,而内存管理正是使用Block坑最多的地方,错误的内存管理 要么导致return cycle内存泄漏要么内存被提前释放导致crash。 Block的使用很像函数指针,不过与函数最大的不同是:Block可以访问函数以外、词法作用域以内的外部变量的值。换句话说,Block不仅 实现函数的功能,还能携带函数的执行环境。

    可以这样理解,Block其实包含两个部分内容:

    1. Block执行的代码,这是在编译的时候已经生成好的;
    2. 一个包含Block执行时需要的所有外部变量值的数据结构。 Block将使用到的、作用域附近的变量建立一份快照拷贝

    根据Block在内存中的位置分为三种类型:NSConcreteGlobalBlock,NSConcreteMallocBlock, _NSConcreteStackBlock。

    • _NSConcreteGlobalBlock:类似函数,位于text段;
    • _NSConcreteStackBlock:位于栈内存,函数返回后Block将无效;
    • _NSConcreteMallocBlock:位于堆内存。

    _NSConcreteGlobalBlock:

    Blocks that don't capture any variables are global blocks. Since all instances of the block are the same, the compiler can just allocate one copy statically for the life of the program。

    NSConcreteStackBlock 和 NSConcreteMallocBlock:

    Blocks that capture variables (closures) are either stack or heap (malloc) blocks. Blocks start out on the stack, as stack blocks. When a stack block is copied for the first time, it is moved to the heap. Copying a heap block does not create another copy; but simply retains it. 在开启 ARC 时,大部分情况下编译器通常会将创建在栈上的 block 自动拷贝到堆上。

    • 当 block 作为函数返回值返回时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
    • 当 block 被赋值给 _strong id 类型的对象或 block 的成员变量时,编译器自动将 block 作为 Block_copy 函数,效果等同于 block 直接调用 copy 方法;
    • 当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 拷贝;

    ARC中自动copy block的例子

     - (void)blockObjectInMemory
    
    {
    
        // global block
    
        void (^globalBlockInMemory)(int number) = ^(int number){
    
            printf("%d 
    ",number);
    
        };
    
        globalBlockInMemory(90);
    
        BLog(@"global block %@", globalBlockInMemory);
    
     
    
        // malloc block
    
        int outVariable = 100;
    
        void (^mallocBlockInMemory)(int number) = ^(int number){
    
            printf("%d 
    ",outVariable+number);
    
        };
    
        BLog(@"stackBlock block %@", mallocBlockInMemory);     // ARC 自动将栈中block拷贝到堆上
    
    }

    运行结果:

     

    位置和指针变量名:global block %@ ,指针内存地址:0x7fff5b422c78, 指针值:0x1047e01f0 ,指向的对象:<__NSGlobalBlock__: 0x1047e01f0> 
    
    位置和指针变量名:stackBlock block %@ ,指针内存地址:0x7fff5b422c68, 指针值:0x7fe6849085f0 ,指向的对象:<__NSMallocBlock__: 0x7fe6849085f0> 
    - (id)returnBlock
    
    {
    
        int outVariable = 100;
    
        void (^mallocBlockInMamory)(void) = ^(void){
    
                NSLog(@"in block");
    
        };
    
        BLog(@" block  ", mallocBlockInMamory);
    
        return mallocBlockInMamory;
    
    }
    
    - (void)blockInmemory
    
    {
    
        id block = [self returnBlock];
    
        BLog(@"a block %@", block);
    
    }

    运行结果:

    位置和指针变量名: block   ,指针内存地址:0x7fff516a9c30, 指针值:0x10e559250 ,指向的对象:<__NSGlobalBlock__: 0x10e559250> 
    
    位置和指针变量名:a block %@ ,指针内存地址:0x7fff516a9c78, 指针值:0x10e559250 ,指向的对象:<__NSGlobalBlock__: 0x10e559250> 

    在没有ARC之前,由于ARC 自动将栈中block拷贝到堆上,所以当returnBlock函数退出,在栈中内存释放后,仍然可以访问到block对象。

    ARC 中需要手动拷贝Block的例子

    在以下情形中, block 会从栈拷贝到堆:

    • 当 block 调用 copy 方法时,如果 block 在栈上,会被拷贝到堆上;
    • 当 block 作为函数返回值返回时,编译器自动将 block 作为 _Block_copy 函数,效果等同于 block 直接调用 copy 方法;
    • 当 block 被赋值给 _strong id 类型的对象或 block 的成员变量时,编译器自动将 block 作为 Block_copy 函数,效果等同于 block 直接调用 copy 方法;
    • 当 block 作为参数被传入方法名带有 usingBlock 的 Cocoa Framework 方法或 GCD 的 API 时。这些方法会在内部对传递进来的 block 调用 copy 或 _Block_copy 进行拷贝;

    其他情况需要手动拷贝。

    - (void)stackBlockInMemory
    
    {
    
        NSArray *array = [self getBlockArray];
    
        id block = array[0];
    
        BLog(@"block %@", block);
    
    }
    
     
    
    - (id)getBlockArray
    
    {
    
        int val = 10;
    
        return [[NSArray alloc] initWithObjects:
    
                ^{NSLog(@"value:%d", val);},
    
                ^{NSLog(@"value:%d", val);}, nil];
    
    }

    程序会报EXC_BAD_ACCESS ,getBlockArray返回的数组里面的 block 是不可访问的。

    手动copy后,block拷贝到堆上,getBlockArray函数返回的栈帧被销毁后,仍可以访问堆中的block拷贝。

    - (id)getBlockArray
    
    {
    
        int val = 10;
    
        return [[NSArray alloc] initWithObjects:
    
                [^{NSLog(@"value:%d", val);} copy],
    
                [^{NSLog(@"value:%d", val);} copy], nil];
    
    }

    Block中造成内存泄漏的一些场景

    推荐文章:

    http://www.tanhao.me/pieces/310.html/

    block 内存管理

    block 实现原理

    Block-ABI-Apple

    正确使用Block避免Cycle Retain和Crash

    weak与block区别

    block实现原理(一)

    block和变量的内存管理(二)

    How blocks are implemented (and the consequences

    block实现原理

     

    作者:sue_zheng

    出处:http://www.cnblogs.com/sueZheng/p/4954938.html

     

  • 相关阅读:
    生成函数
    LOJ6078 「2017 山东一轮集训 Day7」重排
    Gym101981C Cherry and Chocolate
    Gym102411C Cross-Stitch
    使用filter对请求设置编码
    java过滤器filter使用
    java操作数据库的事务支持
    jsp泛型支持
    jstl 使用
    jsp el的内置对象
  • 原文地址:https://www.cnblogs.com/sueZheng/p/4954938.html
Copyright © 2011-2022 走看看