我的应用程序中有一个单例类,根据init()
中的单行单例(带有私有this blog post)声明。具体来说,它看起来像这样:
@objc class Singleton {
static let Singleton sharedInstance = Singleton()
@objc dynamic var aProperty = false
private init() {
}
}
我想将aProperty
的状态绑定到菜单项是否隐藏。
以下是我执行此操作的步骤:
Singleton
。@IBOutlet weak var singleton: Singleton!
aProperty
。不幸的是,这不起作用:更改属性对相关菜单项没有影响。
问题似乎在于,尽管将init()
声明为私有,但Interface Builder正在设法创建我的单例的另一个实例。为了证明这一点,我将NSLog("singleton init")
添加到私有init()
方法以及以下代码到我的app委托中的applicationDidFinishLaunching()
:
NSLog("sharedInstance = \(Singleton.sharedInstance) singleton = \(singleton)")
当我运行应用程序时,会在日志中输出:
singleton init
singleton init
sharedInstance = <MyModule.Singleton: 0x600000c616b0> singleton = Optional(<MyModule.Singleton: 0x600000c07330>)
因此,确实存在两种不同的情况。我还在我的app delegate中的其他地方添加了这段代码:
NSLog("aProperty: [\(singleton!.aProperty),\(String(describing:singleton!.value(forKey: "aProperty"))),\(Singleton.sharedInstance.singleton),\(String(describing:Singleton.sharedInstance.value(forKey: "aProperty")))] hidden: \(myMenuItem.isHidden)")
有一次,这会产生以下输出:
属性:[false,Optional(0),true,Optional(1)] hidden:false
显然,作为一个单身人士,所有的数值都应该匹配,但singleton
产生一个输出,而Singleton.sharedInstance
产生不同的输出。可以看出,对value(forKey:)
的调用与其各自的对象相匹配,因此KVC不应成为问题。
如何在Swift中声明单例类并使用Interface Builder将其连接起来以避免它被实例化两次?
如果那是不可能的,那么我将如何解决在Interface Builder中将全局属性绑定到控件的问题呢?
我希望描述足够详细,但如果有人觉得MCVE是必要的,请留下评论,我将创建一个并上传到GitHub。
我只是想通过声明不应该使用单身人士来分享全球状态来开始我的答案。虽然它们在开始时看起来似乎更容易使用,但它们往往会在以后产生许多令人头疼的问题,因为它们几乎可以从任何地方进行更改,使得程序有时无法预测。
话虽这么说,实现你所需要的并不是不可能的,但有一点点的仪式:
@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 }
}
}
不幸的是,你无法从Swift中的init
返回不同的实例。以下是一些可能的解决方法:
init
始终返回共享的Swift单例实例。在我的特定情况下,有一种解决问题的方法。
回想一下这个问题,我只想根据这个单身人士中的aProperty
状态隐藏和取消隐藏菜单。虽然我试图避免编写尽可能多的代码,但是通过在Interface Builder中执行所有操作,在这种情况下,以编程方式编写绑定更加麻烦:
menuItem.bind(NSBindingName.hidden, to: Singleton.sharedInstance, withKeyPath: "aProperty", options: nil)