Return Class conform to protocol - swift

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

Related

How to add the Hashable protocol to CLLocationCoordinate2D using an extension in SwiftUI

So I have a custom struct with one property of type String and another of type CLLocationCoordinate2D. Apparently, String conforms to Hashable and if I can extend CLLocationCoordinate2D to conform to Hashable, my custom struct will also be Hashable. Here's my attempt at extending CLLocationCoordinate2D:
extension CLLocationCoordinate2D {
static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
func hash(into hasher: inout Hasher) {
hasher.combine(self.latitude) //wasn't entirely sure what to put for the combine parameter but I saw similar things online
}
}
You need to declare Hashable explicitly:
extension CLLocationCoordinate2D: Hashable {
public static func == (lhs: Self, rhs: Self) -> Bool {
return lhs.latitude == rhs.latitude && lhs.longitude == rhs.longitude
}
public func hash(into hasher: inout Hasher) {
hasher.combine(latitude)
hasher.combine(longitude)
}
}
CoreLocation and MapKit haven't been properly Swiftified yet. I recommend reusing functionality for them.
extension CLLocationCoordinate2D: HashableSynthesizable { }
extension MKCoordinateRegion: HashableSynthesizable { }
extension MKCoordinateSpan: HashableSynthesizable { }
/// A type whose `Hashable` conformance could be auto-synthesized,
/// but either the API provider forgot, or more likely,
/// the API is written in Objective-C, and hasn't been modernized.
public protocol HashableSynthesizable: Hashable { }
public extension HashableSynthesizable {
static func == (hashable0: Self, hashable1: Self) -> Bool {
zip(hashable0.hashables, hashable1.hashables).allSatisfy(==)
}
func hash(into hasher: inout Hasher) {
hashables.forEach { hasher.combine($0) }
}
}
private extension HashableSynthesizable {
var hashables: [AnyHashable] {
Mirror(reflecting: self).children
.compactMap { $0.value as? AnyHashable }
}
}

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) {
}

Swift enum protocol conformance

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 ?? ""

Swift Declare a protocol equatable

I have a protocol:
protocol CustomProtocol {
var title: String { get }
var subtitle: String { get }
}
Then i have 2 objects, that conform this procotol. And i want to compare them, so i would like to CustomProtocol to be Equatable.
protocol CustomProtocol: Equatable {
var title: String { get }
var subtitle: String { get }
static func ==(lhs: Self, rhs: Self) -> Bool
}
extension CustomProtocol {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.title == rhs.title
}
}
But after that change i get "Protocol CustomProtocol can only be used as a generic constraint because it has Self or associated type requeriments.
The only way i can think to solve this is to have a third property like a hash that depends on the others and compare this property.
Here you have a sample playground with the actual code.
Because Equatable has Self requirements, it should not be implemented directly on a protocol. Otherwise, the protocol will be unusable as a type.
To implement Equatable at the protocol level yet be able to use the protocol as a type, you can use type erasure.
To demonstrate, I have modified the code given in your playground to build a type eraser.
For a detailed explanation of the approach I have used, check out this post on my blog:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/
Here is the modified code from your playground:
protocol CustomProtocol {
var title: String { get }
var subtitle: String { get }
func isEqualTo(_ other: CustomProtocol) -> Bool
func asEquatable() -> AnyEquatableCustomProtocol
}
extension CustomProtocol where Self: Equatable {
func isEqualTo(_ other: CustomProtocol) -> Bool {
guard let o = other as? Self else { return false }
return self == o
}
func asEquatable() -> AnyEquatableCustomProtocol {
return AnyEquatableCustomProtocol(self)
}
}
struct A: CustomProtocol, Equatable {
var title: String
var subtitle: String
static func ==(lhs: A, rhs: A) -> Bool {
return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
}
}
struct B: CustomProtocol, Equatable {
var title: String
var subtitle: String
static func ==(lhs: B, rhs: B) -> Bool {
return lhs.title == rhs.title && lhs.subtitle == rhs.subtitle
}
}
struct AnyEquatableCustomProtocol: CustomProtocol, Equatable {
var title: String { return value.title }
var subtitle: String { return value.subtitle }
init(_ value: CustomProtocol) { self.value = value }
private let value: CustomProtocol
static func ==(lhs: AnyEquatableCustomProtocol, rhs: AnyEquatableCustomProtocol) -> Bool {
return lhs.value.isEqualTo(rhs.value)
}
}
// instances typed as the protocol
let a: CustomProtocol = A(title: "First title", subtitle: "First subtitle")
let b: CustomProtocol = B(title: "First title", subtitle: "First subtitle")
let equalA: CustomProtocol = A(title: "First title", subtitle: "First subtitle")
let unequalA: CustomProtocol = A(title: "Second title", subtitle: "Second subtitle")
// equality tests
print(a.asEquatable() == b.asEquatable()) // prints false
print(a.asEquatable() == equalA.asEquatable()) // prints true
print(a.asEquatable() == unequalA.asEquatable()) // prints false
The point to note is that with this approach the actual == comparisons are delegated to the underlying concrete types, yet we deal only with protocol types to maintain abstraction.
Here I have used the type erased instances only for one comparison. However, since the type eraser conforms to CustomProtocol these instances can be saved and used in any place where a protocol type is expected. Because they conform to Equatable, they can also be used in any place where Equatable conformance is required.
Just for context, this post explains why it is not advisable to try to implement Equatable conformance or even == functions directly on protocols:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/
Hence the type erasure.
Hope this helps.
The Equatable protocol has a self constraint to solve the problem that you only should be able to check equality between objects of the same type, not the same protocol. That's why it has a self-requirement. Otherwise you could just say
let a: Equatable = 42
let b: Equatable = "hello"
and a == b would work. This would be bad because you could compare objects of totally unrelated types. The self-requirement makes this a compile time error.
If you want to compare your objects on a protocol basis, just implement the == operator without a self-requirement:
extension CustomProtocol {
func == (lhs: CustomProtocol, rhs: CustomProtocol) -> Bool {
return lhs.name == rhs.name
}
func != (lhs: CustomProtocol, rhs: CustomProtocol) -> Bool {
return !(lhs == rhs)
}
}
Now you can declare instances of your protocol directly with the CustomProtocol type and compare them.
But maybe the protocol is not the right abstraction in this case. Maybe you should implement this as an abstract class.
The problem is the rhs parameter is not the same type of the lhs. They just conform to the same protocol.
You could solve that by using a generic type as the second parameter.
exension CustomProtocol {
static func ==<T: CustomProtocol>(lhs: Self, rhs: T) -> Bool {
return lhs.title == rhs.title
}
}

How to solve compile error using Array.filter? [duplicate]

I have an array called subscribers that stores objects which conform to the protocol JABPanelChangeSubscriber. The protocol is declared as
public protocol JABPanelChangeSubscriber {
}
and my array is declared as:
var subscribers = [JABPanelChangeSubscriber]()
Now I need to implement a method to add a subscriber to the list, but it first has to check that that subscriber has not already been added before.
public func addSubscriber(subscriber: JABPanelChangeSubscriber) {
if subscribers.find(subscriber) == nil { // This ensures that the subscriber has never been added before
subscribers.append(subscriber)
}
}
Unfortunately, JABPanelChangeSubscriber is not Equatable, and I can't figure out how to make it Equatable, so the find method is giving me an error. Can anyone help me out with a fix or with a suggestion for a different approach?
Thanks
Assuming that all types implementing your protocol are reference types
(classes), you can declare the protocol as a "class protocol"
public protocol JABPanelChangeSubscriber : class {
}
and use the identity operator === to check if the array already
contains an element pointing to the same instance as the given argument:
public func addSubscriber(subscriber: JABPanelChangeSubscriber) {
if !contains(subscribers, { $0 === subscriber } ) {
subscribers.append(subscriber)
}
}
Add isEqualTo(:) requirement to JABPanelChangeSubscriber protocol
public protocol JABPanelChangeSubscriber {
func isEqualTo(other: JABPanelChangeSubscriber) -> Bool
}
Extension to JABPanelChangeSubscriber protocol with Self requirement
extension JABPanelChangeSubscriber where Self: Equatable {
func isEqualTo(other: JABPanelChangeSubscriber) -> Bool {
guard let other = other as? Self else { return false }
return self == other
}
}
Remember to make objects conform Equatable protocol too
class SomeClass: Equatable {}
func == (lhs: SomeClass, rhs: SomeClass) -> Bool {
//your code here
//you could use `===` operand
}
struct SomeStruct: Equatable {}
func == (lhs: SomeStruct, rhs: SomeStruct) -> Bool {
//your code here
}
Then find the index
func indexOf(object: JABPanelChangeSubscriber) -> Int? {
return subcribers.indexOf({ $0.isEqualTo(object) })
}
If you want to check the existence of an object before adding them to the array
func addSubscriber(subscriber: JABPanelChangeSubscriber) {
if !subscribers.contains({ $0.isEqualTo(subscriber) }) {
subscribers.append(subscriber)
}
}