Understanding Singleton in Swift

Understanding Singleton in Swift

This pattern does not guarantee there will only ever be one instance of the Global class. It just allows for anyone to access a single common instance of Global via its sharedinstance property.

So Global() declares a new instance of the Global class. But Global.sharedinstance does not create a new instance of Global, just fetches a pre-created one (that is created the first time anyone accesses it).

(If you alter your declaration of b to read var b = Global.sharedinstance you’ll see it confirms that a and b are pointing to the same instance.)

If you want to ban the creation of further instances of Global, make its init private:

    private init() {  }

But bear in mind you’ll still be able to create other Globals from within the file in which it’s declared, so if you’re doing the above in a playground or single-file test project, you won’t see any effect.

Using the Swift Singleton

There is a lot of info available on singletons in Swift. Have you come across this article with your Google prowess? http://krakendev.io/blog/the-right-way-to-write-a-singleton

But to answer your question, you can simply define anything you'd like to use normally.

class Singleton {
static let sharedInstance = Singleton() // this makes singletons easy in Swift
var stringArray = [String]()

}

let sharedSingleton = Singleton.sharedInstance

sharedSingleton.stringArray.append("blaaaah") // ["blaaaah"]

let anotherReferenceToSharedSingleton = Singleton.sharedInstance

print(anotherReferenceToSharedSingleton.stringArray) // "["blaaaah"]\n"

Swift singleton vs. static properties/methods

To me, a simpler alternative would be to convert all properties and methods to static, and drop the sharedInstance property.

These do not do the same thing. The recommended approach is not actually a singleton at all. It's just a well-known instance. The concept of the Singleton pattern is that there must only be one instance. The concert of the shared instance pattern is that there can be more than one instance, but there is one that you probably want, and you would like easy access to it.

The advantage of shared instances is that they are not magical. They're just instances. That means that they can be handed around as values. They can be replaced with other instances that may be configured differently. They are easier to test (because they can be passed into functions).

True singletons are a very rigid pattern that should only be used when it is absolutely necessary that no other instance exist, usually because they interact with some external unique resource in a way that would create conflicts if there were multiples (this is pretty rare). Even in this case, in Swift, you should generally just make init private to prevent additional instances being created.

If you look around Cocoa, you'll find that shared instances are extremely common for things that would be Singletons in other frameworks, and this has been very powerful. For instance, there is a well known NotificationCenter called default, and it's probably the only one you've ever used. But it's completely valid to create a private NotificationCenter that's independent (I've actually done this in production code).

The fact that UIDevice.current is how you access the device, rather than static methods, leaves open the possibility of new APIs that can handle multiple devices (it also helps with unit testing). In the earliest versions of iOS, the only UIScreen was .main, and it might have made sense to make it a singleton. But because Apple didn't, when mirroring was added in 4.3, it was simple to talk about the second screen (UIScreen.mirrored). You should generally be very slow to assume that there can only be one of something.

Patterns: Singletons vs. Static vars and methods approach

A class-based singleton is the way to go, provided you accommodate for dependency injection for your tests. The way to do this is to create a single singleton for your app, called, say, DependencyManager. In your AppDelegate (or from other classes if needed), you'd create whatever controllers, network services, realm models, etc you want to hang on your DependencyManager, and then assign them to the DependencyManager. This code would be skipped by your unit tests.

Your unit tests can then access the DependencyManager (and thus instantiate the DependencyManager during first access), and populate it with mock versions of those controllers and services to whatever degree each unit test desires.

Your UIViewControllers, your MVVM view models, etc... can access the DependencyManager as a singleton, and thus get either the real controllers and services, or a mock version of them, depending on if you're running the app or unit tests.

If you're doing MVVM, I also recommend that when a UIViewController is about to create its view model class, that it first checks a special property in the DependencyManager to see if a mockViewModel exists. A single property can serve this purpose, as only one of your UIViewControllers ever would be tested at once. It'd use that property instead of creating a new view model for itself. In this way, you can mock your view models when testing each UIViewController. (There's other tricks involved to being able to prop up a single UIViewController for testing, but I won't cover that here).

Note that all of the above can work very nicely with an app that also wants to use storyboards and/or nibs. People are so down on storyboards because they can't figure out how to do dependency injection of mock services for their view controllers. Well, the above is the solution! Just make sure in your AppDelegate to load the storyboard AFTER setting up the DependencyManager. (Remove the storyboard name from your info.plist, and instantiate it yourself in AppDelegate).

I've written a few shipped apps this way, as well as some sample apps for an SDK, along with the tests. I highly recommend the approach! And be sure to write your unit tests and viewController tests either during or at least immediately after development of each such class, or you'll never get around to them!

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

Why we should use struct and class function in singleton pattern?

Actually it is recommended to use your second code because of the improvements in the versions of swift.One more thing that you should consider is to declare your singleton object using static let and also make the initializer private

class Bar{

private init(){
// initialization
}

static let shared = Bar()

}

Where Singleton object is allocated?

  • Swift allocates storage for MyClass.shared in the data segment, initialized to nil. The data segment's layout and initial contents are defined by the executable file. Historically, the heap started immediately at the end of the data segment, but on modern 64-bit systems with address space layout randomization (ASLR), I don't know if that's still true.

  • Swift also allocates a swift_once_t in the data segment to record whether MyClass.shared has been initialized yet.

  • Swift generates a getter function for MyClass.shared in the code segment. The getter function uses the swift_once_t to initialize the storage of MyClass.shared the first time the getter is called. It looks approximately like this:

    var _storage_MyClass_shared: MyClass? = nil
    var _once_MyClass_shared: swift_once_t = .init() // essentially, false

    func _getter_MyClass_shared() -> MyClass {
    swift_once(&_once_MyClass_shared, {
    _storage_MyClass_shared = MyClass()
    })
    return _storage_MyClass_shared!
    }
  • The instance of MyClass is stored on the heap. It starts with a word containing the isa pointer (to the MyClass metadata), followed by a word containing (usually) reference counts, followed by storage for the object's instance variables. In your case, there are no instance variables, so there is no additional storage. The blue box labeled Myclass() in your diagram, and the arrow pointing to it, do not exist.

  • If myClass is at top-level (not inside a method or data type declaration), then it is also stored in the data segment along with another swift_once_t that tracks whether it's been initialized, and Swift generates a getter for it in the code segment.

  • If myClass is an instance variable of a data type, then it is stored as part of its containing object, which may be either on the stack or the heap (in the case of a struct, enum, or tuple) or always on the heap (in the case of a class or actor).

  • If myClass is a local variable in a function, then it is stored on the stack.



Related Topics



Leave a reply



Submit