Swift - Protocol Extensions - Property Default Values

Swift - Protocol extensions - Property default values

It seems you want to add a stored property to a type via protocol extension. However this is not possible because with extensions you cannot add a stored property.

I can show you a couple of alternatives.

Subclassing (Object Oriented Programming)

The easiest way (as probably you already imagine) is using classes instead of structs.

class IdentifiableBase {
var id = 0
var name = "default"
}

class A: IdentifiableBase { }

let a = A()
a.name = "test"
print(a.name) // test

Cons: In this case your A class needs to inherit from IdentifiableBase and since in Swift theres is not multiple inheritance this will be the only class A will be able to inherit from.

Components (Protocol Oriented Programming)

This technique is pretty popular in game development

struct IdentifiableComponent {
var id = 0
var name = "default"
}

protocol HasIdentifiableComponent {
var identifiableComponent: IdentifiableComponent { get set }
}

protocol Identifiable: HasIdentifiableComponent { }

extension Identifiable {
var id: Int {
get { return identifiableComponent.id }
set { identifiableComponent.id = newValue }
}
var name: String {
get { return identifiableComponent.name }
set { identifiableComponent.name = newValue }
}
}

Now you can make your type conform to Identifiable simply writing

struct A: Identifiable {
var identifiableComponent = IdentifiableComponent()
}

Test

var a = A()
a.identifiableComponent.name = "test"
print(a.identifiableComponent.name) // test

Swift: Default value for properties in Protocol

protocol Test {
var aValue: CGFloat { get set }
}

extension Test {
var aValue: CGFloat {
get {
return 0.3
}
set {
print("the new value is \(newValue)")
}
}
}

class Default: Test {
init() {
print("value \(aValue)")
}
}

class ViewController: Test {

var aValue: CGFloat {
get {
return 0.4
}
set {
print("i am overriding the setter")
}
}

init() {
print("value \(aValue)")
}
}

var d = Default() // value 0.3
d.aValue = 1 // the new value is 1.0

var vc = ViewController() // value 0.4
vc.aValue = 1 // i am overriding the setter

Since you have a protocol extension, you don't have to implement neither the getter nor the setter if you don't want to.

https://docs.swift.org/swift-book/LanguageGuide/Properties.html#//apple_ref/doc/uid/TP40014097-CH14-ID259

In addition to stored properties, classes, structures, and
enumerations can define computed properties, which do not actually
store a value. Instead, they provide a getter and an optional setter
to retrieve and set other properties and values indirectly.

You can't set the value of the same variable in the setter itself.

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.

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.

Is default implementation of a get-set variable possible in a protocol extension?

You can provide default implementations of get/set properties for computed values, but you can’t add storage to a type from an extension.

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

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.

How to encode protocol property default implementation to dictionary

The synthesized encoder considers only the members in the struct, not any properties in a protocol extension nor computed properties.

You have to write a custom initializer. And I'd prefer to make the struct adopt Encodable rather than the protocol.

struct MyStruct: MyStructProtocol, Encodable {
var value: String

private enum CodingKeys: String, CodingKey { case value, defaultValue }

func encode(to encoder: Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)
try container.encode(value, forKey: .value)
try container.encode(defaultValue, forKey: .defaultValue)
}
}

protocol MyStructProtocol { ...


Related Topics



Leave a reply



Submit