Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does the protocol default value passed to the function not change, even though the function does when subclassing?

I have a protocol, to which I have assigned some default values:

protocol HigherProtocol {
    var level: Int { get }
    
    func doSomething()
}

extension HigherProtocol {
    var level: Int { 10 }
    
    func doSomething() {
        print("Higher level is \(level)")
    }
}

Then I have another protocol which conforms to the higher level protocol, but has different default values and implementation of functions:

protocol LowerProtocol: HigherProtocol {}

extension LowerProtocol {
    var level: Int { 1 }
    
    func doSomething() {
        print("Lower level is \(level)")
    }
}

I then create a class that conforms to the HigherProtocol, and then a subclass that conforms to the lower level protocol:

class HigherClass: HigherProtocol {}

class LowerClass: HigherClass, LowerProtocol {}

When I instantiate this lower class, however, it displays some odd behaviour:

let lowerClass = LowerClass()

lowerClass.level // is 1

lowerClass.doSomething() // Prints "Lower level is 10" to the console.

The default property is correct, but the default implementation of the function seems to be a hybrid of the two.

I'm wondering what's happening here?

like image 412
Kramer Avatar asked Jan 19 '21 13:01

Kramer


Video Answer


2 Answers

You appear to be trying to use protocols to create multiple-inheritance. They're not designed for that, and even if you get this working, you're going to get bitten several times. Protocols are not a replacement for inheritance, multiple or otherwise. (As a rule, Swift favors composition rather than inheritance in any form.)

The problem here is that HigherClass conforms to HigherProtocol and so now has implementations for level and doSomething. LowerClass inherits from that, and wants to override those implementations. But the overrides are in a protocol extension, which is undefined behavior. See Extensions from The Swift Programming Language:

Extensions can add new functionality to a type, but they cannot override existing functionality.

Undefined behavior doesn't mean "it doesn't override." It means "anything could happen" including this weird case where it sometimes is overridden and sometimes isn't.

(As a side note, the situation is similar in Objective-C. Implementing a method in two different categories makes it undefined which one is called, and there's no warning or error to let you when this happens. Swift's optimizations can make the behavior even more surprising.)

I wish the compiler could detect these kinds of mistakes and raise an error, but it doesn't. You'll need to redesign your system to not do this.

like image 119
Rob Napier Avatar answered Nov 15 '22 11:11

Rob Napier


Protocols are existential types that is why you are confused. You need to expose to protocol types of your class Type. In your case you can do LowerProtocol or HigherProtocol so it prints 10 now. Let`s make like this

let lowerClass: LowerProtocol = LowerClass()

or

let lowerClass: HigherProtocol = LowerClass()

lowerClass.level // now prints 10

lowerClass.doSomething() // Prints "Lower level is 10" to the console.
like image 23
zeytin Avatar answered Nov 15 '22 12:11

zeytin