美文网首页iOSIOS知识整理iOS面试
Block 使用__weak 与 __block的作用

Block 使用__weak 与 __block的作用

作者: 若幹年後 | 来源:发表于2017-04-01 13:51 被阅读46次

    差不多有四个月没有写帖子了 最近一段时间 公司终端部 全部转去做前端开发 搞了一个月Meteor 搞了一个月Vue.js 有搞了一个月的HTML CSS 现在又在搞 JSP! 哎!公司东搞搞西搞搞的 都把我给整蒙圈了! 好久没写OC代码 今天抽闲复习复习!

    在面试的时候经常会被问到 block 避免循环引用的问题时,发现好多人都说通过添加 __block 修饰词来避免!今天忙里抽闲给大家说道说道Block中__Weak 与 __Block 的正确使用方法;如有错误 还请指正!废话不多说直接上代码!

    准备工作:
    首先定义了一个类 MyObject 继承 NSObject,并添加了一个属性 text,重写了description方法,返回 text 的值。这个主要是因为编译器本身对 NSString 是有优化的,创建的 string 对象有可能是静态存储区永不释放的,为了避免使用 NSString 引起一些问题,还是创建一个 NSObject 对象比较合适。

    在自己定义了一个 TLog 方法输出对象相关值,定义如下:

    #define TLog(prefix,Obj) {NSLog(@"变量内存地址:%p, 变量值:%p, 指向对象值:%@, --> %@",&Obj,Obj,Obj,prefix);}

    1.__weak 在Block 中的使用

      MyObject *obj =[[MyObject alloc] init];
        
       obj.text = @"my-object";
    
        TLog(@"obj", obj);
    
        __weak MyObject *weakobj  = obj;
    
        TLog(@"weakObj", weakobj);
    
    
        void(^testBlock)() = ^() {
    
             TLog(@"weakObj - block", weakobj);
    
        };
    
        testBlock();
    
        obj = nil;
    
        testBlock();
    

    从输出结果可以看出:
    block 内的 weakObj 和外部的 weakObj 并不是同一个变量
    block 捕获了 weakObj 同时也是对 obj 进行了弱引用,当我在 block 外把 obj 释放了之后,block 内也读不到这个变量了
    当 obj 赋值 nil 时,block 内部的 weakObj 也为 nil 了,也就是说 obj 实际上是被释放了,可见 __weak 是可以避免循环引用问题的。

    第二段代码:

        MyObject *obj = [[MyObject alloc]init];
        obj.text = @"my-object";
        TLog(@"obj", obj);
    
        __weak MyObject *weakObj = obj;
        TLog(@"weakObj-0", weakObj);
    
        void(^testBlock)() = ^(){
            __strong MyObject *strongObj = weakObj;
            TLog(@"weakObj - block", weakObj);
            TLog(@"strongObj - block", strongObj);
        };
    
        TLog(@"weakObj-1", weakObj);
        testBlock();
        TLog(@"weakObj-2", weakObj);
        obj = nil;
        testBlock();
        TLog(@"weakObj-3", weakObj);
    

    从上面例子我们看到即使在 block 内部用 strong 强引用了外面的 weakObj
    但是一旦 obj 释放了之后,内部的 strongObj 同样会变成 nil,那么这种写法又有什么意义呢?

          MyObject *obj = [[MyObject alloc]init];
          obj.text = @"my-object";
          TLog(@"obj", obj);
      
        __weak MyObject *weakObj = obj;
        TLog(@"weakObj-0", weakObj);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
            __strong MyObject *strongObj = weakObj;
            TLog(@"weakObj - block", weakObj);
            TLog(@"strongObj - block", strongObj);
    
            sleep(3);
    
            TLog(@"weakObj - block", weakObj);
            TLog(@"strongObj - block", strongObj);
        });
        NSLog(@"------ sleep 1s");
        sleep(1);
        obj = nil;
        TLog(@"weakObj-1", weakObj);
        NSLog(@"------ sleep 5s");
        sleep(5);
        TLog(@"weakObj-2", weakObj);
    

    代码中使用 sleep 来保证代码执行的先后顺序。从结果中我们可以看到,只要 block 部分执行了,即使我们中途释放了 obj,block 内部依然会继续强引用它。对比上面代码,也就是说 block 内部的 __strong 会在执行期间进行强引用操作,保证在 block 内部 strongObj 始终是可用的。这种写法非常巧妙,既避免了循环引用的问题,又可以在 block 内部持有该变量。

    2.__block在Block 中的使用

    MyObject *obj = [[MyObject alloc] init];
    obj.text = @"my-object-1";
    TLog(@"obj", obj);
    
    __block MyObject *blockObj  =obj;
    
    obj  = nil;
    
    TLog(@"blockObj -1", blockObj);
    
    void(^testBlock)() =^() {
      
        TLog(@"blockObj - blokc", blockObj);
        
        MyObject *obje2  = [[MyObject alloc] init];
        
        obje2.text =@"my-object-2";
        
        TLog(@"obj2", obje2);
        
        blockObj = obje2;
        
        TLog(@"blockObj - block",blockObj);
        
    };
    
    NSLog(@"%@",testBlock);
    
    TLog(@"blockObj -2",blockObj);
    
    testBlock();
    
    TLog(@"blockObj -3",blockObj);
    
    MyObject *obj = [[MyObject alloc]init];
    obj.text = @"11111111111111";
    TLog(@"obj",obj);
    
    __block MyObject *blockObj = obj;
    obj = nil;
    void(^testBlock)() = ^(){
        TLog(@"blockObj - block",blockObj);
    };
    obj = nil;
    testBlock();
    TLog(@"blockObj",blockObj);
    

    当外部 obj 指向 nil 的时候,obj 理应被释放,但实际上 blockObj 依然强引用着 obj,obj 其实并没有被真正释放。因此使用 __block 并不能避免循环引用的问题。

    但是我们可以通过手动释放 blockObj 的方式来释放 obj,这就需要我们在 block 内部将要退出的时候手动释放掉 blockObj ,如下这种形式

    MyObject *obj = [[MyObject alloc]init];
    obj.text = @"11111111111111";
    TLog(@"obj",obj);
    
    __block MyObject *blockObj = obj;
    obj = nil;
    void(^testBlock)() = ^(){
        TLog(@"blockObj - block",blockObj);
        blockObj = nil;
    };
    obj = nil;
    testBlock();
    TLog(@"blockObj",blockObj);
    

    这种形式既能保证在 block 内部能够访问到 obj,又可以避免循环引用的问题,但是这种方法也不是完美的,其存在下面几个问题

    必须记住在 block 底部释放掉 block 变量,这其实跟 MRC 的形式有些类似了,不太适合 ARC

    当在 block 外部修改了 blockObj 时,block 内部的值也会改变,反之在 block 内部修改 blockObj 在外部再使用时值也会改变。这就需要在写代码时注意这个特性可能会带来的一些隐患

    __block 其实提升了变量的作用域,在 block 内外访问的都是同一个 blockObj 可能会造成一些隐患

    3. Block如何修改局部变量

    在block 中 不可以直接修改局部变量 如果要修改局部变量的值 在局部变量前使用下划线下划线block修饰,在声明Block之后,调用Block之前对局部变量进行修改,在调用Block时局部变量值是修改之后的新值。

    Block不允许修改外部变量的值,这里所说的外部变量的值,指的是栈中指针的内存地址。

    __block所起到的作用就是只要观察到该变量被 block 所持有,就将“外部变量”在栈中的内存地址放到了堆中。 进而在block内部也可以修改外部变量的值。

     __block  int global  = 100;
    
    void(^myBlock)() =^{
        
        NSLog(@"global = %d",global);
    };
    
    global = 200;
    myBlock();
    

    在这里global是局部变量,所以是放在栈当中的。block相当于另一个内部函数,要在另一个作用域中改变栈中指针的内存地址是不行的。此时,如果block想要在内部进行操作就要将global变量拷贝到堆中,程序才能去改变。

    在block中再次打印global地址,可以发现地址不一样了。这就是__block的作用,它将栈中的变量a进行copy放到堆上。(此时输出的是堆地址)

    0x7开头的是在栈上的地址。0x1开头的为堆上地址。
    在调用block后,global变量就已经是新的内存地址,也不再是栈上。

    这里如果不加__block,global = 200就是修改a指针的内存地址。这样是不行的。

    4.堆 栈 简介

    在计算机系统中,运行的应用程序的数据都是保存在内存中的,不同类型的数据,保存的内存区域不同。

    1. 栈区(stack) 由编译器自动分配并释放,存放函数的参数值,局部变量等。栈是系统数据结构,对应线程/进程是唯一的。
      优点是快速高效,缺点时有限制,数据不灵活。[先进后出]

    栈空间分静态分配 和动态分配两种。

    静态分配是编译器完成的,比如自动变量(auto)的分配。
    动态分配由alloca函数完成。
    栈的动态分配无需释放(是自动的),也就没有释放函数。
    为可移植的程序起见,栈的动态分配操作是不被鼓励的!

    2.堆区(heap) 由程序员分配和释放,如果程序员不释放,程序结束时,可能会由操作系统回收 ,比如在ios 中 alloc 都是存放在堆中。
    优点是灵活方便,数据适应面广泛,但是效率有一定降低。[顺序随意]

    堆是函数库内部数据结构,不一定唯一。
    不同堆分配的内存无法互相操作。
    堆空间的分配总是动态的
    虽然程序结束时所有的数据空间都会被释放回系统,但是精确的申请内存,释放内存匹配是良好程序的基本要素。

    总结

    __weak 本身是可以避免循环引用的问题的,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong 的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题。

    __block 本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。

    另外一点就是 __block 修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。

    相关文章

      网友评论

      本文标题:Block 使用__weak 与 __block的作用

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