asunquan.com

不忘初心 不止于行

职场新人 iOS菜鸟 就职于搜狐畅游 从事iOS/tvOS的SDK开发 对于游戏市场有点点了解 平日和一只小Corgi同吃同睡


[转]面试题: weak的内部实现

题目

**weak 变量在引用计数为0时, 会被自动设置成 nil, 这个特性是如何实现的? **

分析及答案

在 Friday QA 上, 有一期专门介绍 weak 的实现原理. Zeroing Weak References in Objective-C

[Objective-C高级编程]一书中也介绍了相关的内容.

简单来说, 系统有一个全局的 CFMutableDictionary 实例, 来保存每个对象的 weak 指针列表, 因为每个对象可能有多个 weak 指针, 所以这个实例的值是 CFMutableSet 类型.

剩下我们要做的, 就是在引用计数变成 0 的时候, 去这个全局的字典里面, 找到所有的 weak 指针, 将其值设置成 nil. 如何做到这一点呢? Friday QA 上介绍了一种类似 KVO 实现的方式. 当对象存在 weak 指针时, 我们可以将这个实例指向一个新创建的子类, 然后修改这个子类的 release 方法, 在 release 方法中, 去从全局的 CFMutableDictionary 字典中找到所有的 weak 对象, 并且设置成 nil. 我摘抄了 Friday QA 上的实现的核心代码, 如下:

Class subclass = objc_allocateClassPair(class, newNameC, 0);
Method release = class_getInstanceMethod(class, @selector(release));
Method dealloc = class_getInstanceMethod(class, @selector(dealloc));
class_addMethod(subclass, @selector(release), (IMP)CustomSubclassRelease, method_getTypeEncoding(release));
class_addMethod(subclass, @selector(dealloc), (IMP)CustomSubclassDealloc, method_getTypeEncoding(dealloc));
objc_registerClassPair(subclass);

当然, 这并不代表苹果官方是这么实现的, 因为苹果的这部分代码并没有开源. «Objective-C高级编程»一书中介绍了 GNUStep 项目中的开源代码, 思想也是类似的. 所以我认为虽然实现细节会有差异, 但是大致的实现思路应该差别不大.

题目

我们知道, 从 Storyboard 往编译器拖出来的 UI 控件的属性是 weak 的, 如下所示:

@property (weak, nonatomic) IBOutlet UIButton *myButton;

那么, 如果有一些 UI 控件我们要用代码的方式来创建, 那么它应该用 weak 还是 strong 呢? 为什么?

分析及答案

这是一道有意思的问题, 这个问题是我当时和 Lancy 一起写猿题库 App 时产生的一次小争论. 简单来说, 这道题并没有标准答案, 但是答案背后的解释却非常有价值, 能够看出一个人对于引用计数, 对于 view 的生命周期的理解是否到位.

从昨天的评论上, 我们就能看到一些理解非常不到位的解释, 例如:

@spume 说:Storyboard 拖线使用 weak 是为了规避出现循环引用的问题。

这个理解是错误的, Storyboard 拖出来的控件即使是 strong 的, 也不会有循环引用问题.

我认为 UI 控件用默认用 weak, 根源还是苹果希望只有这些 UI 控件的父 View 来强引用它们, 而 ViewController 只需要强引用 ViewController.view 成员, 则可以间接持有所有的 UI 控件. 这样有一个好处是: 在以前, 当系统收到 Memory Warning 时, 会触发 ViewController 的 viewDidUnload 方法, 这样的弱引用方式, 可以让整个 view 整体都得到释放, 也更方便重建时整体重新构造.

但是首先 viewDidUnload 方法在 iOS 6 开始就被废弃掉了, 苹果用了更简单有效地方式来解决内存警告时的视图资源释放, 具体如何做的呢? 总之就是, 除非你特殊地操作 view 成员, ViewController.view 的生命期和 ViewController 是一样的了.

所以在这种情况下, 其实 UI 控件是不是 weak 其实关系并不大. 当 UI 控件是 weak 时, 它的引用计数是 1, 持有它的是它的 superview, 当 UI 控件是 strong 时, 它的引用计数是 2, 持有它的有两个地方, 一个是它的 superview, 另一个是这个 strong 的指针. UI 控件并不会持有别的对象, 所以, 不管是手写代码还是 Storyboard, UI 控件是 strong 都不会有循环引用的.

那么回到我们的最初的问题, 自己写的 view 成员, 应该用 weak 还是 strong? 我个人觉得应该用 strong, 因为用 weak 并没有什么特别的优势, 加上上一篇面试题文章中, 我们还看到, 其实 weak 变量会有额外的系统维护开销的, 如果你没有使用它的特别的理由, 那么用 strong 的话应该更好.

另外有读者也提到, 如果你要做 Lazy 加载, 那么你也只能选择用 strong.

当然, 如果你非要用 weak, 其实也没什么问题, 只需要注意在赋值前, 先把这个对象用 addSubView 加到父 view 上, 否则可能刚刚创建完, 它就被释放了.

在我心目中, 这才是我喜欢的面试题, 没有标准答案, 每种方案各有各的特点, 面试者能够足够分清楚每种方案的优缺点, 结合具体的场景做选择, 这才是优秀的面试者.

最近的文章

[转]面试题: 为什么 Objective-C 的方法调用要用方括号?

题目**为什么 Objective-C 的方法调用要用方括号 [obj foo] , 而不是别的语言常用的点 obj.foo ? **分析及答案首先要说的是, Objective-C 的历史相当久远, 如果你查 wiki 的话, 你会发现 Objective-C 和 C++ 这两种语言的发行年份都是1983年. 在设计之初, 二者都是作为 C 语言的面向对象的接班人, 希望成为事实上的标准. 最后结果大家都知道了, C++ 最终胜利了, 而 Objective-C 在之后的几十年中, 基本...…

iOS开发面试题继续阅读
更早的文章

[转]面试题: block中关于self的一些问题

题目我们知道, 在使用 block 的时候, 为了避免产生循环引用, 通常需要使用 weakSelf 与 strongSelf, 写下面这样的代码:__weak typeof(self) weakSelf = self;[self doSomeBlockJob:^{ __strong typeof(weakSelf) strongSelf = weakSelf; if (strongSelf) { ... }}];那么请问: 什么时候在 block 里面用 ...…

iOS开发面试题继续阅读