Swift enum protocol conformance - swift

I want to create a Swift protocol that my different enums can conform to, so I can use the same 'type' while utilizing the enum's rawValue. Basically, the protocol should look like this:
protocol SidebarCustomFilter {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
An example enum that conforms to this:
enum SidebarFilterLogs : String, CaseIterable, SidebarCustomFilter{
case filterAll = "All"
case filterThisWeek = "This Week"
var image: UIImage {
return UIImage(systemName: "tray.full.fill")!
}
var filterPredicate: NSPredicate {
return NSPredicate.init(format: "TRUEPREDICATE")
}
}
Now I want to use this enum inside a struct:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter?
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
So far so good. However, if I try to use the 'rawValue' for the enum (of the protocol type I've described above), it gives me an error
sidebarItem.sidebarCustomFilter?.rawValue // Value of type 'SidebarCustomFilter' has no member 'rawValue'
That makes sense, since the SidebarCustomFilter is only a protocol. However, if I try to make it inherit from RawRepresentable (which should allow it to work with rawValue),
protocol SidebarCustomFilter: RawRepresentable {
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
I get a different error:
var sidebarCustomFilter: SidebarCustomFilter? // Protocol 'SidebarCustomFilter' can only be used as a generic constraint because it has Self or associated type requirements
I believer this has something to do with RawRepresentable using an associatedtype RawValue, but I'm not sure how to resolve it.
So how do I get my protocol to work such that it enforces that only other enums are conforming to it (and hence it's valid to use 'rawValue' against it)?

Found a solution:
protocol SidebarCustomFilter {
var name: String { get }
var image: UIImage { get }
var filterPredicate: NSPredicate { get }
}
extension SidebarCustomFilter where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
Now I can use sidebarItem.sidebarCustomFilter?.name instead of directly asking for the rawValue, and it works!
EDIT: adding code to show how it's stored:
struct CJSidebarFilterItem: Hashable {
private var identifier: String = ""
var sectionType: SectionType
var sidebarCustomFilter: SidebarCustomFilter? // storing as generic type
init(logsFilter: SidebarFilterLogs) {
self.sectionType = .filters
self.sidebarCustomFilter = logsFilter
self.identifier = UUID().uuidString
}
func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}
static func == (lhs: CJSidebarFilterItem, rhs: CJSidebarFilterItem) -> Bool {
return lhs.identifier == rhs.identifier
}
}
and how it's used:
let titleString = sidebarItem.sidebarCustomFilter?.name ?? ""

Related

any Identifiable can't conform to 'Identifiable'

update: add same error about Hashable
I have created an Identifiable compliant protocol and compliant structures. Then, when I create the list and reference it in ForEach, I get the error Type 'any TestProtocol' cannot conform to 'Identifiable'(I get the same error about Hashable).
How should I fix this program?
If I write ForEach(list, id: \.id) , it works, but I don't think it makes sense to be Identifiable compliant.
import SwiftUI
protocol TestProtocol: Identifiable, Hashable {
var id: UUID { get set }
var name: String { get set }
func greeting() -> String
static func == (lhs: Self, rhs: Self) -> Bool
}
extension TestProtocol {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.id == rhs.id
}
}
struct Person: TestProtocol {
var id = UUID()
var name: String
func greeting() -> String {
return "my name is \(name) and I'm a human."
}
}
struct Dog: TestProtocol {
var id = UUID()
var name: String
func greeting() -> String {
return "my name is \(name) and I'm a dog."
}
}
struct ContentView: View {
var list: [any TestProtocol] = [Person(name: "p1"), Dog(name: "d1")]
#State var selected: any TestProtocol
var body: some View {
VStack {
Picker(selection: $selected) { // Type 'any TestProtocol' cannot conform to 'Hashable'
ForEach(list) { l in // Type 'any TestProtocol' cannot conform to 'Identifiable'
Text(l.greeting()).tag(l) // Type 'any TestProtocol' cannot conform to 'Hashable'
}
} label: {
Text("select")
}
}
}
}
Your error message complaining about Hashable is a "red hering". The protocol TestProtocol, and therefor all structs conforming to it, conforms to Hashable.
let person = Person(name: "IAmHashable")
print(person.hashValue)
The reason this is failing is within the Picker. It needs a concrete type and not a protocol. One solution would be to create a "Container" type and a custom binding that handles this.
struct Container: Identifiable, Hashable{
//implement the same equality as in your TestProtocol
static func == (lhs: Container, rhs: Container) -> Bool {
rhs.wrapped.id == lhs.wrapped.id
}
func hash(into hasher: inout Hasher) {
hasher.combine(wrapped)
}
var wrapped: any TestProtocol
//for convenience
var id: UUID {wrapped.id}
}
and the ContentView:
struct ContentView: View {
let startArr: [any TestProtocol] = [Person(name: "p1"), Dog(name: "d1")]
#State private var selected: (any TestProtocol)?
var body: some View {
// list of wrapped protocols
var list: [Container] = { startArr.map{Container(wrapped: $0)}}()
// binding
let selectionBinding: Binding<Container> = .init {
let returninstance = list.first { cont in
cont.id == selected?.id
}
return returninstance ?? list[0]
} set: { container in
selected = container.wrapped
}
// viewCode
VStack {
Picker(selection: selectionBinding) {
ForEach(list) { l in
Text(l.wrapped.greeting())
.tag(l)
}
} label: {
Text("select")
}
// confirmation selection changed
Text(selected?.name ?? "no Selection")
}
}
}
Remarks:
This solution has a few drawbacks:
your initial array startArr should never be empty, else return returninstance ?? list[0] will break you code. This can be handled, but I think this is out of the scope of this question.
the equality comparison of Container needs to be the same as in TestProtocol as you cannot compare two any Protocols
on the appearance of ContainerView selected will be nil until something is selected. Any usage, e.g.: the Text element in this case, needs to deal with this. But you could probably set this in .onApear.

Return Class conform to protocol

I have a protocol that represent brand for example
I have two class in this example, Jbl and Columbo that conform to Brand protocol
The builder build some stuff and finally return the name of Jbl or Columbo class
(The build stuff is simplified with random in code sample)
In this sample code we can expect randomly Jbl Brand or Columbo Brand in the print result
But this cannot compile with error : Protocol 'Brand' can only be used as a generic constraint because it has Self or associated type requirements
protocol Brand: AnyObject, Hashable {
var name: String { get }
}
extension Brand {
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.name == rhs.name
}
}
class Jbl: Brand {
var name: String { "Jbl" }
}
class Columbo: Brand {
var name: String { "Columbo" }
}
class Builder {
func build() -> Brand { // Protocol 'Brand' can only be used as a generic constraint because it has Self or associated type requirements
if .random() {
return Jbl()
} else {
return Columbo()
}
}
}
var builder = Builder()
print(builder.build().name)
The problem is that for build() you are trying to return a type of Brand, but Hashable has Self requirements as part of its definition. This is why you get the error about "Self or associated type requirements".
A possible solution is to make Brand not Hashable itself, but any class which conforms to Brand and Hashable will get the Hashable implementation for Brand:
protocol Brand: AnyObject {
var name: String { get }
}
extension Brand where Self: Hashable {
func hash(into hasher: inout Hasher) {
hasher.combine(name)
}
static func == (lhs: Self, rhs: Self) -> Bool {
lhs.name == rhs.name
}
}
However, accessing build() will not give you a hashValue, since only classes which conform to Brand and Hashable have hashValue.
To fix this, conform Jbl and Columbo to the Hashable protocol. Then add a function like this in the Builder class for example:
func getHashValue() -> Int {
if .random() {
return Jbl().hashValue
} else {
return Columbo().hashValue
}
}
Called like:
print(builder.getHashValue())
// Prints: -8479619369585612153
Obviously this hash changes each time the app is relaunched, so don't rely on it for persisting data.
And just for style reasons: you may prefer to use the following and make the classes just conform to HashableBrand:
typealias HashableBrand = Brand & Hashable

Swift - Can I make a protocol Hashable?

I have written a simple protocol Data :
public protocol Data {
var state: [String: Any] { get set }
var objectId: String? { get set }
}
public extension Data {
var objectId: String? {
get {
return self.state["objectId"] as? String
}
set {
self.state["objectId"] = newValue
}
}
}
This way, I have created several types conforming to it:
public struct Person: Data {
public var state: [String : Any]
}
public struct Car: Data {
public var state: [String : Any]
}
// ...
Now what I want to do is to make each of these types Hashable, the problem is that I need to write this code in every type:
extension Car Hashable {
public static func == (lhs: Car, rhs: Car) -> Bool {
return lhs.objectId == rhs.objectId
}
public func hash(into hasher: inout Hasher) {
hasher.combine(self.objectId ?? "")
}
}
// ...
What I want to know is if it is possible to generically declare Data as Hashable from its objectId. As I am using a protocol, I couldn't find a way to do so.
Thank you for your help.
As #Leo Dabus mentioned in his comment, you should probably use another name for your protocol due to native Foundation.Data type existence.
Either way, using the following code you can implement Hashable protocol into your Data protocol:
public protocol Data: Hashable {
var state: [String: Any] { get set }
var objectId: String? { get set }
}
public extension Data {
var objectId: String? {
get {
return self.state["objectId"] as? String
}
set {
self.state["objectId"] = newValue
}
}
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.objectId == rhs.objectId
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.objectId ?? "")
}
}
Although this will have as a side effect that protocol Data can only be used as a generic constraint because it has Self or associated type requirements:
Meaning that you can now on use the Data protocol like this:
func myFunc<T: Data>(data: T) {
}

Array of protocol type

I have checked all answers about this problem on stackoverflow, but still can not figure out how to fix this.
My model looks like this
protocol Commandable: Equatable {
var condition: Condition? {get set}
func execute() -> SKAction
}
And 3 structs which implement this protocol
struct MoveCommand: Commandable {
var movingVector: CGVector!
//MARK: - Commandable
var condition: Condition?
func execute() -> SKAction {
...
}
}
extension MoveCommand {
// MARK:- Equatable
static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
return lhs.movingVector == rhs.movingVector && lhs.condition == rhs.condition
}
}
struct RotateCommand: Commandable {
var side: RotationSide!
// MARK: - Commandable
var condition: Condition?
func execute() -> SKAction {
...
}
}
extension RotateCommand {
// MARK: - Equatable
static func ==(lhs: RotateCommand, rhs: RotateCommand) -> Bool {
return lhs.side == rhs.side && lhs.condition == rhs.condition
}
}
The problems start when I am trying to create third structure which has array of [Commandable]:
struct FunctionCommand: Commandable {
var commands = [Commandable]()
The compiler output: Protocol 'Commandable' can only be used as a generic constraint because it has Self or associated type requirements. Then i rewrote my struct in this way:
struct FunctionCommand<T : Equatable>: Commandable {
var commands = [T]()
I resolve this problem but new problem has appeared. Now i can't create FunctionCommand with instances of Rotate and Move command, only with instances of one of them :( :
let f = FunctionCommand(commands: [MoveCommand(movingVector: .zero, condition: nil),
RotateCommand(side: .left, condition: nil)], condition: nil)
Any Help would be appreciated.
Update: That article helped me to figure out - https://krakendev.io/blog/generic-protocols-and-their-shortcomings
What you need to do is to use type erasure, much like AnyHashable does in the Swift Standard Library.
You can't do:
var a: [Hashable] = [5, "Yo"]
// error: protocol 'Hashable' can only be used as a generic constraint because it has Self or associated type requirements
What you have to do is to use the type-erased type AnyHashable:
var a: [AnyHashable] = [AnyHashable(5), AnyHashable("Yo")]
a[0].hashValue // => shows 5 in a playground
So your solution would be to first split the protocol in smaller parts and promote Equatable to Hashable (to reuse AnyHashable)
protocol Conditionable {
var condition: Condition? { get set }
}
protocol Executable {
func execute() -> SKAction
}
protocol Commandable: Hashable, Executable, Conditionable {}
Then create an AnyCommandable struct, like this:
struct AnyCommandable: Commandable, Equatable {
var exeBase: Executable
var condBase: Conditionable
var eqBase: AnyHashable
init<T: Commandable>(_ commandable: T) where T : Equatable {
self.condBase = commandable
self.exeBase = commandable
self.eqBase = AnyHashable(commandable)
}
var condition: Condition? {
get {
return condBase.condition
}
set {
condBase.condition = condition
}
}
var hashValue: Int {
return eqBase.hashValue
}
func execute() -> SKAction {
return exeBase.execute()
}
public static func ==(lhs: AnyCommandable, rhs: AnyCommandable) -> Bool {
return lhs.eqBase == rhs.eqBase
}
}
And then you can use it like this:
var a = FunctionCommand()
a.commands = [AnyCommandable(MoveCommand()), AnyCommandable(FunctionCommand())]
And you can easily access properties of commands, because AnyCommandable implements Commandable
a.commands[0].condition
You need to remember to now add Hashable and Equatable to all your commands.
I used those implementations for testing:
struct MoveCommand: Commandable {
var movingVector: CGVector!
var condition: Condition?
func execute() -> SKAction {
return SKAction()
}
var hashValue: Int {
return Int(movingVector.dx) * Int(movingVector.dy)
}
public static func ==(lhs: MoveCommand, rhs: MoveCommand) -> Bool {
return lhs.movingVector == rhs.movingVector
}
}
struct FunctionCommand: Commandable {
var commands = [AnyCommandable]()
var condition: Condition?
func execute() -> SKAction {
return SKAction.group(commands.map { $0.execute() })
}
var hashValue: Int {
return commands.count
}
public static func ==(lhs: FunctionCommand, rhs: FunctionCommand) -> Bool {
return lhs.commands == rhs.commands
}
}
I think it can be easily done by introduction of your own CustomEquatable protocol.
protocol Commandable: CustomEquatable {
var condition: String {get}
}
protocol CustomEquatable {
func isEqual(to: CustomEquatable) -> Bool
}
Then, you objects have to conform to this protocol and additionally it should conform Equitable as well.
struct MoveCommand: Commandable, Equatable {
let movingVector: CGRect
let condition: String
func isEqual(to: CustomEquatable) -> Bool {
guard let rhs = to as? MoveCommand else { return false }
return movingVector == rhs.movingVector && condition == rhs.condition
}
}
struct RotateCommand: Commandable, Equatable {
let side: CGFloat
let condition: String
func isEqual(to: CustomEquatable) -> Bool {
guard let rhs = to as? RotateCommand else { return false }
return side == rhs.side && condition == rhs.condition
}
}
All you need to do now is connect your CustomEquatable protocol to Swift Equatable through generic extension:
extension Equatable where Self: CustomEquatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.isEqual(to: rhs)
}
}
It's not a perfect solution, but now, you can store your objects in a array of protocol objects and use == operator with your objects as well. For example(I simplified objects a little bit):
let move = MoveCommand(movingVector: .zero, condition: "some")
let rotate = RotateCommand(side: 0, condition: "some")
var array = [Commandable]()
array.append(move)
array.append(rotate)
let equal = (move == MoveCommand(movingVector: .zero, condition: "some"))
let unequal = (move == MoveCommand(movingVector: .zero, condition: "other"))
let unequal = (move == rotate) // can't do this, compare different types
PS. Using var on struct is not a good practice, especially for performance reasons.
I believe the problem here is that the equatable protocol has self requirements. So you can solve you problem by removing equatable protocol from your Commandable protocol and make your your structs equatable instead. This will of course limit your protocol but maybe it is a trade-off that is reasonable?

Swift: Hashable struct with dictionary property

I have a struct in Swift that looks like this:
internal struct MapKey {
internal let id: String
internal let values: [String:String]
}
extension MapKey: Equatable {}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
I now have the need to use MapKey as the key in a Swift dictionary, which requires MapKey to conform to the Hashable protocol.
What would a correct implementation of Hashable be for a struct like this one?
extension MapKey: Hashable {
var hashValue: Int {
return ??? // values does not have a hash function/property.
}
}
I've been doing some research but failed to identify what the proper way to hash a dictionary is, as I need to be able to generate a hash value for values property itself. Any help is much appreciated.
I think you need to review your data model if you have to use a whole struct as a dictionary key. Anyhow, here's one way to do it:
internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]
var hashValue: Int {
get {
var hashString = self.id + ";"
for key in values.keys.sort() {
hashString += key + ";" + values[key]!
}
return hashString.hashValue
}
}
}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
This assumes that you don't have semicolon (;) in id or in the keys and values of values. Hasable implies Equatable so you don't need to declare it conforming to Equatable again.
Since both id and values are immutable both are ok to use as basis for equals and hashValue.
However - if MapKey.id (which the name somewhat implies) uniquely identifies the MapKey (at least within the context of one dictionary)
then it is both easier and more performant to just use the MakKey.id as basis for == operator as well as hashValue
internal struct MapKey: Hashable {
internal let id: String
internal let values: [String:String]
var hashValue: Int {
get { return self.id.hashValue}
}
}
func ==(lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id
}
foundation data types are Hashable in Swift 4.2, you only need to let your MapKey struct to conform Hashable protocol:
struct MapKey: Hashable {
let id: String
let values: [String: String]
}
in case you want to use a class, you need conform hash(:) func like this:
class MapKey: Hashable {
static func == (lhs: MapKey, rhs: MapKey) -> Bool {
return lhs.id == rhs.id && lhs.values == rhs.values
}
let id: String = ""
let values: [String: String] = [:]
func hash(into hasher: inout Hasher) {
hasher.combine(id)
hasher.combine(values)
}
}