Singletons in Swift and Interface Builder

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:

  1. 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.

  2. 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!
  1. 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.

I just want to start my answer by stating that singletons should not be used for sharing global state. While they might seem easier to use in the beginning, they tend to generate lots of headaches later on, since they can be changed virtually from any place, making your program unpredictable some times.

That being said, it's not impossible to achieve what you need, but with a little bit of ceremony:

@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 }
    }
}

Reference from Swift: /// LPAnySwiftClass.swift import Foundation class LPSwiftClass { init { println("my singleton constant: \(mySingleton.myConstant)") } } The Question is: how can I access this LPSingleton class from within Interface Builder?. There is no "Swift class" in the Object library.

In this post, I will demonstrate one particular way of creating singletons in Swift. But, before starting, let me just say: Swift is a very powerful programming language that allows developers to construct the same functionality in multiple ways. Therefore, the following example is just one way of building a singleton in Swift.

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)

A software design pattern that restricts the instantiation of a class to one. Only classes can be singletons in Swift. Structures are value types, and copies are created every time you pass a value to a function or a type. This clearly defeats the purpose of singletons.

Singletons In Swift Explained Written by Reinder de Vries on January 10 2019 in App Development A singleton is a class of which exactly one instance exists, that can be globally accessed. How do you create a singleton in Swift?

A singleton designed to log into many different services would be a single, enormous class that would be difficult to maintain. You'd have to add many additional variables, api keys, URLs, and more. Alternatively, a new singleton could be created to make the other service's API calls.

Singletons are not meant to be subclassed since that would result in multiple instances of the base singleton. Using static enforces this in a beautiful, Swifty way. For Swift 1.0 and 1.1: With the recent changes in Swift, mostly new access control methods, I am now leaning towards the cleaner way of using a global variable for singletons.

Comments
  • Even if you say that your class is a singleton, by adding a new Object to storyboard you are not referencing that singleton. You are creating a new instance. Actually, I am pretty sure you cannot do that. You cannot reference an object created in code in your storyboards.
  • @Sulthan I thought the way the class was declared would preclude that, due to init() being private.
  • Interface Builder creates objects using Objective-C and you cannot really enforce private init there. Even if you could, you would just get an error when loading the storyboard.
  • That's too bad, I had no idea. Can you suggest a different design in this case? Maybe if I made aProperty static?
  • It's been a few years, but I think I hacked around IB duplicating singletons by using allocWithZone and copyWithZone
  • The property in question tracks whether I was able to connect to a helper tool, and I think it's a good use of the singleton pattern. It's only updated by code inside the class, it can't be set by other code. With that said, it seems that rather than trying to coerce IB into accepting a singleton, my other answer ends up being the cleanest solution in this particular case, in my opinion.
  • This would be perfect if you delete everything after the first line! ;-) In any case, your Singleton class is really misnamed, since it's not a singleton at all. You could call it SingletonProxy or FauxSingleton or something like that.
  • @Caleb I agree, I just used the same names as in the question. BTW, I could've add a shared property and make the init private, left them outside to not clutter the solution code.
  • @swineone can't argue about cleanliness, after all the solution you found has only one line ;)
  • To be honest, you can return a different instance from init in Swift with some ugly but permitted workaround but I would avoid it because it's not a good idea. And I would consider that unfortunate. It's always unfortunate to break type system, even a little.
  • Let me add that I used option 2 (create a helper class for use in Interface Builder) for a different class, with a lot of properties, where I didn't want to bind each manually as per my answer.