Stéphane de Luca
Stéphane de Luca

Reputation: 13583

Shared instance accross inheritage

Say a framework provides customizable services through an open class A, that exposes a shared instance to use as follows:

open class A {
    public static let shared = A()
    open func aService() {} 
}

The regular usage of the service is as follows:

A.shared.aService()

Important note: the shared instance itself is also used from within the class A code or related code in the Framework.

Say you want to customize the service through inheritance, and you still want to keep the use of the shared instanced as follows:

override class B: A {
    public override func aService() {
        super.aService()
    } 
}

When you refer to the shared instance, unfortunately, it refers to the class A instance where you would like it refers to the inherited class instance.

B.shared.aService() // Failed!: This actually calls A.shared.aService()

One way to fix it is to make the construct as follows:

class A {
    public static var shared = A()
}

Then you are sure before any use of the service within you app, to change the instance as follows:

A.shared = B()
B.shared.aService() // OK! This actually calls B.aService()

Though the whole thing works, I would like to make it automatic and not rely on the initial line that changes the shared instance.

How would you do that?

[CODE FOR PLAYGROUND] that illustrates the goal to achieve and to help you better understand the question

open class A {
    public static var shared = A()
    open func aService() {
        print("\(type(of: self)): service (from A)")
    }

}

class B: A {
    public override func aService() {
        super.aService()
        print("\(type(of: self)): service (from B)")
    }
}

A.shared = B()          // Question : How to remove the need to this line, yet achieving the same functionality (and output)

A.shared.aService()
B.shared.aService()

// The output (which is correct) :
//B: service (from A)
//B: service (from B)
//B: service (from A)
//B: service (from B)

Upvotes: 0

Views: 44

Answers (1)

Pete Morris
Pete Morris

Reputation: 1562

There's a solution, but...

I do, personally, agree with the other comments here. This really doesn't sound like a job for the singleton pattern.

In your current solution, you seem perfectly happy to overwrite the singleton instance mid-execution, which means it's not a singleton. There's only ever supposed to be one instance of a singleton, and if you can continually create and assign a new instance to the shared variable without breaking anything, then it's just not necessary to have this global state across your application.

However, for the record, you can achieve what you want with a struct that composes the static instances of each class, and which can detect the calling context and return the appropriate one each time shared is accessed:

protocol ExampleProtocol {
    static var shared: ExampleProtocol { get }
    func service()
}

struct ExampleProvider {
    private static var a = A()
    private static var b = B()
    static func instance(type: ExampleProtocol.Type) -> ExampleProtocol {
        return type == A.self ? ExampleProvider.a : ExampleProvider.b
    }
}

class A: ExampleProtocol {
    static var shared: ExampleProtocol {
        return ExampleProvider.instance(type: Self.self)
    }
    func service() {
        print("Hello")
    }
}

class B: A {
    override func service() {
        print("Goodbye")
    }
}

A.shared.service() // Hello
B.shared.service() // Goodbye

So, yes, you can achieve what you want. But there's a pretty strong case for saying that you shouldn't...

Upvotes: 1

Related Questions