3

I have the following protocol that defines a singleton with property:

protocol SingletonProtocol {
    static var shared: SingletonProtocol { get }
    var variable : Int { get set }
}

And the following class that implements this protocol:

class Singleton : SingletonProtocol{
    static let shared : SingletonProtocol = Singleton()
    var variable = 4
}

If I call:

Singleton.shared.variable = 5

I get the following error:

 change 'let' to 'var' to make it mutable

If I implement this class without the protocol I don't get the error and the variable can be changed. I can solve this by adding setVariable: method but I want to access and modify the variable directly.

How can I write a protocol that defines a singleton with variables that can be modified?

amir
  • 1,332
  • 8
  • 15
  • 2
    You can't really ensure that a conforming type only has a singleton instance, since for that you'd need to declare a private initializer in your class, which you can do, but you can't declare it in `SingletonProtocol`, so a class can conform to your protocol without having only a singleton instance. With your current implementation, it is possible to instantiate any number of `Singleton` instances. – Dávid Pásztor Jun 13 '18 at 09:11
  • Thank you @DávidPásztor for this comment, is there a way for a protocol to "enforce" private init in its comforting protocols. – amir Jun 13 '18 at 09:16
  • Sadly no, you can't mark protocol methods as private. – Dávid Pásztor Jun 13 '18 at 09:18
  • You can set it like `var shared = Singleton.shared shared.variable = 5` – Mukesh Jun 13 '18 at 10:12
  • I'm curious, why do you need a protocol here? What other types would conform to `SingletonProtocol`? – Hamish Jun 13 '18 at 10:40
  • I want to unitest a singleton class using dependency injection and for that I need a variable in the protocol with the type of an other protocol to set as a mock in the testing. – amir Jun 13 '18 at 11:27

3 Answers3

7

Make the protocol available only for classes (struct won't be able to conform to this protocol):

protocol SingletonProtocol: AnyObject {
    static var shared: SingletonProtocol { get }
    var variable: Int { get set }
}

Now you can set the shared property as a let

class Singleton: SingletonProtocol {
    static let shared: SingletonProtocol = Singleton()
    var variable: Int = 0
}
Rico Crescenzio
  • 3,952
  • 1
  • 14
  • 28
  • In your class, don't forget to make your init private – Rico Crescenzio Jun 13 '18 at 09:21
  • 1) with private set I get the error "cannot assign to property: 'shared' setter is inaccessible" 2) how does making it only for classes help me? – amir Jun 13 '18 at 09:26
  • Sorry, my mistake; that problem is about struct, which are value-types; I changed answer. If a protocol is only for classes, it means that only reference-types can conform to it; a reference type can be immutable even if its members aren't. So now you can use let instead of var – Rico Crescenzio Jun 13 '18 at 09:30
  • If I do, the class will not be a singleton anymore, shared must be immutable. – amir Jun 13 '18 at 09:41
  • If you do this, in the class you can declare shared as let, so it’s immutable – Rico Crescenzio Jun 13 '18 at 09:43
  • And back to the original problem, if I try to do Singleton.shared.variable = 2 I get the error '"change 'let' to 'var' to make it mutable" – amir Jun 13 '18 at 10:03
  • 1
    I'm testing in Playground and it works good for me, swift 4.1 – Rico Crescenzio Jun 13 '18 at 10:08
0

Change

static let shared : SingletonProtocol = Singleton()

to

static var shared : SingletonProtocol = Singleton()
private init() {} 
Tom E
  • 1,530
  • 9
  • 17
-2

In your singleton class change the variable declaration from let to var. so that will be same as in protocol's declaration.

class Singleton : SingletonProtocol{
    static var shared : SingletonProtocol = Singleton()
    var variable = 4
}

EDIT:

also add only getter for sharedInstance in protocol declaration to serve purpose of singleton class as:

protocol SingletonProtocol: AnyObject {
    static var shared: SingletonProtocol { get }
    var variable: Int { get set }
}
Van
  • 1,225
  • 10
  • 18
  • If I do, the class will not be a singleton anymore, shared must be immutable. – amir Jun 13 '18 at 09:14
  • @amir check the tom's comment above, same can be applicable here. – Van Jun 14 '18 at 06:17
  • yes but I still want shared to be let because it is the correct way of writing singleton, so the correct solution is Rico Crescenzio's – amir Jun 14 '18 at 10:13