在使用 Block 时,除了使用 __weak 修饰符避免循环引用外,还有一点经常容易忘记。苹果把它称为:“Strong-Weak Dance”。

问题来源

这是一种 强引用 --> 弱引用 --> 强引用 的变换过程。在弄明白为什么要如此大费周章之前,我们首先来看看一般的写法会有什么问题。

__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
[wself.property removeObserver: wself forKeyPath:@"pathName"];
};
复制代码

这种写法可以避免循环引用,但是我们要考虑这样的问题:

假设 block 被放在子线程中执行,而且执行过程中 self 在主线程被释放了。由于 wself 是一个弱引用,因此会自动变为 nil。而在 KVO 中,这会导致崩溃。

Strong-Weak Dance

解决以上问题的方法很简单,新增一行代码即可:

__weak MyViewController *wself = self;
self.completionHandler = ^(NSInteger result) {
__strong __typeof(wself) sself = wself; // 强引用一次
[sself.property removeObserver: sself forKeyPath:@"pathName"];
};
复制代码

这样一来,self 所指向对象的引用计数变成 2,即使主线程中的 self 因为超出作用于而释放,对象的引用计数依然为 1,避免了对象的销毁。

思考

在和小伙伴的讨论过程中,他提出了几个问题。虽然都不难,但是有利于把各种知识融会贯通起来。

  1. Q:下面这行代码,将一个弱引用的指针赋值给强引用的指针,可以起到强引用效果么?
__strong __typeof(wself) sself = wself;
复制代码

A:会的。引用计数描述的是对象而不是指针。这句话的意思是:

sself 强引用 wself 指向的那个对象

因此对象的引用计数会增加一个。

  1. Q:block 内部定义了sself,会不会因此强引用了 sself

A:不会。block 只有截获外部变量时,才会引用它。如果是内部新建一个,则没有任何问题。

  1. Q:如果在 block 内部没有强引用,而是通过 if 判断,是不是也可以,比如这样写:
__weak MyViewController *wself = self;
wself.completionHandler = ^(NSInteger result) {
if (wself) { // 只有当 wself 不为 nil 时,才执行以下代码
[wself.property removeObserver: wself forKeyPath:@"pathName"];
}
};
复制代码

A:不可以!考虑到多线程执行,也许在判断的时候,self 还没释放,但是执行 self 里面的代码时,就刚好释放了。

  1. Q:那按照这个说法,block 内部强引用也没用啊。也许 block 执行以前,self 就释放了。

A:有用!如果在 block 执行以前,self 就释放了,那么 block 的引用计数降为 0,所以自己就会被释放。这样它根本就不会被执行。另外,如果执行一个为 nil 的闭包会导致崩溃。

  1. Q:如果在执行 block 的过程中,block 被释放了怎么办?

A:简单来说,block 还会继续执行,但是它捕获的指针会具有不确定的值,详细内容请参考这篇文章

@strongify 和 @weakify

这是 ReactiveCocoa 中定义的一个宏。一般可以这样使用:

@weakify(self);
self.completionHandler = ^(NSInteger result) {
@strongify(self);
[self.property removeObserver: sself forKeyPath:@"pathName"];
};
复制代码

本文并非分析它们的实现原理,所以就简单解释两点:

  1. 这里的“@”没有任何用处,仅表示强调,这个宏实际上包含了一个空的 AutoreleasePool,这也就是为什么一定要加上“@”。

  2. 它的原理还是和之前一样,生成了一段形如 __weak MyViewController *wself = self; 这种格式的代码:

#define rac_strongify_(INDEX, VAR) \\
__strong __typeof__(VAR) VAR = metamacro_concat(VAR, _weak_);
复制代码

Swift 中的情况

感谢 @Cyrus_dev 的提醒,在 Swift 中也有 Strong-Weak Dance 的概念。最简单的方法就是直接沿用 OC 的思路:

self.completionHandler = { [weak self] in
if let strongSelf = self {
/// ....
}
};
复制代码

这种写法的缺点在于,我们不能写 if let self = self,因此需要重新定义一个变量 strongSelf,命名方式显得不够优雅。

除此以外还可以使用 Swift 标准库提供的函数 withExtendedLifetime

self.completionHandler = { [weak self] in
withExtendedLifetime(self) {
/// ...
}
};
复制代码

这种写法的缺点在于,self 依然是可选类型的,还需要把它解封后才能使用。

最后,还有一种解决方案是自定义 withExtendedLifetime函数:

extension Optional {
func withExtendedLifetime(body: T -> Void) {
if let strongSelf = self {
body(strongSelf)
}
}
}
复制代码

至于这种写法是否更加优雅,就见仁见智了:

self.completionHandler = { [weak self] in
self.withExtendedLifetime {
/// 这里用 $0 表示 self
}
};
复制代码

参考资料

  1. What happens when a block is set to nil during its execution?
  2. The Weak/Strong Dance in Swift

对 Strong-Weak Dance的思考相关推荐

  1. 关于@property()的那些属性及ARC简介【nonatomic,atomic,assign,retain,strong,weak,copy。】

    @property()常用的属性有:nonatomic,atomic,assign,retain,strong,weak,copy. 其中atomic和nonatomic用来决定编译器生成的gette ...

  2. @property 各个属性作用【使用时最需注意strong/weak类型】【补充部分内存知识】...

    一. 属性readwrite,readonly,assign,retain,copy,nonatomic 等各是什么作用,在那种情况下用? 1>  readwrite:同时生成get方法和set ...

  3. 对copy/strong/weak/__weak/__strong/assign的使用场景总结总结

    ARC在编译期间,根据Objective-C对象的存活周期,在适当的位置添加retain和release代码.从概念上讲,ARC与手动引用计数内存管理遵循同样的内存管理规则,但是ARC也无法防止循环强 ...

  4. copy, retain, assign , readonly , readwrite,strong,weak,nonatomic整理

    copy:建立一个索引计数为1的对象,然后释放旧对象 对NSString 对NSString 它指出,在赋值时使用传入值的一份拷贝.拷贝工作由copy方法执行,此属性只对那些实行了NSCopying协 ...

  5. assign,copy,strong,weak,nonatomic的具体理解

    例子: NSString *houseOfMM = [[NSString alloc] initWithString:'MM的三室两厅']; 上面一段代码会执行以下两个动作:   1 在堆上分配一段内 ...

  6. assign,copy,strong,weak,nonatomic的理解

    举个例子: NSString *houseOfMM = [[NSString alloc] initWithString:'MM的三室两厅']; 上面一段代码会执行以下两个动作:   1 在堆上分配一 ...

  7. iOS学习笔记-retain/assign/strong/weak/copy/mutablecopy/autorelease区别

    readwrite:是可读可写特性,需要生成getter和setter方法: readonly是之都特性,只会生成getter方法,不会生成setter方法,不希望属性在类外改变时候使用: alloc ...

  8. iOS开发-retain/assign/strong/weak/copy/mutablecopy/autorelease区别

    依旧本着尊重原创和劳动者的原则,将地址先贴在前面: http://www.cnblogs.com/nonato/archive/2013/11/28/3447162.html,作者Nonato 以下内 ...

  9. Objective-c属性修饰关键字(assign retain copy strong weak)

    一.线程相关: [atomic/nonatomic] (1)atomic[默认属性]:OC使用的一种线程保护技术,是防止在写未完成的时候被另外一个线程读取,造成数据错误.而这种机制是耗费系统资源的,所 ...

最新文章

  1. qsort函数的用法
  2. linux编程之GDB调试
  3. css知识点笔记-常用属性
  4. ucOS_II移植:Stm32启动代码分析
  5. JavaScript 动态创建标记
  6. python tempfile cleanup_python tempfile 模块---生成临时文件和目录
  7. prometheus命令_Prometheus 入门
  8. c语言中有关main的问题,c语言小问题
  9. Ubuntu的超宽屏支持2560*1080
  10. 计算机控制系统第三次作业,城市轨道交通能控制系统第三次作业.ppt
  11. plsql链接oracle64位报错,PLSQL developer 连接不上64位Oracle 的解决方法
  12. 计算几何模板(kuangbin)
  13. Java|二叉树基础详解
  14. uLua和toLua原理解析
  15. 苹果手机微信怎么接龙_【手机】微信接龙
  16. 记录一次jar文件在windows系统下开机自启
  17. 思维导图是一个查漏补缺的好工具
  18. Spyder 打开失败问题解决
  19. Date int java_java.util.Calendar.set(int year, int month, int date)方法实例
  20. 关于360浏览器兼容模式下文档模式默认以ie7标准渲染

热门文章

  1. java之上三角矩阵_7-1 判断上三角矩阵
  2. 第三方登录如何给前台返回token_如何设计优雅的第三方账号登录?
  3. es6变量赋值重命名
  4. IE10,IE9透明元素无法点击
  5. 从后端获取数据然后在页面处理技巧
  6. 使用git向码云上提交代码
  7. 用java爬虫来提高CSDN博客访问量,浏览量
  8. Linux云服务器安装node.js
  9. VTK初始化New返回Null问题
  10. android程序运行无操作一段时间显示屏保