Search code examples
swiftswift-protocolsswift-extensions

how to extend a swift protocol with an extension so that classes get an implemented variable defined by the protocol


Circumstances

The following class:

class User {
    var name = "Max"
    var surname = "Mustermann"
}

and the following protocol:

protocol Sharable {
    func share(name: String)
    var isSharable: Bool { get set }
}

Intention

Extend the protocol sharable in a way so that subclasses of User get an implemented version of share(:) and isSharable.

The following extension attempts to do this:

extension Sharable where Self: User {
    func share(name: String) { print(name) }
    var isSharable: Bool { return !name.isEmpty }
}

Where the problem occurs

The created subclass Admin should get all functionality of Sharable for free without storing and implementing variables and functions.

class Admin: User, Sharable {
    
}

But Xcode will print the error:

'Admin' does not conform to protocol 'Sharable'

Xcode proposes the following:

class Admin: User, Sharable {
    var isSharable: Bool
}

Now Xcode prints the error:

class 'Admin' has no initializers

Question

Is there a way to extend the protocol Sharable so that Admin has a variable isSharable without defining or initializing it? Basically like it works with the function share(:), which has not to be implemented by the subclass because it is implemented by the extension.

Desired Call site

class ArbitraryObject: User, Sharable {
    // no implementation or initializing of isSharable or share(:)
}


let arbitraryObject = ArbitraryObject()
if arbitraryObject.isSharable {
    arbitraryObject.share(name: arbitraryObject.name)
}

Solution

  • Currently your protocol defines isSharable as { get set }

    protocol Sharable {
        func share(name: String)
        var isSharable: Bool { get set }
    }
    

    This means that when you construct your extension it is not actually conforming to the protocol

    extension Sharable where Self: User {
        func share(name: String) { print(name) }
        var isSharable: Bool { return !name.isEmpty }
    }
    

    This is due to the fact that you have defined isSharable in your extension as a computed property. Computed properties are get only.

    You can make your extension conform to the Sharable protocol by removing the set requirement. So your protocol would become:

    protocol Sharable {
        func share(name: String)
        var isSharable: Bool { get }
    }