南京iOS培训专家指出,单例不是一种反模式,只是被滥用的模式。初学者很喜欢,因为它很方便,但它也可能增加复杂性并引起致命bug。
打个比方,单例代表了一个无所不在的对象,一个永远不会随你改变的对象,像目前的应用,设备的加速度计,或你的上帝。
NSNotificationCenter 只在一种情况下起作用,即整个组件层只有一个单一的广播,因此它的名字中含有“中心”。除此之外,不要使用单例。
一、不当的模型
用 User.currentUser 或 Account.sharedAccount 表示当前登录用户,确实很方便,但是这样并不正确。
帐户不是单例,用户会注销帐户。许多应用程序通过一种特殊类型的帐户表示一个登出的状态。服务逐渐支持多账户同时登录。帐户是可变的,因此单例就成了谎言。
二、单例共享状态
“单例”可以看作是“全局变量”。有时,全局变量没有必要,最好是尽力避免它们。保存状态很难,共享状态就更难了。
自定义容器的视图控制器使 viewWillAppear 没那么难以预料。即使是普通的导航也存在边缘情况:如果你尝试滑动返回,就会改变想法,并取消它,因为你会得到一个错误的 viewWillappear,从而在错误的视图控制器中记录事件。
不过,现实中,你的分析团队会发现存在重复点击,因而他们要求你只记录一次,即每个视图控制器实例只调用 Tappedreply 一次。
你可能会想把你所有的分析请求都保留在一个队列中,这样你就可以将它们进行节流。也许一个单例是比较好的选择。至少缩小了范围。
三、跨界单例
class Account:NSObject {
static sharedAccount = Account()
var homeTimeline:Timeline()
var preferences:AccountPreference init(){
preferences = AccountPreference.loadFromDisk()
homeTimeline = Timeline()
super.init()
}
}
class Timeline:NSObject {
init(){
self.orderPreference = Account.sharedAccount.preferences.timelineOrder
super.init()
}
}
这段代码存在严重的bug,连初始化都完成不了,因为 Timeline 对象在初始化的过程中访问 Account 单例,而 Account 单例在初始化中也调用了 Timeline 初始化方法。这样的错误代码在对象图中必然是潜在的隐患。
想对 Timeline 进行单元测试该,我们就必须模拟 Account 对象,并返回一个模拟 preference 对象,真是糟透了。
那就重构一下吧
init(){
preferences = AccountPreference.loadFromDisk()
homeTimeline = Timeline(order:preferences.timelineOrder)
super.init()
}
这样所有权就很清晰了。Account 配置它的子类,Timeline。它恰好从 preference 中获取配置,但是我们可以在测试中指定任何值。没有必要将视图控制器与 Account 单例分离。