Background
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.
How I tried to solve the problem
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!
- Go to the Bindings inspector for the menu item, choose "Hidden" under "Availability", check "Bind to", select "Singleton" in the combo box in front of it, and type
aProperty
under "Model Key Path".
The issue
Unfortunately, this doesn't work: changing the property has no effect on the menu item in question.
Investigating the cause
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.
The question
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?
Is an MCVE necessary?
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.