Swift struct adopting protocol with static read-write property doesn't conform?
At this point I'm persuaded by Nate Cook's example that this is nothing but a bug in the Swift compiler. As he points out, merely adding an empty didSet
observer on the static variable allows the code to compile. The fact that this could make a difference, even though it makes no functional difference, has "bug" written all over it.
Is it possible to have a Swift protocol that enforces static method and not class method or vice versa?
You can't, because the apple's docs says explicitly to use only static
for this purpose:
To declare a
class
orstatic
method requirement in a protocol
declaration, mark the method declaration with thestatic
declaration
modifier.
Source: Protocol Method Declaration
When you implement the static
method of the protocol
in your class
, there is no difference in using class
or static
in your implementation.
protocol ProtocolForClasses: class {
static func method()
}
class ClassOne: ProtocolForClasses {
class func method() {
}
}
class ClassTwo: ProtocolForClasses {
static func method() {
}
}
Why declare readonly property in protocol?
So that it's not settable from outside the class/struct. Imagine your API returned some instance of a protocol that has a get and set property (in your protocol), then anyone getting this instance would be able to set the value!
Also get and set properties can't be constants:
protocol RWProt {
var value : Int { get set }
}
// Error: Type 'Value' does not conform to protocol 'RWProt'
struct Value : RWProt {
let value = 0
}
This however works:
protocol Read {
var value : Int { get }
}
struct Value : Read {
var value = 0
mutating func change() {
value++
}
}
The protocol only needs the value to be gettable, so get protocols properties are not get only but rather get or set
Okay, here is another example:
import Foundation
public protocol ExternalInterface {
var value : Int { get }
}
private struct PrivateStuff : ExternalInterface {
var value = 0
mutating func doSomePrivateChangingStuff() {
value = Int(arc4random())
}
}
public func getInterfaceToPrivateStuff() -> ExternalInterface {
var stuff = PrivateStuff()
stuff.doSomePrivateChangingStuff()
return stuff
}
// In another file:
let interfaceToSomethingICantChange = getInterfaceToPrivateStuff()
// error: cannot assign to property: 'value' is a get-only property
interfaceToSomethingICantChange.value = 0
In Swift, why does assigning to a static variable also invoke its getter
This is because static and global stored variables are currently (this is all subject to change) only given one accessor by the compiler – unsafeMutableAddressor
, which gets a pointer to the variable's storage (this can be seen by examining the SIL or IR emitted).
This accessor:
Gets a pointer to a compiler-generated global flag determining whether the static variable has been initialised.
Calls
swift_once
with this pointer, along with a function that initialises the static variable (this is the initialiser expression you give it, i.e= Hat()
). On Apple platforms,swift_once
simply forwards ontodispatch_once_f
.Returns a pointer to the static variable's storage, which the caller is then free to read and mutate – as the storage has static lifetime.
So it does more or less the equivalent of the Objective-C thread-safe lazy initialisation pattern:
+(Hat*) hat {
static Hat* sharedHat = nil;
static dispatch_once_t oncePredicate;
dispatch_once(&oncePredicate, ^{
sharedHat = [[Hat alloc] init];
});
return sharedHat;
}
The main difference being that Swift gives back a pointer to the storage of sharedHat
(a pointer to a reference), rather than sharedHat
itself (just a reference to the instance).
Because this is the one and only accessor for static and global stored variables, in order to perform an assignment, Swift needs to call it in order to get the pointer to the storage. Therefore, if it wasn't initialised already – the accessor needs to first initialise it to its default value (as it has no idea what the caller is going to do with it), before the caller then sets it to another value.
This behaviour is indeed somewhat unintuitive, and has been filed as a bug. As Jordan Rose says in the comments of the report:
This is currently by design, but it might be worth changing the design.
So this behaviour could well change in a future version of the language.
Satisfy protocol requirement with property of covariant type
Question: Are there any workarounds for this limitation?
You could use an associatedtype
in P
(conforming to V
) that is used as the type annotation of v
in P
, and use this associatedtype
as a generic typeholder for types conforming to P
.
protocol V {}
protocol P {
associatedtype T: V
var v: T? { get }
}
/* typeholder conformance via protocol inheritance */
protocol Some: V {}
class B<T: Some>: P {
var v: T?
}
/* ... protocol composition */
protocol Another {}
class C<T: Another & V>: P {
var v: T?
}
How to work around Swift not supporting first class meta types?
The solution seems to be a type-erased wrapper. Type-erasure fixes the problem of not being able to use protocols with associated types (PATs) as first-class citizens, by creating a wrapper type, which only exposes the properties defined by the protocol, which it wraps.
In this case, LanguageType
is a PAT, due to its adoption of Equatable
(which it conforms to, due to its adoption of Hashable)
:
protocol LanguageType: Hashable { /*...*/ }
Therefore it can not be used as a first-class type in the Translatable
protocol:
protocol Translatable {
var translations: [LanguageType: [String]] { get set } // error
}
Defining an associated type for Translatable
would not fix the problem, as this would constrain the LanguageType
to be one specific type:
protocol Translatable {
typealias Language: LanguageType
var translations: [Language: [String]] { get set } // works
}
struct MyTranslatable<T: LanguageType>: Translatable {
var translations: [T: [String]] // `T` can only be one specific type
//...
}
As mentioned the solution is a type-erased wrapper AnyLanguage
(Apple uses the same naming convention for their type-erased wrappers. For example AnySequence
):
// `AnyLanguage` exposes all of the properties defined by `LanguageType`
// in this case, there's only the `description` property
struct AnyLanguage: LanguageType {
private(set) var description: String
// `AnyLanguage` can be initialized with any type conforming to `LanguageType`
init<T: LanguageType>(_ language: T) { description = language.description }
}
// needed for `AnyLanguage` to conform to `LanguageType`, as the protocol inherits for `Hashable`, which inherits from `Equatable`
func ==(left: AnyLanguage, right: AnyLanguage) -> Bool {
return left.description == right.description
}
// the use of `AnyLanguage` allows any `LanguageType` to be used as the dictionary's `Key`, as long as it is wrapped as `AnyLanguage`
protocol Translateable {
var translations: [AnyLanguage: [String]] { get set }
}
This implementation now allows the following:
struct SomethingTranslatable: Translatable {
var translations: [AnyLanguage: [String]] = [:]
}
func ==(left: SomethingTranslatable, right: SomethingTranslatable) -> Bool { /*return some `Bool`*/ }
struct English: LanguageType { }
struct German: LanguageType { }
var something = SomethingTranslatable()
something.translations[AnyLanguage(English())] = ["Hello", "World"]
let germanWords = something.translations[AnyLanguage(German())]
Different types, conforming to LanguageType
, can now be used as the Key
. The only syntactical difference, is the necessary initialization of an AnyLanguage
:
AnyLanguage(English())
How to make array of protocol at the same time conform to Equatable?
Thanks to @Alexander and his pointed video resource - https://youtu.be/_m6DxTEisR8?t=2585
Here's is the good workaround, to overcome the current limitation of Swift's protocol.
protocol Animal {
func isEqual(to: Animal) -> Bool
}
func isEqual(lhs: [Animal], rhs: [Animal]) -> Bool {
let count0 = lhs.count
let count1 = rhs.count
if count0 != count1 {
return false
}
for i in 0..<count0 {
if !(lhs[i].isEqual(to: rhs[i])) {
return false
}
}
return true
}
// struct. By conforming Equatable, struct is getting an auto
// implementation of "static func == (lhs: Dog, rhs: Dog) -> Bool"
struct Dog: Animal, Equatable {
func isEqual(to other: Animal) -> Bool {
guard let other = other as? Dog else { return false }
return self == other
}
}
// class
class Cat: Animal, Equatable {
static func == (lhs: Cat, rhs: Cat) -> Bool {
// TODO:
return true
}
func isEqual(to other: Animal) -> Bool {
guard let other = other as? Cat else { return false }
return self == other
}
}
var animals0 = [Animal]()
var animals1 = [Animal]()
// Instead of using
// if animals0 == animals1 {
// we will use the following function call
if isEqual(lhs: animals0, rhs: animals1) {
}
Conforming to Hashable protocol?
You're missing the declaration:
struct DateStruct: Hashable {
And your ==
function is wrong. You should compare the three properties.
static func == (lhs: DateStruct, rhs: DateStruct) -> Bool {
return lhs.year == rhs.year && lhs.month == rhs.month && lhs.day == rhs.day
}
It's possible for two different values to have the same hash value.
Related Topics
Sound for Scene Transition, That Doesn't Stutter
Solve Equations of Type A*X = B Using Dgtsv_ or Sgtsv_
Getting Reference to a Dictionary Value
Change Color of Row Programmatically in Watchkit
How to Convert String to Date Without Time in Swift 3
How to Enable a Button in Different Cases in Swift
Using Auto Layout to Orientate Stack Views Vertically in Portrait and Horizontally in Landscape
How to Create Viewcontrollers Without Storyboard and Set One as Delegate of The Other One
Kvo Listener Issues in Swift 4
Swiftui: Unable to Animate Images
How to Reconnect Akplayer and Akmixer After Audiokit.Stop()
How to Pause an Animation in Swiftui
Adding Constraints Programmatically in UIview with UItextview
Problems with Unified Logging, Staticstring, Customstringconvertible and Description
Swift: Mkannotation Long Title Text
Scenekit Ar Game Fps Getting Low and The Device Getting Hot with Use