I have a singleton class in my app, declared according following the one line singleton (with a private init()
) in this blog post. Specifically, it looks like this:
@objc class Singleton {
static let Singleton sharedInstance = Singleton()
@objc dynamic var aProperty = false
private init() {
}
}
I would like to bind the state of aProperty
to whether a menu item is hidden.
Here are the steps I followed to do this:
Go to the Object Library in Interface Builder and add a generic "Object" to my Application scene. In the Identity inspector, configure "Class" to Singleton
.
Create a referencing outlet in my App Delegate by Ctrl-dragging from the singleton object in Interface Builder to my App Delegate code. It ends up looking like this:
@IBOutlet weak var singleton: Singleton!
aProperty
under "Model Key Path".Unfortunately, this doesn't work: changing the property has no effect on the menu item in question.
The issue appears to be that, despite declaring init()
as private, Interface Builder is managing to create another instance of my singleton. To prove this, I added NSLog("singleton init")
to the private init()
method as well as the following code to applicationDidFinishLaunching()
in my app delegate:
NSLog("sharedInstance = \(Singleton.sharedInstance) singleton = \(singleton)")
When I run the app, this is output in the logs:
singleton init
singleton init
sharedInstance = <MyModule.Singleton: 0x600000c616b0> singleton = Optional(<MyModule.Singleton: 0x600000c07330>)
Therefore, there are indeed two different instances. I also added this code somewhere else in my 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)")
At one point, this produces the following output:
aProperty: [false,Optional(0),true,Optional(1)] hidden: false
Obviously, being a singleton, all values should match, yet singleton
produces one output and Singleton.sharedInstance
produces a different one. As can be seen, the calls to value(forKey:)
match their respective objects, so KVC shouldn't be an issue.
How do I declare a singleton class in Swift and wire it up with Interface Builder to avoid it being instantiated twice?
If that's not possible, how else would I go about solving the problem of binding a global property to a control in Interface Builder?
I hope the description was detailed enough, but if anyone feels an MCVE is necessary, leave a comment and I'll create one and upload to GitHub.
There is a way around the problem in my particular case.
Recall from the question that I only wanted to hide and unhide a menu according to the state of aProperty
in this singleton. While I was attempting to avoid write as much code as possible, by doing everything in Interface Builder, it seems in this case it's much less hassle to just write the binding programmatically:
menuItem.bind(NSBindingName.hidden, to: Singleton.sharedInstance, withKeyPath: "aProperty", options: nil)