Swift "built-in" singleton

Is it possible to have this built-in singleton behaviour for my class, similar to:

assert(NSNull() === NSNull()) // passes

Is it possible to do this without Obj-C?

FWIW, this is what Xcode shows for autogenerated NSNull class:

open class NSNull : NSObject, NSCopying, NSSecureCoding {
}

It needs this feature:

I hope @Joe_Groff revisits it.

It has been proposed many times before in different forms. Here is the oldest I could find from 2015:

I think it is possible. Here's the code:

protocol Singleton {
  static var shared: Self { get }
}

extension Singleton {
  init() {
    // Protocols can reassign self
    self = Self.shared
  }
}

final class Null: Singleton {
  static let shared: Null = .init(placeholder: ())

  /// - Parameter placeholder: "Dummy" argument to distinguish
  ///   this initializer from the one inherited from Singleton.
  private init(placeholder: Void) {}
}

assert(Null() === Null()) // passes

This takes advantage of the "quirk" that protocol methods can reassign self even if the conforming type is a class.

13 Likes

Thank you. Do you think this quirk is a bug or a feature?

It's intentional behavior as far as I know, so no bug. That being said, if Swift were redesigned from scratch, maybe we'd choose a different behavior with different tradeoffs. I don't think there's an obviously better solution to fix this.

@jrose in Swift Regret: mutating Protocol Methods vs. Classes:

At the time, @dabrahams had the idea that classes should not be allowed to conform to protocols with mutating requirements. At the very least, he brought up the problem and hoped we could think more on it. And this is Dave, who tolerates the existence of classes at best. ;-) But that doesn’t solve the extension method part, and, well, really this is about reference semantics, not classes specifically. (Consider using isSorted with UnsafeMutableBufferPointer.)

1 Like

Yeah, it certainly won’t be “fixed”; in the worst case the rules would become stricter somehow in a new major version. (I have used this trick myself, though I’d prefer proper factory init support.) See also SR-142.

That said, I usually wouldn’t bother to force singleton-hood like this. I’d just make a lazy static property called shared.

4 Likes