我的应用程序中有一个单例类,在this blog post中按照单行单例(带有私有init())声明.具体来说,它看起来像这样:
@objc class Singleton { static let Singleton sharedInstance = Singleton() @objc dynamic var aProperty = false private init() { } }
我想将aProperty的状态绑定到菜单项是否被隐藏.
我是如何尝试解决问题的
以下是我执行此操作的步骤:
>转到Interface Builder中的对象库,并将一个通用的“对象”添加到我的应用程序场景中.在Identity检查器中,将“Class”配置为Singleton.
>通过Ctrl键从Interface Builder中的单例对象拖动到我的App Delegate代码,在App Delegate中创建一个引用插座.最终看起来像这样:
@IBOutlet weak var singleton: Singleton!
>转到菜单项的Bindings检查器,在“Availability”下选择“Hidden”,选中“Bind to”,在其前面的组合框中选择“Singleton”,然后在“Model Key Path”下键入aProperty.
问题
不幸的是,这不起作用:更改属性对相关菜单项没有影响.
调查原因
问题似乎是,尽管将init()声明为私有,但Interface Builder正在设法创建我的单例的另一个实例.为了证明这一点,我在我的app委托中将NSLog(“singleton init”)添加到私有init()方法以及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)")
有一次,这会产生以下输出:
aProperty:[false,Optional(0),true,Optional(1)] hidden:false
显然,作为单例,所有值都应该匹配,但是singleton产生一个输出,而Singleton.sharedInstance产生不同的输出.可以看出,对value(forKey :)的调用与它们各自的对象匹配,因此KVC应该不是问题.
这个问题
如何在Swift中声明单例类并使用Interface Builder将其连接起来以避免它被实例化两次?
如果那是不可能的,那么我将如何解决在Interface Builder中将全局属性绑定到控件的问题呢?
是否需要MCVE?
我希望描述足够详细,但如果有人觉得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 } } }