swift - Swift - 没有全局访问的单例
问题描述
我想创建一个没有全局访问权限的 Swift Singleton。我要创建的模式是确保始终只存在一个类的一个实例,但不应通过通常的全局MyClass.shared
语法访问该类。这样做的原因是我希望该类是完全可正确测试的(这对于全局单例来说实际上是不可能的)。然后,我将使用依赖注入将单个实例从 viewcontroller 传递到 viewcontroller。因此,“访问”问题在没有全局静态实例的情况下得到了解决。
我能做的就是基本上什么都不做。只需创建一个普通类并相信所有开发人员的纪律,不要一次又一次地实例化该类,而仅将其用作依赖项。但我宁愿有一些禁止这种情况的编译器强制模式。
所以要求是:
- 确保在编译时只实例化一个类的一个实例
- 没有全局访问权限
- 在单元测试期间不应强制执行仅实例化一个类的保证,因此可以正确测试它
我第一次尝试解决这个问题是这样的:
class WebService {
private static var instances = 0
init() {
assertSingletonInstance()
}
private func assertSingletonInstance() {
#if DEBUG
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.isUnitTestRunning.rawValue) == false {
WebService.instances += 1
assert(WebService.instances == 1, "Do not create multiple instances of this class. Get it thru the shared dependencies in your module.")
}
#endif
}
}
备注:在启动期间传递参数会创建一个用户默认值,可以在运行时检查。这就是我知道当前运行是单元测试的方式。
一般来说,这种模式效果很好。我唯一的问题 - 我必须为每一个可能的单例一遍又一遍地复制这段代码。这不好。我更喜欢可重复使用的解决方案。
单例协议扩展
一种解决方案是创建一个协议扩展:
protocol Singleton {
static var instances: Int { get set }
func assertSingletonInstance()
}
extension Singleton {
// Call this assertion in init() to check for multiple instances of one type.
func assertSingletonInstance() {
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.isUnitTestRunning.rawValue) == false {
Self.instances += 1
assert(Self.instances == 1, "Do not create multiple instances of this class. Get it thru the shared dependencies in your module.")
}
#endif
}
}
然后以这种方式使用它:
class WebService: Singleton {)
static var instances = 0
init() {
assertSingletonInstance()
}
}
这种方法的问题是instances
变量不是private
. 所以有人可以在实例化类之前将此变量设置为 0 并且检查将不再起作用。
单例基类
另一种尝试是Singleton
基类。在这种情况下,private static var instances
可以使用 a。
class Singleton {
private static var instances = 0
required init() {
assertSingletonInstance()
}
private func assertSingletonInstance() {
#if DEBUG
if UserDefaults.standard.bool(forKey: UserDefaultsKeys.isUnitTestRunning.rawValue) == false {
Singleton.instances += 1
assert(Singleton.instances == 1, "Do not create multiple instances of this class. Get it thru the shared dependencies in your module.")
}
#endif
}
}
这种方法的问题是 - 它不起作用。递增Singleton.instance
将 1 添加到static instances
类型Singleton
而不是派生自Singleton
基类的类。
现在我要么什么都不做,要么依靠所有开发人员的纪律和理解,要么至少使用协议扩展internal
或public
访问。
可以在此处找到示例实现。
也许有人对这个问题的真正干净的解决方案有更好的想法。我很感激任何关于它的提示或讨论。谢谢。
解决方案
您可以使用原子标志(用于线程安全)将单例标记为被实例化:
class Singleton {
static private var hasInstance = atomic_flag()
init() {
// use precondition() instead of assert() if you want the crashes to happen in Release builds too
assert(!atomic_flag_test_and_set(&type(of: self).hasInstance), "Singleton here, don't instantiate me more than once!!!")
}
deinit {
atomic_flag_clear(&type(of: self).hasInstance)
}
}
您将单例标记为在 中分配init
,并在中重置标志deinit
。这允许您一方面只有一个实例(如果原始实例没有被释放),另一方面可以有多个实例,只要它们不重叠。
应用程序代码:假设您将在某处保留对下游注入的单例的引用,则deinit
永远不应调用它,这只会导致一种可能的分配。
单元测试代码:如果单元测试正确地进行了清理(被测试的单例在每次测试后被释放),那么在某个时间点将只有一个活实例,这不会触发断言失败。
推荐阅读
- php - 尝试对包含多个字符串的列进行排序(我的雇主有一些限制)
- java - FTPS协议版本
- linux - 如何将特定列转换为标题大小写
- swift - 如何使用滑动手势在 swift UI 中创建退格功能(与原始 IOS 计算器相同)
- python - 动态显示动画图上的最后一个数据值
- python - 如何在一列熊猫数据框中编码json?
- java - 如何使用java在android studio中创建这种类型的对话框?
- python - 如何在 Microsoft Power Bi 中使用 Python Visuals 查找平均值?
- django - Django REST 框架最佳实践
- javascript - 节点模块@kenjiuno/msgreader 错误的原因:MsgReader 不是构造函数