11

According to this blog post and the currently highest voted answer to this Stack Overflow question, which in turn cites Apple's documentation, the best way to create a singleton in modern Swift is:

class Singleton  {
   static let sharedInstance = Singleton()
}

Although it wasn't mentioned, probably a private init() is called for as well.

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

For example, suppose I wrote a class with a property and a method, following the advice above, as follows:

class Singleton {
  static let sharedInstance = Singleton("whatever")

  var myProperty: String

  func myMethod() {
    // ...
  }

  private init(_ myProperty) {
    self.myProperty = myProperty
  }
}

If the user needs to access the property in question, they would write Singleton.sharedInstance.myProperty, and if they need to call the method, they would write Singleton.sharedInstance.myMethod().

I propose rewriting the class as follows:

class Singleton {
  static var myProperty: String = "whatever"

  static func myMethod() {
    // ...
  }
}

So: less boilerplate code, and less characters to type when accessing the property (just Singleton.myProperty) and method (Singleton.myMethod()).

One disadvantage is that accesses to the property and method from inside the class would need to be fully spelled out (Singleton.myProperty and Singleton.myMethod()), compared to just myProperty and myMethod() for the previous solution.

Thus, it's a little bit easier for the user (dropping the sharedInstance part) and a little bit harder for the class writer (which needs to add Singleton. in front of all accesses). It seems reasonable that, when faced with a design choice that either favors the user or the class writer, the better option is to favor the user.

Nobody else appears to advocate the method I proposed for making a singleton, so I get the feeling there must be something wrong with it. Would someone be so kind as to point out what is it?

swineone
  • 2,296
  • 1
  • 18
  • 32
  • "One disadvantage is that accesses to the property and method from inside the class would need to be fully spelled out" -> Static methods and properties can use other static methods and properties without needing to call the type name. Also, remember that you can use `Self` inside your type instead of your type's name. – Clément Cardonnel Aug 07 '22 at 08:10

1 Answers1

15

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.

Rob Napier
  • 286,113
  • 34
  • 456
  • 610
  • Point taken about singleton vs. well-known instance, and I will ponder which one is better suited in my case. Assuming I did want to use a singleton, how does a class with a private `init()` compare to one where all properties and methods are static? – swineone Jan 25 '19 at 18:11
  • 3
    A private `init` allows for future expansion (for example, to later expose an `init`, or to create subclasses), and is consistent with the interfaces for all other objects. The caller shouldn't have to guess whether something is a static method or an instance method. Unless it is fundamental to the *type* (i.e. a factory or a default value), methods should be on the instance. – Rob Napier Jan 25 '19 at 18:57
  • 1
    +1 FWIW, I understand the distinction that you’re drawing between the strict definition of singleton and Apple’s weaker definition as outlined in [their discussion of the pattern](https://developer.apple.com/documentation/swift/cocoa_design_patterns/managing_a_shared_resource_using_a_singleton). But they repeatedly consider this pattern as a singleton (e.g., [`URLSession.shared`](https://developer.apple.com/documentation/foundation/urlsession/1409000-shared) is called a “shared singleton” even though we instantiate our own `URLSession` instances all the time). – Rob Jan 25 '19 at 20:18
  • 2
    Yeah, sloppiness about the names has led to all kinds of bad patterns over the years. So many people copied Apple's overcomplicated example of a "strict implementation" because they didn't read the first paragraph carefully: https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/CocoaFundamentals/CocoaObjects/CocoaObjects.html#//apple_ref/doc/uid/TP40002974-CH4-SW32 – Rob Napier Jan 25 '19 at 20:39