Swift: Providing a Default Protocol Implementation in a Protocol Extension

swift protocol extension default implementation vs actual implementation in class

Having a default implementation for a required protocol function/property means that your conforming types won't have to implement that function/property, they can use the default implementation instead.

However, if a conforming type does implement the function/property, then the compiler will always call the more specific implementation, namely the one in your conforming class and not the default one.

So even if you stored an instance of MyClass2 in a variable of type MyProtocol, you'd still get the MyClass2 implementation when accessing the property on the variable.

let myClass2: MyProtocol = MyClass2()
type(of: myClass2).name // "Specific name"

The behaviour is different for non-required properties/functions declared and defined in a protocol extension. If you declare a property/function in the protocol extension only, then even if you provide a different implementation for that in a conforming class, you won't be able to access that implementation from a variable whose type is the protocol type rather than the specific conforming type.

protocol MyProtocol {
static var name: String { get }
}

extension MyProtocol {
static var name: String {
return "unnamed"
}

// Optional protocol requirement
static var nonRequired: String {
return "nonRequired"
}
}

// does not specify its own name
class MyClass: MyProtocol { }

//specifies its own name!
class MyClass2: MyProtocol {
static var name: String {
return "Specific name"
}

// Specific implementation
static var nonRequired: String {
return "Specific"
}
}

let myClass = MyClass()
MyClass.name

let myClass2: MyProtocol = MyClass2()
type(of: myClass2).name // "Specific name"
type(of: myClass2).nonRequired // "nonRequired"
MyClass2.nonRequired // "Specific"

override protocol extension default implementation

If you declare the variable as the protocol type, it will always take the default implementation of the protocol method, since the method is declared in an extension of the protocol.

Without adding the method to the protocol declaration itself (which you've stated to be not possible for you), the only way to access the specific implementation of your conforming type is to downcast some to B or store it as B in the first place.

let some: SomeProtocol = B()
some.someMethod() //this prints "protocol implementation"
(some as? B)?.someMethod() // this prints "class implementation"

let someB = B()
someB.someMethod() // this prints "class implementation"

Swift - Protocol default implementation in extension with generic superclass constraint

This is because Printer<A> and Printer<B> are different types, even A & B are Printable, so due to possible ambiguity compiler generate error.

You need the following (tested with Xcode 11.4)

extension Press {
func print<P>() where Self: Printer<P>, P: Printable {
// Do Something with self.printable.value
}
}

Extending a Protocol property to provide a default implementation in Swift

A possible solution is to locate the current value in the allCases collection,
and the return the next element (or wrap around to the first element):

public protocol CycleValue: CaseIterable, Equatable {
var nextValue: Self { get }
}

public extension CycleValue {
var nextValue: Self {
var idx = Self.allCases.index(of: self)!
Self.allCases.formIndex(after: &idx)
return idx == Self.allCases.endIndex ? Self.allCases.first! : Self.allCases[idx]
}
}

(Note that both forced unwraps are safe!)
Example:

public enum AppThemeAttributes: CycleValue  {
case classic, darkMode // etc.
}

let a = AppThemeAttributes.classic
print(a) // classic
let b = a.nextValue
print(b) // darkMode
let c = b.nextValue
print(c) // classic

The protocol must conform to Equatable for this to compile, but that
is not a real restriction: A CaseIterable protocol cannot have
associated values, so that the compiler can always synthesize the
Equatable conformance.

How the call the default implementation code in protocol in swift?

A protocol is more like a compile-time guarantee that type has certain methods and properties. Default implementation adds another layer of complexity to this by injecting an implementation to the protocol's adopters. I don't have the skills to go through Swift's source code but I think when the adopter provide its own implementation, the default implementation is overshadowed and there's no way to get it back.

A workaround is to add a method with a different name to your protocol, which provides the default implementation and can be called by any adopter:

protocol ImplementedProtocol {
func printInfo()

func defaultPrintInfo()
}

extension ImplementedProtocol where Self: BaseClass {
func printInfo() {
defaultPrintInfo()
}

func defaultPrintInfo() {
print("Hello! This is ImplementedProtocol")
}
}

class SuperClass: BaseClass, ImplementedProtocol {
func printInfo() {
self.defaultPrintInfo()
print("Hello! This is SuperClass")
}
}

How to add default implementation using generic protocol extension?

Maybe this would be simpler.

extension Comparable {
func limit(from minValue: Self, to maxValue: Self) -> Self {
return Swift.max(Swift.min(self, maxValue), minValue)
}
}

Swift protocol default values are not changable


var type1: AssetViewAttribures = Crypto(name: "name", logo: URL(string: "https://pixabay.com/de/illustrations/online-maus-web-internet-weltweit-523234/")!, symbol: "symbol", avgPrice: "123", precision: 2)

type1.avgPrice

This would call the getter declared in the protocol extension, which just returns "". This is because Crypto.avgPrice has no relation to the avgPrice declared in the protocol extension. You can't "override" a member in an extension, because extensions are dispatched statically. The compiler sees that test is of type AssetViewAttributes, finds the default getter you have declared in the extension, and that's what it will call.

To fix this, you need to add avgPrice as a requirement of the protocol:

protocol AssetViewAttributes {
...
var avgPrice: String { get }
}

This causes Swift to find avgPrice declared in the protocol, and dispatches it dynamically. If the implementing class happens to implement avgPrice, that implementation will be called. If not, then the default implementation is called.

Why must Protocol defaults be implemented via Extensions in Swift?

The @optional directive is an Objective-C only directive and has not been translated to Swift. This doesn't mean that you can't use it in Swift, but that you have to expose your Swift code to Objective-C first with he @objc attribute.

Note that exposing to Obj-C will only make the protocol available to types that are in both Swift and Obj-C, this excludes for example Structs as they are only available in Swift!

To answer your first question, Protocols are here to define and not implement:

A protocol defines a blueprint of methods, properties, and other requirements [...]

And the implementation should thus be supplied by the class/stuct/enum that conforms to it:

The protocol can then be adopted by a class, structure, or enumeration to provide an actual implementation of those requirements

This definition really applies to Protocols we use in our daily lives as well. Take for example the protocol to write a Paper:

The PaperProtocol defines a paper as a document with the following non-nil variables:

  • Introduction
  • Chapters
  • Conclusion

What the introduction, chapters and conclusion contain are up to the one implementing them (the writer) and not the protocol.

When we look at the definition of Extensions, we can see that they are here to add (implement) new functionalities:

Extensions add new functionality to an existing class, structure, enumeration, or protocol type. This includes the ability to extend types for which you do not have access to the original source code.

So extending a protocol (which is allowed) gives you the possibility to add new functionality and hereby give a default implementation of a defined method. Doing so will work as a Swift only alternative to the @optional directive discussed above.

UPDATE:

While giving a default implementation to a protocol function in Switch does make it "optional", it is fundamentally not the same as using the @optional directive in Objective-C.

In Objective-C, an optional method that is not implemented has no implementation at all so calling it would result in a crash as it does not exist. One would thus have to check if it exists before calling it, in opposition to Swift with an extension default where you can call it safely as a default implementation exists.

An optional in Obj-C would be used like this:

NSString *thisSegmentTitle;
// Verify that the optional method has been implemented
if ([self.dataSource respondsToSelector:@selector(titleForSegmentAtIndex:)]) {
// Call it
thisSegmentTitle = [self.dataSource titleForSegmentAtIndex:index];
} else {
// Do something as fallback
}

Where it's Swift counterpart with extension default would be:

let thisSegmentTitle = self.dataSource.titleForSegmentAtIndex(index)


Related Topics



Leave a reply



Submit