我的应用程序中有一个单例类,该类根据this blog post中的一行单例(带有私有init()
)进行声明。具体来说,它看起来像这样:
@objc class Singleton {
static let Singleton sharedInstance = Singleton()
@objc dynamic var aProperty = false
private init() {
}
}
我想将aProperty
的状态绑定到菜单项是否隐藏。
这是我执行此操作的步骤:
转到Interface Builder中的对象库,然后向我的Application场景添加一个通用的“ Object”。在身份检查器中,将“类”配置为Singleton
。
通过Ctrl-从Interface Builder中的singleton对象拖动到我的App Delegate代码中,在我的App Delegate中创建引用出口。最终看起来像这样:
@IBOutlet weak var singleton: Singleton!
不幸的是,这是行不通的:更改属性不会影响所涉及的菜单项。
问题似乎是,尽管将aProperty
声明为私有,但Interface Builder仍在设法创建我的单例的另一个实例。为了证明这一点,我将init()
添加到私有NSLog("singleton init")
方法中,并将以下代码添加到应用委托中的init()
中:
applicationDidFinishLaunching()
当我运行该应用程序时,这将在日志中输出:
NSLog("sharedInstance = \(Singleton.sharedInstance) singleton = \(singleton)")
因此,确实存在两个不同的实例。我还将此代码添加到了我的应用程序委托中的其他位置:
singleton init
singleton init
sharedInstance = <MyModule.Singleton: 0x600000c616b0> singleton = Optional(<MyModule.Singleton: 0x600000c07330>)
在某一点上,将产生以下输出:
aProperty:[false,Optional(0),true,Optional(1)]隐藏:false
很显然,作为一个单例,所有值都应该匹配,但是NSLog("aProperty: [\(singleton!.aProperty),\(String(describing:singleton!.value(forKey: "aProperty"))),\(Singleton.sharedInstance.singleton),\(String(describing:Singleton.sharedInstance.value(forKey: "aProperty")))] hidden: \(myMenuItem.isHidden)")
产生一个输出,而singleton
产生另一个输出。可以看出,对Singleton.sharedInstance
的调用与它们各自的对象匹配,因此KVC不应成为问题。
我如何在Swift中声明一个单例类,并将其与Interface Builder连接起来,以避免实例化两次?
如果这不可能,那么我将如何解决将全局属性绑定到Interface Builder中的控件的问题?
我希望描述足够详细,但是如果有人认为有必要使用MCVE,请发表评论,我将创建一个评论并上传到GitHub。
答案 0 :(得分:4)
我只想通过声明单身人士不应用于共享全局状态来开始我的回答。尽管一开始它们似乎较容易使用,但它们以后往往会引起很多麻烦,因为可以在任何地方进行虚拟更改,这有时会使您的程序变幻莫测。
话虽如此,要实现您的需求并非不可能,但要有一点仪式:
@objc class Singleton: NSObject {
// using this class behind the scenes, this is the actual singleton
class SingletonStorage: NSObject {
@objc dynamic var aProperty = false
}
private static var storage = SingletonStorage()
// making sure all instances use the same storage, regardless how
// they were created
@objc dynamic var storage = Singleton.storage
// we need to tell to KVO which changes in related properties affect
// the ones we're interested into
override class func keyPathsForValuesAffectingValue(forKey key: String) -> Set<String> {
switch key {
case "aProperty":
return ["storage.aProperty"]
default: return super.keyPathsForValuesAffectingValue(forKey: key)
}
}
// and simply convert it to a computed property
@objc dynamic var aProperty: Bool {
get { return Singleton.storage.aProperty }
set { Singleton.storage.aProperty = newValue }
}
}
答案 1 :(得分:1)
很遗憾,您无法在Swift中返回与init
不同的实例。
以下是一些可能的解决方法:
init
始终返回共享的Swift单例实例。答案 2 :(得分:1)
在我的特殊情况下,有一种解决问题的方法。
回想一下我仅想根据此单例中aProperty
的状态来隐藏和取消隐藏菜单的问题。当我尝试避免编写尽可能多的代码时,通过在Interface Builder中进行所有操作,似乎在这种情况下,以编程方式编写绑定的麻烦要小得多:
menuItem.bind(NSBindingName.hidden, to: Singleton.sharedInstance, withKeyPath: "aProperty", options: nil)