I know that you can give a default value with a protocol extension like this
protocol SomeProtocol {
var prop: String { get }
}
extension SomeProtocol {
var prop: String {
return "defaultValue"
}
}
struct SomeA: SomeProtocol {}
struct SomeB: SomeProtocol {}
let a = SomeA()
let b = SomeB()
debugPrint(a.prop) // prints defaultValue
debugPrint(b.prop) // prints defaultValue
but is there a way to have different default value for different implementations of the protocol like this without implementing the property for every class or struct that conforms to this protocol?
debugPrint(a.prop) // prints defaultValue
debugPrint(b.prop) // prints differentDefaultValue
or some similar pattern for doing something like this?
Protocol inheritance.
protocol 😺: SomeProtocol { }
extension 😺 {
var prop: String { "😺" }
}
struct SomeA: SomeProtocol { }
struct SomeB: 😺 { }
struct SomeC: 😺 { }
SomeA().prop // "defaultValue"
SomeB().prop // "😺"
SomeC().prop // "😺"
Related
I've created the following extension:
import Foundation
extension Collection {
/// Returns `nil` if empty
var nonEmptyValue: Self? {
isEmpty ? nil : self
}
}
Now I'd like to make it a property wrapper so I could use it like this:
final class MyClass {
#NonEmpty
var string: String? = "test"
}
The idea is that whenever an empty string is assigned to the property, it gets replaced with nil.
Is it even possible to create such a property wrapper (since String? and String are of different type) and how would I go about it?
I'm using your extension:
import Foundation
#propertyWrapper
struct NonEmpty<T: Collection> {
var wrappedValue: T? {
didSet {
self.wrappedValue = wrappedValue?.nonEmptyValue
}
}
init(wrappedValue: T?) {
self.wrappedValue = wrappedValue?.nonEmptyValue
}
}
extension Collection {
/// Returns `nil` if empty
var nonEmptyValue: Self? {
isEmpty ? nil : self
}
}
and the result is just like image below:
I have the following piece of code. The protocols and extensions I've added don't seem to be doing quite what I expect. What am I missing?
struct foo {
var getFoo: String
var getBar: bar
struct bar {
var getBar: String
}
}
protocol FooProtocol {
var getFoo: String { get }
var getBar: BarProtocol { get }
}
extension foo: FooProtocol {} // Type 'foo' does not conform to protocol 'FooProtocol'
protocol BarProtocol {
var getBar: String { get }
}
extension foo.bar: BarProtocol {}
The problem in this code is the line extension foo: FooProtocol {}, where we're trying to say that, in effect foo.getBar.getBar should be a valid property. However, Xcode throws up a compilation error saying "Type 'foo' does not conform to protocol 'FooProtocol'.
I can fix this by changing the following:
protocol FooProtocol {
var getFoo: String { get }
var getBar: foo.bar { get } // instead of var getBar: BarProtocol { get }
}
However, it seems the line extension foo.bar: BarProtocol {} should mean FooProtocol doesn't care if we give it foo.bar or BarProtocol. What am I missing here?
#Drobs answer was correct. Here's a bonus question.
BONUS
Now suppose I need to implement another struct that conforms to FooProtocol. Neither of these approaches work. What's the fix?
struct FooImplementation: FooProtocol { // Type 'FooImplementation' does not conform to protocol 'FooProtocol'
var getFoo: String
var getBar: BarProtocol
}
Do I need to use some sort of typealias to achieve this?
You want to use an associatedtype in the protocol. In that case it essentially says give me a type that implements the protocol BarProtocol.
protocol FooProtocol {
associatedtype BarType: BarProtocol
var getFoo: String { get }
var getBar: BarType { get }
}
protocol BarProtocol {
var getBar: String { get }
}
struct Foo: FooProtocol {
var getFoo: String
var getBar: Bar
struct Bar: BarProtocol {
var getBar: String
}
}
Let's say I have two non-generic protocols (1)
protocol StringValue {
var asString: String {get}
}
protocol StringProvider {
var value: StringValue {get}
}
I want to have a generic version of the second one (2)
protocol TypedStringProvider: StringProvider { // inherits from StringProvider
associatedtype TypedStringValue: StringValue
var typedValue: TypedStringValue { get }
}
And extension with default implementation of non-generic version to have a code free conformance to StringProvider (doesn't work, pls read below) (3)
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
Now I want several classes to conform both generic TypedStringProvider and non-generic StringProvider protocols (4)
extension UIView: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return "Some String" }
}
extension Double: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return String(self) }
}
extension String: StringValue {
var asString: String { return self }
}
And get compiler error: Type 'UIView' does not conform to protocol 'StringProvider'.
Seemingly extension (3) doesn't work like I want because TypedStringValue is not a StringValue in despite of this constraint associatedtype TypedStringValue: StringValue from (2)
The question is how to conform to non-generic StringProvider while keeping value typed
Example:
0.5.value.lowercased()
Of course StringValue doesn't have lowercased method from String so it won't compile.
What I have tried:
First is to add untyped property to extension (3)
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
var value: StringValue { return typedValue }
}
Doesn't work because of Invalid redeclaration of 'value' error
Second is to extend my classes and add untyped property there (5)
extension UIView {
var value: StringValue { return typedValue }
}
extension Double {
var value: StringValue { return typedValue }
}
It works without compiler errors but
No autocompletion for lowercased in our example.
With extensions (5) I need to write a lot of code for every class conforming StringProvider and every property this protocol has
Any ideas?
value is defined as type StringValue, so this is the type you should specify in your extension of TypedStringProvider in order to complete the protocol conformance:
extension TypedStringProvider {
var value: StringValue {
return typedValue
}
}
Problem
On StringProvider you are defining value to a StringValue:
protocol StringProvider {
var value: StringValue { get }
}
but here you are defining the type to TypedStringValue which is not the same as StringValue. (underlying value can be TypedStringValue, however it needs to be typecasted from StringValue to TypedStringValue when using it)
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
Solution
There are two solutions that I can come up for this scenario right now:
1. Generic Approach
If you want to make value generic and change the type based on TypedStringProvider you can:
protocol StringProvider {
associatedtype StringProviderValue: StringValue
var value: StringProviderValue { get }
}
Conform to the protocol by defining StringProviderValue to TypedStringValue
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
2. StringValue approach
Keep the StringProvider as it is:
protocol StringProvider {
var value: StringValue { get }
}
Conform to the protocol by using the correct StringValue type, and inside it return typedValue: TypedStringValue which can be downcasted to StringValue
extension TypedStringProvider {
var value: StringValue { return typedValue }
}
Output
Both solutions give the same output:
Found solution
extension StringProvider where Self: TypedStringProvider {
var value: StringValue { return typedValue }
}
With this extension there's no need to write similar extensions to every class and autocompletion works too.
Full code:
protocol StringValue {
var asString: String {get}
}
protocol StringProvider {
var value: StringValue {get}
}
protocol TypedStringProvider: StringProvider {
associatedtype TypedStringValue: StringValue
var typedValue: TypedStringValue { get }
}
extension StringProvider where Self: TypedStringProvider {
var value: StringValue { return typedValue }
}
extension TypedStringProvider {
var value: TypedStringValue { return typedValue }
}
extension UIView: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return "some string" }
}
extension Double: TypedStringProvider {
typealias TypedStringValue = String
var typedValue: String { return String(self) }
}
extension String: StringValue {
var asString: String { return self }
}
let doubleValue = 0.5.value.lowercased()
I have a doubt about protocols in Swift about the use of var and the keywords { get set }.
From Apple documentation:
If a protocol requires a property to be gettable and settable, that
property requirement cannot be fulfilled by a constant stored property
or a read-only computed property. If the protocol only requires a
property to be gettable, the requirement can be satisfied by any kind
of property, and it is valid for the property to be also settable if
this is useful for your own code.
Property requirements are always declared as variable properties,
prefixed with the var keyword. Gettable and settable properties are
indicated by writing { get set } after their type declaration, and
gettable properties are indicated by writing { get }.
I can't understand why I can't use let. A var in a protocol with only get isn't just a let?
Something like this:
protocol someProtocol
{
var someProperty: String { get }
}
it would not be just:
protocol someProtocol
{
let someProperty: String
}
I'm missing something?
"A var in a protocol with only get isn't just a let?" No. A let indicates a constant. But that is not the case here. Consider the following:
protocol SomeProtocol {
var someProperty: String { get }
}
class SomeClass : SomeProtocol {
var someProperty: String = ""
func cla () {
someProperty = "asd"
}
}
let someInstance = SomeClass()
print(someInstance.someProperty) // outputs ""
someInstance.cla()
print(someInstance.someProperty) // outputs "asd"
The protocol specifies what the conforming class shows to the outside - some property of type String named someProperty which you can at least get.
If the protocol specifies { get } your class can choose to conform via let someProperty: String = "" but it can similarly choose to conform via the above code. If on the other hand the protocol specifies { get set } you cannot use let in the implementation but have to make it set-able as well.
A protocol simply cannot define that a value has to be constant - neither should it, that is an implementation detail that has to be taken care (or decided about) by the class / struct that implements it.
The difference is between
protocol MyProtocol {
let someProperty: String
}
which makes no sense — a protocol isn't supposed to dictate how someProperty is defined/stored, only that it's available as a property. It could be either a computed or stored property, but that's for the implementer to decide, not the protocol itself.
and
protocol MyProtocol {
var someProperty: String { get } // abstract interface
}
struct MyStruct: MyProtocol {
let someProperty: String // concrete implementation: stored property
}
struct OtherStruct: MyProtocol {
let i: Int
var someProperty: String { return "\(i)" } // concrete implementation: computed property
}
which is perfectly allowed!
I think a protocol can require that a structure has something, but it can't restrict functionality of struct or object. That shouldn't prevent you from doing what you'd probably like to do in code, for example using a var in the protocol and a let for the implementation is acceptable.
protocol MyProtocol {
var trythis: Int { get }
}
struct MyStructure: MyProtocol {
let trythis: Int
}
A property declared with let is considered read-only under the hood. For this reason, a protocol can require that a property be a constant by setting it read-only. This deduction can be verified using some of the Objc runtime functions property_getAttributes.
protocol SomeProtocol {
var someTypeProperty: Int { get }
}
struct Foo: SomeProtocol {
let someTypeProperty: Int
}
I'm looking for a way to add a default initializer to a protocol via protocol extensions.
My protocol is:
protocol TestProtocol {
var myVar : Double { get set }
init(value: Double)
init(existingStruct : TestProtocol)
}
I've implemented a struct using this protocol as:
struct TestStruct : TestProtocol {
var myVar : Double
init(value : Double) {
myVar = value
}
init (existingStruct : TestProtocol) {
myVar = existingStruct.myVar
}
}
However if I try via extension to make a default initializer for this protocol I run into self issues:
extension TestProtocol {
init(value : Double) {
myVar = value
}
init(existingStruct : TestProtocol) {
myVar = existingStruct.myVar
}
}
Where both assignment lines issue the error
Variable 'self' passed by reference before being initialized
Is there a way to make this work - or am i limited to using classes?
Your question is almost the same as in this post I answered yesterday.
Here is the trick to solve this :)
protocol TestProtocol {
var myVar : Double { get set }
init() // designated initializer which will ensure that your class or structer type will instantiate correctly
}
struct TestStruct : TestProtocol {
var myVar : Double
init() {
myVar = 0
}
}
extension TestProtocol {
init(value : Double) {
self.init()
myVar = value
}
init(existingStruct : TestProtocol) {
self.init()
myVar = existingStruct.myVar
}
}
Have a good day. :) Protocol extension is so nice.