Search code examples
swiftsyntax-errorsingletonprotocols

Swift: Singleton class "extends" from protocol


I'm trying to make a singleton class from a protocol, my intentions with this is to have multiple xxxxxHandlers, eg. SpotifyHandler, AppleMusicHandler and so on... and create the correct one when a user logs in with their music-account, then make the handler accessible all throughout the program.

But when i try to make SpotifyHandler i run into this error: Property 'shared' in non-final class 'SpotifyHandler' must specify type 'Self' to conform to protocol 'MusicHandler'

On this line: static var shared: SpotifyHandler = SpotifyHandler()

Here's the code for the singleton class and protocol (separate files)

import Foundation
import SwiftUI

class SpotifyHandler : MusicHandler {
    static var shared: SpotifyHandler = SpotifyHandler()
    
    var is_logged_in: Bool = false;
 
    func set_is_logged_in(is_logged_in: Bool) {
        self.is_logged_in = is_logged_in
    }
    
    func print_status() {
        print("--- Status ---")
        print("Is logged in: \(self.is_logged_in)")
    }
}


public protocol MusicHandler {
    static var shared: Self { get }
    
    var is_logged_in: Bool { get }
    
    func set_is_logged_in(is_logged_in: Bool)
    func print_status()
}

I am quite new to swift but have experience in other languages, i am open to suggestions on alternative ways to solve this if you have any. :)


Solution

  • The problem arises because SpotifyHandler being a class can have subclasses. If you create a sub class that inherits from SpotifyHandler it will not conform to the protocol because Self has got to simultaneously have type SpotifyHandler and the type of the subclass e.g.

    class AdFreeSpotifyHandler: SpotifyHandler {}
    
    let foo = AdFreeSpotifyHandler.shared
    

    What type would you expect foo to have?

    There are two solution: you can do what @lorum ipsum suggests and make the shared instance of type MusicHandler or you can stop people from subclassing SpotifyHandler by making it final i.e.

    final class SpotifyHandler : MusicHandler {
        static var shared: SpotifyHandler = SpotifyHandler() // No error
        // rest of the implementation
    }
    

    As a general rule, as soon as your protocol has any Self requirements or an associated type, you can't make non final classes conform to it.


    I don't recommend @lorum ipsum's approach on the whole because if I have

    class AdFreeSpotifyHandler: SpotifyHandler
    {
    }
    
    let foo = AdFreeSpotifyHandler.shared
    
    

    We know foo is a MusicHandler, but it's not an AdFreeSpotifyHandler which is what you'd really want in the above fragment.