Having Hard Time Implement a Simple Singleton in Swift

Having hard time implement a simple singleton in swift

You can't just have var = xxx in an init. The variable has to be declared at the class top level.

Example of using your singleton:

class Globals {

static let sharedInstance = Globals()

var max: Int

private init() {
self.max = 100
}

}

let singleton = Globals.sharedInstance

print(singleton.max) // 100

singleton.max = 42

print(singleton.max) // 42

When you need to use the singleton in another class, you just do this in the other class:

let otherReferenceToTheSameSingleton = Globals.sharedInstance

Update following Martin R and Caleb's comments: I've made the initializer private. It prevents, in other Swift files, the initialization of Globals(), enforcing this class to behave as a singleton by only being able to use Globals.sharedInstance.

Singleton in Swift

The standard singleton pattern is:

final class Manager {
static let shared = Manager()

private init() { ... }

func foo() { ... }
}

And you'd use it like so:

Manager.shared.foo()

Credit to appzYourLife for pointing out that one should declare it final to make sure it's not accidentally subclassed as well as the use of the private access modifier for the initializer, to ensure you don't accidentally instantiate another instance. See https://stackoverflow.com/a/38793747/1271826.

So, returning to your image cache question, you would use this singleton pattern:

final class ImageCache {

static let shared = ImageCache()

/// Private image cache.

private var cache = [String: UIImage]()

// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.

private init() { }

/// Subscript operator to retrieve and update cache

subscript(key: String) -> UIImage? {
get {
return cache[key]
}

set (newValue) {
cache[key] = newValue
}
}
}

Then you can:

ImageCache.shared["photo1"] = image
let image2 = ImageCache.shared["photo2"])

Or

let cache = ImageCache.shared
cache["photo1"] = image
let image2 = cache["photo2"]

Having shown a simplistic singleton cache implementation above, we should note that you probably want to (a) make it thread safe by using NSCache; and (b) respond to memory pressure. So, the actual implementation is something like the following in Swift 3:

final class ImageCache: NSCache<AnyObject, UIImage> {

static let shared = ImageCache()

/// Observer for `UIApplicationDidReceiveMemoryWarningNotification`.

private var memoryWarningObserver: NSObjectProtocol!

/// Note, this is `private` to avoid subclassing this; singletons shouldn't be subclassed.
///
/// Add observer to purge cache upon memory pressure.

private override init() {
super.init()

memoryWarningObserver = NotificationCenter.default.addObserver(forName: .UIApplicationDidReceiveMemoryWarning, object: nil, queue: nil) { [weak self] notification in
self?.removeAllObjects()
}
}

/// The singleton will never be deallocated, but as a matter of defensive programming (in case this is
/// later refactored to not be a singleton), let's remove the observer if deallocated.

deinit {
NotificationCenter.default.removeObserver(memoryWarningObserver)
}

/// Subscript operation to retrieve and update

subscript(key: String) -> UIImage? {
get {
return object(forKey: key as AnyObject)
}

set (newValue) {
if let object = newValue {
setObject(object, forKey: key as AnyObject)
} else {
removeObject(forKey: key as AnyObject)
}
}
}

}

And you'd use it as follows:

ImageCache.shared["foo"] = image

And

let image = ImageCache.shared["foo"]

For Swift 2.3 example, see previous revision of this answer.

Any reason not use use a singleton variable in Swift?

Functionally, these are very similar, but I'd advise using the Model.shared syntax because that makes it absolutely clear, wherever you use it, that you're dealing with a singleton, whereas if you just have that model global floating out there, it's not clear what you're dealing with.

Also, with globals (esp with simple name like "model"), you risk of having some future class that has similarly named variables and accidentally reference the wrong one.

For a discussion about the general considerations regarding globals v singletons v other patterns, see Global Variables Are Bad which, despite the fairly leading title, presents a sober discussion, has some interesting links and presents alternatives.


By the way, for your "OCD friends" (within which I guess I must count myself, because I think it's best practice), not only would declare init to be private, but you'd probably declare the whole class to be final, to avoid subclassing (at which point it becomes ambiguous to what shared references).

How to create a proper singleton for Dictionary in Swift 2

You can use a static property:

static var imagesOfChef = [Int : chefImages]()

and then you use:

Test.imagesOfChef 

But I suggest avoiding the static approaches as much as possible, you can use the prepare segue or assign the property from outside if possible if Test has Test2.

Create singleton of a viewcontroller in swift 3

If you really wanted to have singleton for a view controller corresponding to some scene, you'd probably do something like:

class SecondViewController: UIViewController {

static let shared = UIStoryboard(name: "Main", bundle: nil).instantiateViewController(withIdentifier: "Foo")

}

In this example, the storyboard was Main.storyboard and the storyboard identifier for the scene in question was Foo. Obviously, replace those values for whatever was appropriate in your case.

Then your other view controller that was invoking this could do something like:

@IBAction func didTapButton(_ sender: Any) {
let controller = SecondViewController.shared
show(controller, sender: self)
}

I wouldn't recommend singletons for view controllers. View controllers (and their views) should be created when needed and be allowed to be deallocated when they're dismissed. And you're losing many storyboard benefits (by which you see the logical flow between scenes with segues between them). And, if you use this view controller in different contexts, you're inviting problems stemming from the view controller hierarchy falling out of sync with the view hierarchy. I really would discourage you from using singletons for view controllers.

But if you were going to do it, you could do something like that...

iOS8 + Swift: Create a true singleton class

Just declare your initializer as private:

private init() {}

Now new instances can only be created from within the same file.



Related Topics



Leave a reply



Submit