block的那些事

block分为__NSGlobalBlock__,__NSStackBlock__,__NSMallocBlock__。

在MRC中

#import "NoArc.h"

typedef int (^Sun)(void);

@interface NoArc()
@property (nonatomic, strong) Sun s1;
@property (nonatomic, copy) Sun s2;
@property (nonatomic, retain) Sun s3;
@property (nonatomic, assign) Sun s4;
@end

@implementation NoArc

-(void)blockTest{
    
    void (^SunveraAll)() = ^{
        NSLog(@"SunveraAll");
    };
    NSLog(@"SunveraAll-----%@", SunveraAll);
    __block int val =10;
    Sun s= ^{
        NSLog(@"val_block = %d", val);
        return val;
        
    };
    s();
    self.s1 = s;
    self.s2 = s;
    self.s3 = s;
    self.s4 = s;
    NSLog(@"s-----%@", s);
    NSLog(@"self.s1-----%@", self.s1);
    NSLog(@"self.s2-----%@", self.s2);
    NSLog(@"self.s3-----%@", self.s3);
    NSLog(@"self.s4-----%@", self.s4);
}
    
@end

打印结果

2018-10-01 19:48:40.996 JS_OC_WebViewJavascriptBridge[80707:1818340] SunveraAll-----<__NSGlobalBlock__: 0x10b625918>
2018-10-01 19:48:40.997 JS_OC_WebViewJavascriptBridge[80707:1818340] val_block = 10
2018-10-01 19:48:43.506 JS_OC_WebViewJavascriptBridge[80707:1818340] s-----<__NSStackBlock__: 0x7fff545e8488>
2018-10-01 19:48:43.506 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s1-----<__NSMallocBlock__: 0x7f94d2d285d0>
2018-10-01 19:48:43.506 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s2-----<__NSMallocBlock__: 0x7f94d2d27e80>
2018-10-01 19:48:43.506 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s3-----<__NSStackBlock__: 0x7fff545e8488>
2018-10-01 19:48:43.507 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s4-----<__NSStackBlock__: 0x7fff545e8488>

在ARC中执行同样的代码打印结果是

2018-10-01 19:48:45.563 JS_OC_WebViewJavascriptBridge[80707:1818340] SunveraAll-----<__NSGlobalBlock__: 0x10b625648>
2018-10-01 19:48:45.563 JS_OC_WebViewJavascriptBridge[80707:1818340] val_block = 10
2018-10-01 19:48:45.563 JS_OC_WebViewJavascriptBridge[80707:1818340] s-----<__NSMallocBlock__: 0x7f94d2e5f270>
2018-10-01 19:48:45.564 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s1-----<__NSMallocBlock__: 0x7f94d2e5f270>
2018-10-01 19:48:45.564 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s2-----<__NSMallocBlock__: 0x7f94d2e5f270>
2018-10-01 19:48:45.564 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s3-----<__NSMallocBlock__: 0x7f94d2e5f270>
2018-10-01 19:48:45.564 JS_OC_WebViewJavascriptBridge[80707:1818340] self.s4-----<__NSMallocBlock__: 0x7f94d2e5f270>

由此可见在ARC中没有栈区block,只有堆区和全局区。

在MRC中,有栈区,并且strong相当于copy,会把block从栈区copy到堆区,用retain和assign并不会copy到堆区,个人感觉用strong和copy修饰都可以,至于为什么官方推荐block用copy来修饰,我就不太清楚了。

 

block传值

__weak

   Arc *obj = [[Arc alloc]init];
    NSLog(@"obj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&obj,obj,obj);
    
    __weak Arc *weakObj = obj;
    NSLog(@"weakObj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
    
    void(^testBlock)() = ^(){
        NSLog(@"weakObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
    };
    testBlock();
    obj = nil;
    testBlock();
2018-10-02 10:32:01.155 JS_OC_WebViewJavascriptBridge[85577:1924012] obj变量内存地址:0x7fff55666518, 变量值:0x7fb5bb642960, 指向对象值:<Arc: 0x7fb5bb642960>
2018-10-02 10:32:01.156 JS_OC_WebViewJavascriptBridge[85577:1924012] weakObj变量内存地址:0x7fff55666510, 变量值:0x7fb5bb642960, 指向对象值:<Arc: 0x7fb5bb642960>
2018-10-02 10:32:01.156 JS_OC_WebViewJavascriptBridge[85577:1924012] weakObj - block变量内存地址:0x7fb5bb642dc0, 变量值:0x7fb5bb642960, 指向对象值:<Arc: 0x7fb5bb642960>
2018-10-02 10:32:01.156 JS_OC_WebViewJavascriptBridge[85577:1924012] weakObj - block变量内存地址:0x7fb5bb642dc0, 变量值:0x0, 指向对象值:(null)

从上面的结果可以看到

  • block 内的 weakObj 和外部的 weakObj 并不是同一个变量
  • block 捕获了 weakObj 同时也是对 obj 进行了弱引用,当我在 block 外把 obj 释放了之后,block 内也读不到这个变量了
  • 当 obj 赋值 nil 时,block 内部的 weakObj 也为 nil 了,也就是说 obj 实际上是被释放了,可见 __weak 是可以避免循环引用问题的
    Arc *obj = [[Arc alloc]init];
    NSLog(@"obj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&obj,obj,obj);
    __weak Arc *weakObj = obj;
    NSLog(@"weakObj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
    void(^testBlock)() = ^(){
        __strong Arc *strongObj = weakObj;
        NSLog(@"weakObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
        NSLog(@"strongObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&strongObj,strongObj,strongObj);
    };
    NSLog(@"weakObj-1---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
    testBlock();
    NSLog(@"weakObj-2---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
    obj = nil;
    testBlock();
    NSLog(@"weakObj-3---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
2018-10-02 10:43:54.256 JS_OC_WebViewJavascriptBridge[85849:1930209] obj---变量内存地址:0x7fff5a694518, 变量值:0x7fb6e17470d0, 指向对象值:<Arc: 0x7fb6e17470d0>
2018-10-02 10:43:54.258 JS_OC_WebViewJavascriptBridge[85849:1930209] weakObj---变量内存地址:0x7fff5a694510, 变量值:0x7fb6e17470d0, 指向对象值:<Arc: 0x7fb6e17470d0>
2018-10-02 10:43:54.258 JS_OC_WebViewJavascriptBridge[85849:1930209] weakObj-1---变量内存地址:0x7fff5a694510, 变量值:0x7fb6e17470d0, 指向对象值:<Arc: 0x7fb6e17470d0>
2018-10-02 10:43:54.258 JS_OC_WebViewJavascriptBridge[85849:1930209] weakObj - block---变量内存地址:0x7fb6e1747400, 变量值:0x7fb6e17470d0, 指向对象值:<Arc: 0x7fb6e17470d0>
2018-10-02 10:43:54.259 JS_OC_WebViewJavascriptBridge[85849:1930209] strongObj - block---变量内存地址:0x7fff5a6943c8, 变量值:0x7fb6e17470d0, 指向对象值:<Arc: 0x7fb6e17470d0>
2018-10-02 10:43:54.259 JS_OC_WebViewJavascriptBridge[85849:1930209] weakObj-2---变量内存地址:0x7fff5a694510, 变量值:0x7fb6e17470d0, 指向对象值:<Arc: 0x7fb6e17470d0>
2018-10-02 10:43:54.259 JS_OC_WebViewJavascriptBridge[85849:1930209] weakObj - block---变量内存地址:0x7fb6e1747400, 变量值:0x0, 指向对象值:(null)
2018-10-02 10:43:54.291 JS_OC_WebViewJavascriptBridge[85849:1930209] strongObj - block---变量内存地址:0x7fff5a6943c8, 变量值:0x0, 指向对象值:(null)
2018-10-02 10:43:54.291 JS_OC_WebViewJavascriptBridge[85849:1930209] weakObj-3---变量内存地址:0x7fff5a694510, 变量值:0x0, 指向对象值:(null)

如果你看过 AFNetworking 的源码,会发现 AFN 中作者会把变量在 block 外面先用 __weak 声明,在 block 内把前面 weak 声明的变量赋值给 __strong 修饰的变量这种写法。

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

下面再看一段代码:

 Arc *obj = [[Arc alloc]init];
    NSLog(@"obj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&obj,obj,obj);
    __weak Arc *weakObj = obj;
    NSLog(@"weakObj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
    
    dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
        __strong Arc *strongObj = weakObj;
        NSLog(@"weakObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
        NSLog(@"strongObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&strongObj,strongObj,strongObj);
        
        sleep(3);
        
        NSLog(@"weakObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
        NSLog(@"strongObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&strongObj,strongObj,strongObj);
    });
    NSLog(@"------ sleep 1s");
    sleep(1);
    obj = nil;
    NSLog(@"weakObj-1---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
    NSLog(@"------ sleep 5s");
    sleep(5);
    NSLog(@"weakObj-2---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&weakObj,weakObj,weakObj);
2018-10-02 10:51:25.099 JS_OC_WebViewJavascriptBridge[86042:1934684] obj---变量内存地址:0x7fff577fc518, 变量值:0x7fa468738e80, 指向对象值:<Arc: 0x7fa468738e80>
2018-10-02 10:51:25.100 JS_OC_WebViewJavascriptBridge[86042:1934684] weakObj---变量内存地址:0x7fff577fc510, 变量值:0x7fa468738e80, 指向对象值:<Arc: 0x7fa468738e80>
2018-10-02 10:51:25.100 JS_OC_WebViewJavascriptBridge[86042:1934684] ------ sleep 1s
2018-10-02 10:51:25.100 JS_OC_WebViewJavascriptBridge[86042:1934707] weakObj - block---变量内存地址:0x7fa468454da0, 变量值:0x7fa468738e80, 指向对象值:<Arc: 0x7fa468738e80>
2018-10-02 10:51:25.100 JS_OC_WebViewJavascriptBridge[86042:1934707] strongObj - block---变量内存地址:0x7000033bfde8, 变量值:0x7fa468738e80, 指向对象值:<Arc: 0x7fa468738e80>
2018-10-02 10:51:26.105 JS_OC_WebViewJavascriptBridge[86042:1934684] weakObj-1---变量内存地址:0x7fff577fc510, 变量值:0x7fa468738e80, 指向对象值:<Arc: 0x7fa468738e80>
2018-10-02 10:51:26.106 JS_OC_WebViewJavascriptBridge[86042:1934684] ------ sleep 5s
2018-10-02 10:51:28.104 JS_OC_WebViewJavascriptBridge[86042:1934707] weakObj - block---变量内存地址:0x7fa468454da0, 变量值:0x7fa468738e80, 指向对象值:<Arc: 0x7fa468738e80>
2018-10-02 10:51:28.105 JS_OC_WebViewJavascriptBridge[86042:1934707] strongObj - block---变量内存地址:0x7000033bfde8, 变量值:0x7fa468738e80, 指向对象值:<Arc: 0x7fa468738e80>
2018-10-02 10:51:31.108 JS_OC_WebViewJavascriptBridge[86042:1934684] weakObj-2---变量内存地址:0x7fff577fc510, 变量值:0x0, 指向对象值:(null)

代码中使用 sleep 来保证代码执行的先后顺序。

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

综合两部分代码,我们平时在使用时,常常先判断 strongObj 是否为空,然后再执行后续代码,如下方式:

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
   __strong MyObject *strongObj = weakObj;
   if (strongObj) {
       // do something ...
   }
});

这种方式先判断 Obj 是否被释放,如果未释放在执行我们的代码的时候保证其可用性。

__block

 

    Arc *obj = [[Arc alloc]init];
    NSLog(@"obj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&obj,obj,obj);
    __block Arc *blockObj = obj;
    NSLog(@"blockObj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
    obj = nil;
    NSLog(@"blockObj-1---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
    
    void(^testBlock)() = ^(){
        NSLog(@"blockObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
        Arc *obj2 = [[Arc alloc]init];
        NSLog(@"obj2---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&obj2,obj2,obj2);
        blockObj = obj2;
        NSLog(@"blockObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
    };
    NSLog(@"%@",testBlock);
    NSLog(@"blockObj - 2---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
    testBlock();
    NSLog(@"blockObj - 3---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);

 

2018-10-02 11:41:10.608 JS_OC_WebViewJavascriptBridge[87017:1953201] obj---变量内存地址:0x7fff5a4e0518, 变量值:0x7ffb02d33670, 指向对象值:<Arc: 0x7ffb02d33670>
2018-10-02 11:41:10.609 JS_OC_WebViewJavascriptBridge[87017:1953201] blockObj---变量内存地址:0x7fff5a4e0510, 变量值:0x7ffb02d33670, 指向对象值:<Arc: 0x7ffb02d33670>
2018-10-02 11:41:10.609 JS_OC_WebViewJavascriptBridge[87017:1953201] blockObj-1---变量内存地址:0x7fff5a4e0510, 变量值:0x7ffb02d33670, 指向对象值:<Arc: 0x7ffb02d33670>
2018-10-02 11:41:10.610 JS_OC_WebViewJavascriptBridge[87017:1953201] <__NSMallocBlock__: 0x7ffb02e46e10>
2018-10-02 11:41:10.610 JS_OC_WebViewJavascriptBridge[87017:1953201] blockObj - 2---变量内存地址:0x7ffb02f4b378, 变量值:0x7ffb02d33670, 指向对象值:<Arc: 0x7ffb02d33670>
2018-10-02 11:41:10.610 JS_OC_WebViewJavascriptBridge[87017:1953201] blockObj - block---变量内存地址:0x7ffb02f4b378, 变量值:0x7ffb02d33670, 指向对象值:<Arc: 0x7ffb02d33670>
2018-10-02 11:41:10.611 JS_OC_WebViewJavascriptBridge[87017:1953201] obj2---变量内存地址:0x7fff5a4e0448, 变量值:0x7ffb02e49f40, 指向对象值:<Arc: 0x7ffb02e49f40>
2018-10-02 11:41:10.611 JS_OC_WebViewJavascriptBridge[87017:1953201] blockObj - block---变量内存地址:0x7ffb02f4b378, 变量值:0x7ffb02e49f40, 指向对象值:<Arc: 0x7ffb02e49f40>
2018-10-02 11:41:10.612 JS_OC_WebViewJavascriptBridge[87017:1953201] blockObj - 3---变量内存地址:0x7ffb02f4b378, 变量值:0x7ffb02e49f40, 指向对象值:<Arc: 0x7ffb02e49f40>

可以看到在 block 声明前后 blockObj 的内存地址是有所变化的,这涉及到 block 对外部变量的内存管理问题。

下面来看看 __block 能不能避免循环引用的问题

    Arc *obj = [[Arc alloc]init];
    NSLog(@"obj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&obj,obj,obj);
    __block Arc *blockObj = obj;
    obj = nil;
   
    void(^testBlock)() = ^(){
        NSLog(@"blockObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
    };
    obj = nil;
    testBlock();
    NSLog(@"blockObj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
2018-10-02 11:45:05.530 JS_OC_WebViewJavascriptBridge[87107:1955309] obj---变量内存地址:0x7fff4fcdc518, 变量值:0x7fc064e30fd0, 指向对象值:<Arc: 0x7fc064e30fd0>
2018-10-02 11:45:05.532 JS_OC_WebViewJavascriptBridge[87107:1955309] blockObj - block---变量内存地址:0x7fc064d41ee8, 变量值:0x7fc064e30fd0, 指向对象值:<Arc: 0x7fc064e30fd0>
2018-10-02 11:45:05.532 JS_OC_WebViewJavascriptBridge[87107:1955309] blockObj---变量内存地址:0x7fc064d41ee8, 变量值:0x7fc064e30fd0, 指向对象值:<Arc: 0x7fc064e30fd0>

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

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

Arc *obj = [[Arc alloc]init];
    NSLog(@"obj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&obj,obj,obj);
    __block Arc *blockObj = obj;
    obj = nil;
   
    void(^testBlock)() = ^(){
        NSLog(@"blockObj - block---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
        blockObj = nil;
    };
    obj = nil;
    testBlock();
    NSLog(@"blockObj---变量内存地址:%p, 变量值:%p, 指向对象值:%@",&blockObj,blockObj,blockObj);
2018-10-02 11:47:08.878 JS_OC_WebViewJavascriptBridge[87167:1956769] obj---变量内存地址:0x7fff54257518, 变量值:0x7ffd04d519a0, 指向对象值:<Arc: 0x7ffd04d519a0>
2018-10-02 11:47:08.879 JS_OC_WebViewJavascriptBridge[87167:1956769] blockObj - block---变量内存地址:0x7ffd04c570f8, 变量值:0x7ffd04d519a0, 指向对象值:<Arc: 0x7ffd04d519a0>
2018-10-02 11:47:08.879 JS_OC_WebViewJavascriptBridge[87167:1956769] blockObj---变量内存地址:0x7ffd04c570f8, 变量值:0x0, 指向对象值:(null)

必须记住在 block 底部释放掉 block 变量,这其实跟 MRC 的形式有些类似了,不太适合 ARC这种形式既能保证在 block 内部能够访问到 obj,又可以避免循环引用的问题,但是这种方法也不是完美的,其存在下面几个问题

  • 当在 block 外部修改了 blockObj 时,block 内部的值也会改变,反之在 block 内部修改 blockObj 在外部再使用时值也会改变。这就需要在写代码时注意这个特性可能会带来的一些隐患
  • __block 其实提升了变量的作用域,在 block 内外访问的都是同一个 blockObj 可能会造成一些隐患

总结!!!

  • block下循环引用的问题

    • __block本身并不能避免循环引用,避免循环引用需要在block内部把__block修饰的obj置为nil
    • __weak可以避免循环引用,但是其会导致外部对象释放了之后,block 内部也访问不到这个对象的问题,我们可以通过在 block 内部声明一个 __strong
      的变量来指向 weakObj,使外部对象既能在 block 内部保持住,又能避免循环引用的问题
  • __block与__weak功能上的区别。

    • __block会持有该对象,即使超出了该对象的作用域,该对象还是会存在的,直到block对象从堆上销毁;而__weak仅仅是将该对象赋值给weak对象,当该对象销毁时,weak对象将指向nil;
    • __block可以让block修改局部变量,而__weak不能。

另外,MRC中__block是不会引起retain;但在ARC中__block则会引起retain。所以ARC中应该使用__weak。

 

因此,__block和__weak修饰符的区别其实是挺明显的: 
1.__block不管是ARC还是MRC模式下都可以使用,可以修饰对象,还可以修饰基本数据类型。 
2.__weak只能在ARC模式下使用,也只能修饰对象(NSString),不能修饰基本数据类型(int)。 
3.__block对象可以在block中被重新赋值,__weak不可以。 
4.__block对象在ARC下可能会导致循环引用,非ARC下会避免循环引用,__weak只在ARC下使用,可以避免循环引用。

 http://www.cocoachina.com/ios/20180628/23965.html

posted on 2019-01-16 19:02  sunyaxue  阅读(142)  评论(0编辑  收藏  举报

导航