__weak与__block区别,深层理解两者区别

http://www.cnblogs.com/yajunLi/p/6203222.html?utm_source=itdadao&utm_medium=referral'

准备工作

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

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

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

__weak

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();

变量内存地址:0x7fff58c8a9f0, 变量值:0x7f8e0307f1d0, 指向对象值:my-object, -->obj

变量内存地址:0x7fff58c8a9e8, 变量值:0x7f8e0307f1d0, 指向对象值:my-object, -->weakObj

变量内存地址:0x7f8e030804c0, 变量值:0x7f8e0307f1d0, 指向对象值:my-object, --> weakObj -block

变量内存地址:0x7f8e030804c0, 变量值:0x0, 指向对象值:(null), --> weakObj - block

从上面的结果可以看到

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);

变量内存地址:0x7fff5d7b2d18, 变量值:0x7fcf78c11e80, 指向对象值:my-object, -->obj

变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-0变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-1变量内存地址:0x7fcf78f0f520, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj -block

变量内存地址:0x7fff5d7b2bb8, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> strongObj -block

变量内存地址:0x7fff5d7b2d10, 变量值:0x7fcf78c11e80, 指向对象值:my-object, --> weakObj-2变量内存地址:0x7fcf78f0f520, 变量值:0x0, 指向对象值:(null), --> weakObj -block

变量内存地址:0x7fff5d7b2bb8, 变量值:0x0, 指向对象值:(null), --> strongObj -block

变量内存地址:0x7fff5d7b2d10, 变量值:0x0, 指向对象值:(null), --> weakObj-3

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

从上面例子我们看到即使在 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);

变量内存地址:0x7fff58e2ad18, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, -->obj

变量内存地址:0x7fff58e2ad10, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj-0变量内存地址:0x7fa2b1e80710, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj -block

变量内存地址:0x700000093de8, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> strongObj -block------sleep 1s

变量内存地址:0x7fff58e2ad10, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj-1------sleep 5s

变量内存地址:0x7fa2b1e80710, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> weakObj -block

变量内存地址:0x700000093de8, 变量值:0x7fa2b1e804e0, 指向对象值:my-object, --> strongObj -block

变量内存地址:0x7fff58e2ad10, 变量值:0x0, 指向对象值:(null), --> weakObj-2

代码中使用 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

先上代码

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 - block",blockObj);

MyObject*obj2 =[[MyObject alloc]init];

obj2.text=@"my-object-2";

TLog(@"obj2",obj2);

blockObj=obj2;

TLog(@"blockObj - block",blockObj);

};

NSLog(@"%@",testBlock);

TLog(@"blockObj -2",blockObj);

testBlock();

TLog(@"blockObj -3",blockObj);

变量内存地址:0x7fff5021a9f0, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, -->obj

变量内存地址:0x7fff5021a9e8, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, --> blockObj -1<__NSMallocBlock__:0x7ff6b48d8c20>变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, --> blockObj -2变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d8cd0, 指向对象值:my-object-1, --> blockObj -block

变量内存地址:0x7fff5021a7f8, 变量值:0x7ff6b48d9960, 指向对象值:my-object-2, -->obj2

变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d9960, 指向对象值:my-object-2, --> blockObj -block

变量内存地址:0x7ff6b48da518, 变量值:0x7ff6b48d9960, 指向对象值:my-object-2, --> blockObj -3

可以看到在 block 声明前后 blockObj 的内存地址是有所变化的,这涉及到 block 对外部变量的内存管理问题,大家可以看扩展阅读中的几篇文章,对此有较深入的分析。

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

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);

变量内存地址:0x7fff57eef9f0, 变量值:0x7ff86a55a160, 指向对象值:11111111111111, -->obj

变量内存地址:0x7ff86c918a88, 变量值:0x7ff86a55a160, 指向对象值:11111111111111, --> blockObj -block

变量内存地址:0x7ff86c918a88, 变量值:0x7ff86a55a160, 指向对象值:11111111111111, --> 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 底部释放掉 block 变量,这其实跟 MRC 的形式有些类似了,不太适合 ARC这种形式既能保证在 block 内部能够访问到 obj,又可以避免循环引用的问题,但是这种方法也不是完美的,其存在下面几个问题

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

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

总结!!!

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

__block本身无法避免循环引用的问题,但是我们可以通过在 block 内部手动把 blockObj 赋值为 nil 的方式来避免循环引用的问题。另外一点就是__block修饰的变量在 block 内外都是唯一的,要注意这个特性可能带来的隐患。

但是__block有一点:这只是限制在ARC环境下。在非arc下,__block是可以避免引用循环的

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容