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