Storing objects conforming to a protocol with generics in a typed array - ios

I've got a protocol:
protocol Adjustable: Equatable {
associatedtype T
var id: String { get set }
var value: T { get set }
init(id: String, value: T)
}
And a struct that conforms to it:
struct Adjustment: Adjustable {
static func == (lhs: Adjustment, rhs: Adjustment) -> Bool {
return lhs.id == rhs.id
}
typealias T = CGFloat
var id: String
var value: T
}
And I'm building a wrapper class that behaves like a Set to handle an ordered list of these properties:
struct AdjustmentSet {
var adjustmentSet: [Adjustable] = []
func contains<T: Adjustable>(_ item: T) -> Bool {
return adjustmentSet.filter({ $0.id == item.id }).first != nil
}
}
let brightness = Adjustment(id: "Brightness", value: 0)
let set = AdjustmentSet()
print(set.contains(brightness))
But that of course doesn't work, erroring with:
error: protocol 'Adjustable' can only be used as a generic constraint because it has Self or associated type requirements
var adjustmentSet: [Adjustable] = []
Looking around, I thought at first this was because the protocol doesn't conform to Equatable, but then I added it, and it still doesn't work (or I did it wrong).
Moreover, I would like to be able to use a generic here, so that I can do something like:
struct Adjustment<T>: Adjustable {
static func == (lhs: Adjustment, rhs: Adjustment) -> Bool {
return lhs.id == rhs.id
}
var id: String
var value: T
}
let brightness = Adjustment<CGFloat>(id: "Brightness", value: 0)
Or:
struct FloatAdjustment: Adjustable {
static func == (lhs: Adjustment, rhs: Adjustment) -> Bool {
return lhs.id == rhs.id
}
typealias T = CGFloat
var id: String
var value: T
}
let brightness = FloatAdjustment(id: "Brightness", value: 0)
And still be able to store an array of [Adjustable] types, so that eventually I can do:
var set = AdjustmentSet()
if set.contains(.brightness) {
// Do something!
}
Or
var brightness = ...
brightness.value = 1.5
set.append(.brightness)

You can't have an array of items of type Adjustable, because Adjustable isn't really a type. It's a blue print that describes a set of types, one per every possible value of T.
To get around this, you need to use a type eraser https://medium.com/dunnhumby-data-science-engineering/swift-associated-type-design-patterns-6c56c5b0a73a

Have made some great progress using Alexander's suggestion; I was able to use some nested class types to inherit the base type erasure class, and use a generic protocol that conforms to AnyHashable so I can use this with a set!
// Generic conforming protocol to AnyHashable
protocol AnyAdjustmentProtocol {
func make() -> AnyHashable
}
protocol AdjustmentProtocol: AnyAdjustmentProtocol {
associatedtype A
func make() -> A
}
struct AdjustmentTypes {
internal class BaseType<T>: Hashable {
static func == (lhs: AdjustmentTypes.BaseType<T>, rhs: AdjustmentTypes.BaseType<T>) -> Bool {
return lhs.name == rhs.name
}
typealias A = T
var hashValue: Int { return name.hashValue }
let name: String
let defaultValue: T
let min: T
let max: T
var value: T
init(name: String, defaultValue: T, min: T, max: T) {
self.name = name
self.defaultValue = defaultValue
self.min = min
self.max = max
self.value = defaultValue
}
}
class FloatType: BaseType<CGFloat> { }
class IntType: BaseType<Int> { }
}
struct AnyAdjustmentType<A>: AdjustmentProtocol, Hashable {
static func == (lhs: AnyAdjustmentType<A>, rhs: AnyAdjustmentType<A>) -> Bool {
return lhs.hashValue == rhs.hashValue
}
private let _make: () -> AnyHashable
private let hashClosure:() -> Int
var hashValue: Int {
return hashClosure()
}
init<T: AdjustmentProtocol & Hashable>(_ adjustment: T) where T.A == A {
_make = adjustment.make
hashClosure = { return adjustment.hashValue }
}
func make() -> AnyHashable {
return _make()
}
}
struct Brightness: AdjustmentProtocol, Hashable {
func make() -> AnyHashable {
return AdjustmentTypes.FloatType(name: "Brightness", defaultValue: 0, min: 0, max: 1)
}
}
struct WhiteBalance: AdjustmentProtocol, Hashable {
func make() -> AnyHashable {
return AdjustmentTypes.IntType(name: "White Balance", defaultValue: 4000, min: 3000, max: 7000)
}
}
let brightness = Brightness().make()
let whiteBalance = WhiteBalance().make()
var orderedSet = Set<AnyHashable>()
orderedSet.insert(brightness)
print(type(of: orderedSet))
print(orderedSet.contains(brightness))
for obj in orderedSet {
if let o = obj as? AdjustmentTypes.FloatType {
print(o.value)
}
if let o = obj as? AdjustmentTypes.IntType {
print(o.value)
}
}
Prints:
Set<AnyHashable>
true
0.0
Special thanks to this article: https://medium.com/#chris_dus/type-erasure-in-swift-84480c807534 which had a simple and clean example on how to implement a generic type eraser.

With Swift 5.7 you will be able to this without any error from the compiler by prefixing your protocol with any, so your set becomes:
struct AdjustmentSet {
var adjustmentSet: [any Adjustable] = []
func contains(_ item: some Adjustable) -> Bool {
return adjustmentSet.first { $0.id == item.id } != nil
}
}
Note that all items in your adjustmentSet array will be allocated on heap since compile time swift can't determine the size of existential type Adjustable as types implementing it will have variable size.

Related

Infer return type from function using switch control flow

I've got a set of properties/protocols (back story is here, but I think it's superfluous)
The class types look like this:
struct AdjustmentTypes {
internal class BaseType<T>: Hashable {
static func == (lhs: AdjustmentTypes.BaseType<T>, rhs: AdjustmentTypes.BaseType<T>) -> Bool {
return lhs.name == rhs.name
}
typealias A = T
var hashValue: Int { return name.hashValue }
let name: String
let defaultValue: T
let min: T
let max: T
var value: T
init(name: String, defaultValue: T, min: T, max: T) {
self.name = name
self.defaultValue = defaultValue
self.min = min
self.max = max
self.value = defaultValue
}
}
class FloatType: BaseType<CGFloat> { }
class IntType: BaseType<Int> { }
}
And I'm using type erasure to remove the type so I can store these in a Set, and I've built some helper methods to make my Set tooling simpler:
class AdjustmentsSet {
private var adjustmentsSet: Set<AnyHashable> = []
func insert(_ adjustment: AnyHashable) {
adjustmentsSet.insert(adjustment)
}
func remove(_ adjustment: AnyHashable) {
adjustmentsSet.remove(adjustment)
}
func contains(_ adjustment: AnyHashable) -> Bool {
return adjustmentsSet.contains(adjustment)
}
var count: Int { return adjustmentsSet.count }
}
var adjustmentsSet = AdjustmentsSet()
What I want to do now is add some helpers to my Set management class to be able to retrieve a property with the correct type, e.g. if I do:
let brightness = Brightness().make()
adjustments.get(brightness)
It should return nil, but if I do:
adjustments.insert(brightness)
adjustments.get(brightness)
I should now get the value back, as its correct type, AdjustmentTypes.FloatType.
I'm thinking to so something with a Switch statement like this:
class AdjustmentsSet {
// ...
func get(_ adjustment: AnyHashable) -> Any? {
guard let untyped = adjustmentsSet.first(where: { $0 == adjustment }) else { return nil }
switch adjustment {
case _ as AdjustmentTypes.FloatType: return untyped as! AdjustmentTypes.FloatType
case _ as AdjustmentTypes.IntType: return untyped as! AdjustmentTypes.IntType
default: return nil
}
}
}
However, the fatal flaw of course, is that this returns Any, instead of the intended type.
How can I infer the return value's type and to return the correct type?
Complete example, just drop this into a playground:
// Generic conforming protocol to AnyHashable
protocol AnyAdjustmentProtocol {
func make() -> AnyHashable
}
protocol AdjustmentProtocol: AnyAdjustmentProtocol {
associatedtype A
func make() -> A
}
struct AdjustmentTypes {
internal class BaseType<T>: Hashable {
static func == (lhs: AdjustmentTypes.BaseType<T>, rhs: AdjustmentTypes.BaseType<T>) -> Bool {
return lhs.name == rhs.name
}
typealias A = T
var hashValue: Int { return name.hashValue }
let name: String
let defaultValue: T
let min: T
let max: T
var value: T
init(name: String, defaultValue: T, min: T, max: T) {
self.name = name
self.defaultValue = defaultValue
self.min = min
self.max = max
self.value = defaultValue
}
}
class FloatType: BaseType<CGFloat> { }
class IntType: BaseType<Int> { }
}
struct AnyAdjustmentType<A>: AdjustmentProtocol, Hashable {
static func == (lhs: AnyAdjustmentType<A>, rhs: AnyAdjustmentType<A>) -> Bool {
return lhs.hashValue == rhs.hashValue
}
private let _make: () -> AnyHashable
private let hashClosure:() -> Int
var hashValue: Int {
return hashClosure()
}
init<T: AdjustmentProtocol & Hashable>(_ adjustment: T) where T.A == A {
_make = adjustment.make
hashClosure = { return adjustment.hashValue }
}
func make() -> AnyHashable {
return _make()
}
}
struct Brightness: AdjustmentProtocol, Hashable {
func make() -> AnyHashable {
return AdjustmentTypes.FloatType(name: "Brightness", defaultValue: 0, min: 0, max: 1)
}
}
struct WhiteBalance: AdjustmentProtocol, Hashable {
func make() -> AnyHashable {
return AdjustmentTypes.IntType(name: "White Balance", defaultValue: 4000, min: 3000, max: 7000)
}
}
let brightness = Brightness().make()
let whiteBalance = WhiteBalance().make()
class AdjustmentsSet {
private var adjustmentsSet: Set<AnyHashable> = []
func insert(_ adjustment: AnyHashable) {
adjustmentsSet.insert(adjustment)
}
func remove(_ adjustment: AnyHashable) {
adjustmentsSet.remove(adjustment)
}
func contains(_ adjustment: AnyHashable) -> Bool {
return adjustmentsSet.contains(adjustment)
}
var count: Int { return adjustmentsSet.count }
}
var adjustmentsSet = AdjustmentsSet()
You need to rewrite the method as a generic and call it with sufficient type information, i.e. you need to know in advance what type you expect the method to return.
I'm also not sure passing around AnyHashables is ideal anyway. Nothing stops you from adding Strings, Ints and other random types that are hashable to your adjustment set.
var adjustmentsSet = AdjustmentsSet()
adjustmentsSet.insert("1") // compiles just fine!
Alternatively, you could use and pass around your AdjustmentTypes and rewrite the AdjustmentsSet class with generic methods:
class AdjustmentsSet {
private var adjustmentsSet: Set<AnyHashable> = []
func insert<T>(_ adjustment: AdjustmentTypes.BaseType<T>) {
adjustmentsSet.insert(adjustment)
}
func remove<T>(_ adjustment: AdjustmentTypes.BaseType<T>) {
adjustmentsSet.remove(adjustment)
}
func contains<T>(_ adjustment: AdjustmentTypes.BaseType<T>) -> Bool {
return adjustmentsSet.contains(adjustment)
}
func get<T>(_ adjustment: AdjustmentTypes.BaseType<T>) -> AdjustmentTypes.BaseType<T>? {
return (adjustmentsSet.compactMap { $0 as? AdjustmentTypes.BaseType<T> }).first(where: { $0 == adjustment })
}
var count: Int { return adjustmentsSet.count }
}
Next, your make() methods should be more strongly typed as well, since you are not passing around AnyHashables. I implemented brightness and white balance like this:
extension AdjustmentTypes {
static let Brightness = AdjustmentTypes.FloatType(name: "Brightness", defaultValue: 0, min: 0, max: 1)
static let WhiteBalance = AdjustmentTypes.IntType(name: "White Balance", defaultValue: 4000, min: 3000, max: 7000)
}
And also took advantage of type aliases and structs in Swift, to make your adjustment type system behave with value semantics:
struct AdjustmentTypes {
struct BaseType<T>: Hashable {
static func == (lhs: AdjustmentTypes.BaseType<T>, rhs: AdjustmentTypes.BaseType<T>) -> Bool {
return lhs.name == rhs.name
}
typealias A = T
var hashValue: Int { return name.hashValue }
let name: String
let defaultValue: T
let min: T
let max: T
var value: T
init(name: String, defaultValue: T, min: T, max: T) {
self.name = name
self.defaultValue = defaultValue
self.min = min
self.max = max
self.value = defaultValue
}
}
typealias FloatType = BaseType<CGFloat>
typealias IntType = BaseType<Int>
}
Finally, you are able to use the adjustment set as intended:
var brightness = AdjustmentTypes.Brightness
brightness.value = 0.5
var adjustmentsSet = AdjustmentsSet()
adjustmentsSet.insert(brightness)
let retrievedBrightness = adjustmentsSet.get(AdjustmentTypes.Brightness)! // strongly typed!
retrievedBrightness.value // 0.5
AdjustmentTypes.Brightness.value // 0.0
The entire playground:
struct AdjustmentTypes {
struct BaseType<T>: Hashable {
static func == (lhs: AdjustmentTypes.BaseType<T>, rhs: AdjustmentTypes.BaseType<T>) -> Bool {
return lhs.name == rhs.name
}
typealias A = T
var hashValue: Int { return name.hashValue }
let name: String
let defaultValue: T
let min: T
let max: T
var value: T
init(name: String, defaultValue: T, min: T, max: T) {
self.name = name
self.defaultValue = defaultValue
self.min = min
self.max = max
self.value = defaultValue
}
}
typealias FloatType = BaseType<CGFloat>
typealias IntType = BaseType<Int>
}
extension AdjustmentTypes {
static let Brightness = AdjustmentTypes.FloatType(name: "Brightness", defaultValue: 0, min: 0, max: 1)
static let WhiteBalance = AdjustmentTypes.IntType(name: "White Balance", defaultValue: 4000, min: 3000, max: 7000)
}
class AdjustmentsSet {
private var adjustmentsSet: Set<AnyHashable> = []
func insert<T>(_ adjustment: AdjustmentTypes.BaseType<T>) {
adjustmentsSet.insert(adjustment)
}
func remove<T>(_ adjustment: AdjustmentTypes.BaseType<T>) {
adjustmentsSet.remove(adjustment)
}
func contains<T>(_ adjustment: AdjustmentTypes.BaseType<T>) -> Bool {
return adjustmentsSet.contains(adjustment)
}
func get<T>(_ adjustment: AdjustmentTypes.BaseType<T>) -> AdjustmentTypes.BaseType<T>? {
return (adjustmentsSet.compactMap { $0 as? AdjustmentTypes.BaseType<T> }).first(where: { $0 == adjustment })
}
var count: Int { return adjustmentsSet.count }
}
var brightness = AdjustmentTypes.Brightness
brightness.value = 0.5
var adjustmentsSet = AdjustmentsSet()
adjustmentsSet.insert(brightness)
let retrievedBrightness = adjustmentsSet.get(AdjustmentTypes.Brightness)! // strongly typed!
retrievedBrightness.value // 0.5
AdjustmentTypes.Brightness.value // 0.0
Hope this helps, good luck with your project!

Swift type erasure protocol with required init method

I have the protocol that conform Equatable
protocol TestProtocol: Equatable {
var id: Int {get}
}
func ==<T: TestProtocol>(lhs: T, rhs: T) -> Bool {
return lhs.id == rhs.id
}
To have opportunities to store TestProtocol value we should use type erasure.
class AnyTestProtocol<T: TestProtocol>: TestProtocol {
var id: Int {
return item.id
}
private let item: T
init(_ testProtocol: T) {
self.item = testProtocol
}
}
And, after all, we can use it, like this
struct TestStruct: TestProtocol {
let id: Int
}
let a = TestStruct(id: 1)
let b = TestStruct(id: 1)
a == b /// true
// let a = TestStruct(id: 1)
// let b = TestStruct(id: 0)
// a == b /// false
And all ok. But I want to use TestProtocol with required init method, such as init(id: Int).
How can I implement AnyTestProtocol If TestProtocol contains required init method?
protocol TestProtocol: Equatable {
var id: Int {get}
/// Required init method for every protocol implementation
init(id: Int)
}
func ==<T: TestProtocol>(lhs: T, rhs: T) -> Bool {
return lhs.id == rhs.id
}
class AnyTestProtocol<T: TestProtocol>: TestProtocol {
var id: Int {
return item.id
}
private let item: T
required init(id: Int) {
????????
}
init(_ testProtocol: T) {
self.item = testProtocol
}
}
Just forward the required init(id:) call to T:
class AnyTestProtocol<T: TestProtocol>: TestProtocol {
// ...
required init(id: Int) {
self.item = T(id: id)
}
// ...
}

iOS Swift 3: Comparing One all elements of Array in another array

I have struct model like
struct ModelA {
let text: String
let id: Int
}
extension ModelA: Equatable {}
func ==(lhs: ModelA, rhs: ModelA) -> Bool {
let areEqual = lhs.id == rhs.id
return areEqual
}
i have created arrays of this model
let a1:[ModelA] = [ModelA(text: "10", id: 11), ModelA(text: "11", id: 12)]
let a2:[ModelA] = [ModelA(text: "11", id: 12)]
and having a comparing function
func isEqualArray(first array1: [Any], second array2: [Any]) -> Bool {
let set1 = NSSet(array: array1)
let set2 = NSSet(array: array2)
return set1.isSubset(of: set2 as! Set<AnyHashable>)
}
so when i'm trying to cross check
let flag = isEqualArray(first: a1, second: a2)
print("### \(flag)")
It crashes on function return
What am I doing wrong?
Your struct needs to conform to both equatable and hashable in order to be used in a Set. It seems that you only care about the id, so a simple implementation would be:
struct ModelA {
let text: String
let id: Int
}
extension ModelA: Equatable {
static func ==(lhs: ModelA, rhs: ModelA) -> Bool {
return lhs.id == rhs.id
}
}
extension ModelA: Hashable {
var hashValue: Int {
return id
}
}
Now, you can use Swift sets in your isEqualArray function; you also need to consider which set is smaller since you are using isSubSet(of:):
func isEqualArray(first array1: [AnyHashable], second array2: [AnyHashable]) -> Bool {
let set1: Set<AnyHashable>
let set2: Set<AnyHashable>
if array1.count > array2.count {
set1 = Set(array1)
set2 = Set(array2)
} else {
set1 = Set(array2)
set2 = Set(array1)
}
return set2.isSubset(of: set1)
}
Your code actually determines if one array is a subset of another, not if the arrays are equal, so I am not sure if that is what you want.

Swift Equatable on a protocol

I don't think this can be done but I'll ask anyway. I have a protocol:
protocol X {}
And a class:
class Y:X {}
In the rest of my code I refer to everything using the protocol X. In that code I would like to be able to do something like:
let a:X = ...
let b:X = ...
if a == b {...}
The problem is that if I try to implement Equatable:
protocol X: Equatable {}
func ==(lhs:X, rhs:X) -> Bool {
if let l = lhs as? Y, let r = hrs as? Y {
return l.something == r.something
}
return false
}
The idea to try and allow the use of == whilst hiding the implementations behind the protocol.
Swift doesn't like this though because Equatable has Self references and it will no longer allow me to use it as a type. Only as a generic argument.
So has anyone found a way to apply an operator to a protocol without the protocol becoming unusable as a type?
If you directly implement Equatable on a protocol, it will not longer be usable as a type, which defeats the purpose of using a protocol. Even if you just implement == functions on protocols without Equatable conformance, results can be erroneous. See this post on my blog for a demonstration of these issues:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-one/
The approach that I have found to work best is to use type erasure. This allows making == comparisons for protocol types (wrapped in type erasers). It is important to note that while we continue to work at the protocol level, the actual == comparisons are delegated to the underlying concrete types to ensure correct results.
I have built a type eraser using your brief example and added some test code at the end. I have added a constant of type String to the protocol and created two conforming types (structs are the easiest for demonstration purposes) to be able to test the various scenarios.
For a detailed explanation of the type erasure methodology used, check out part two of the above blog post:
https://khawerkhaliq.com/blog/swift-protocols-equatable-part-two/
The code below should support the equality comparison that you wanted to implement. You just have to wrap the protocol type in a type eraser instance.
protocol X {
var name: String { get }
func isEqualTo(_ other: X) -> Bool
func asEquatable() -> AnyEquatableX
}
extension X where Self: Equatable {
func isEqualTo(_ other: X) -> Bool {
guard let otherX = other as? Self else { return false }
return self == otherX
}
func asEquatable() -> AnyEquatableX {
return AnyEquatableX(self)
}
}
struct Y: X, Equatable {
let name: String
static func ==(lhs: Y, rhs: Y) -> Bool {
return lhs.name == rhs.name
}
}
struct Z: X, Equatable {
let name: String
static func ==(lhs: Z, rhs: Z) -> Bool {
return lhs.name == rhs.name
}
}
struct AnyEquatableX: X, Equatable {
var name: String { return value.name }
init(_ value: X) { self.value = value }
private let value: X
static func ==(lhs: AnyEquatableX, rhs: AnyEquatableX) -> Bool {
return lhs.value.isEqualTo(rhs.value)
}
}
// instances typed as the protocol
let y: X = Y(name: "My name")
let z: X = Z(name: "My name")
let equalY: X = Y(name: "My name")
let unequalY: X = Y(name: "Your name")
// equality tests
print(y.asEquatable() == z.asEquatable()) // prints false
print(y.asEquatable() == equalY.asEquatable()) // prints true
print(y.asEquatable() == unequalY.asEquatable()) // prints false
Note that since the type eraser conforms to the protocol, you can use instances of the type eraser anywhere an instance of the protocol type is expected.
Hope this helps.
The reason why you should think twice about having a protocol conform to Equatable is that in many cases it just doesn't make sense. Consider this example:
protocol Pet: Equatable {
var age: Int { get }
}
extension Pet {
static func == (lhs: Pet, rhs: Pet) -> Bool {
return lhs.age == rhs.age
}
}
struct Dog: Pet {
let age: Int
let favoriteFood: String
}
struct Cat: Pet {
let age: Int
let favoriteLitter: String
}
let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")
if rover == simba {
print("Should this be true??")
}
You allude to type checking within the implementation of == but the problem is that you have no information about either of the types beyond them being Pets and you don't know all the things that might be a Pet (maybe you will add a Bird and Rabbit later). If you really need this, another approach can be modeling how languages like C# implement equality, by doing something like:
protocol IsEqual {
func isEqualTo(_ object: Any) -> Bool
}
protocol Pet: IsEqual {
var age: Int { get }
}
struct Dog: Pet {
let age: Int
let favoriteFood: String
func isEqualTo(_ object: Any) -> Bool {
guard let otherDog = object as? Dog else { return false }
return age == otherDog.age && favoriteFood == otherDog.favoriteFood
}
}
struct Cat: Pet {
let age: Int
let favoriteLitter: String
func isEqualTo(_ object: Any) -> Bool {
guard let otherCat = object as? Cat else { return false }
return age == otherCat.age && favoriteLitter == otherCat.favoriteLitter
}
}
let rover: Pet = Dog(age: "1", favoriteFood: "Pizza")
let simba: Pet = Cat(age: "1", favoriteLitter: "Purina")
if !rover.isEqualTo(simba) {
print("That's more like it.")
}
At which point if you really wanted, you could implement == without implementing Equatable:
static func == (lhs: IsEqual, rhs: IsEqual) -> Bool { return lhs.isEqualTo(rhs) }
One thing you would have to watch out for in this case is inheritance though. Because you could downcast an inheriting type and erase the information that might make isEqualTo not make logical sense.
The best way to go though is to only implement equality on the class/struct themselves and use another mechanism for type checking.
Determining equality across conformances to a Swift protocol is possible without type erasure if:
you are willing to forgo the operator syntax (i.e. call isEqual(to:) instead of ==)
you control the protocol (so you can add an isEqual(to:) func to it)
import XCTest
protocol Shape {
func isEqual (to: Shape) -> Bool
}
extension Shape where Self : Equatable {
func isEqual (to: Shape) -> Bool {
return (to as? Self).flatMap({ $0 == self }) ?? false
}
}
struct Circle : Shape, Equatable {
let radius: Double
}
struct Square : Shape, Equatable {
let edge: Double
}
class ProtocolConformanceEquality: XCTestCase {
func test() {
// Does the right thing for same type
XCTAssertTrue(Circle(radius: 1).isEqual(to: Circle(radius: 1)))
XCTAssertFalse(Circle(radius: 1).isEqual(to: Circle(radius: 2)))
// Does the right thing for different types
XCTAssertFalse(Square(edge: 1).isEqual(to: Circle(radius: 1)))
}
}
Any conformances don't conform to Equatable will need to implement isEqual(to:) themselves
maybe this will be helpful for you:
protocol X:Equatable {
var name: String {get set}
}
extension X {
static func ==(lhs: Self, rhs: Self) -> Bool {
return lhs.name == rhs.name
}
}
struct Test : X {
var name: String
}
let first = Test(name: "Test1")
let second = Test(name: "Test2")
print(first == second) // false
All people who say that you can't implement Equatable for a protocol just don't try hard enough. Here is the solution (Swift 4.1) for your protocol X example:
protocol X: Equatable {
var something: Int { get }
}
// Define this operator in the global scope!
func ==<L: X, R: X>(l: L, r: R) -> Bool {
return l.something == r.something
}
And it works!
class Y: X {
var something: Int = 14
}
struct Z: X {
let something: Int = 9
}
let y = Y()
let z = Z()
print(y == z) // false
y.something = z.something
print(y == z) // true
The only problem is that you can't write let a: X = Y() because of "Protocol can only be used as a generic constraint" error.
Not sure why you need all instances of your protocol to conform to Equatable, but I prefer letting classes implement their equality methods.
In this case, I'd leave the protocol simple:
protocol MyProtocol {
func doSomething()
}
If you require that an object that conforms to MyProtocol is also Equatable you can use MyProtocol & Equatable as type constraint:
// Equivalent: func doSomething<T>(element1: T, element2: T) where T: MyProtocol & Equatable {
func doSomething<T: MyProtocol & Equatable>(element1: T, element2: T) {
if element1 == element2 {
element1.doSomething()
}
}
This way you can keep your specification clear and let subclasses implement their equality method only if required.
I would still advise against implementing == using polymorphism. It's a bit of a code smell. If you want to give the framework user something he can test equality with then you should really be vending a struct, not a protocol. That's not to say that it can't be the protocols that are vending the structs though:
struct Info: Equatable {
let a: Int
let b: String
static func == (lhs: Info, rhs: Info) -> Bool {
return lhs.a == rhs.a && lhs.b == rhs.b
}
}
protocol HasInfo {
var info: Info { get }
}
class FirstClass: HasInfo {
/* ... */
}
class SecondClass: HasInfo {
/* ... */
}
let x: HasInfo = FirstClass( /* ... */ )
let y: HasInfo = SecondClass( /* ... */ )
print(x == y) // nope
print(x.info == y.info) // yep
I think this more efficiently communicates your intent, which is basically "you have these things and you don't know if they are the same things, but you do know they have the same set of properties and you can test whether those properties are the same." That is pretty close to how I would implement that Money example.
You have to implement a protocol extension constrained to your class type. Inside that extension you should implement the Equatable operator.
public protocol Protocolable: class, Equatable
{
// Other stuff here...
}
public extension Protocolable where Self: TheClass
{
public static func ==(lhs: Self, rhs:Self) -> Bool
{
return lhs.name == rhs.name
}
}
public class TheClass: Protocolable
{
public var name: String
public init(named name: String)
{
self.name = name
}
}
let aClass: TheClass = TheClass(named: "Cars")
let otherClass: TheClass = TheClass(named: "Wall-E")
if aClass == otherClass
{
print("Equals")
}
else
{
print("Non Equals")
}
But let me recommend you add the operator implementation to your class. Keep It Simple ;-)
Swift 5.1 introduces a new feature into the language called opaque types
Check code below
that still gets back a X, which might be an Y, a Z, or something else that conforms to the X protocol,
but the compiler knows exactly what is being returned
protocol X: Equatable { }
class Y: X {
var something = 3
static func == (lhs: Y, rhs: Y) -> Bool {
return lhs.something == rhs.something
}
static func make() -> some X {
return Y()
}
}
class Z: X {
var something = "5"
static func == (lhs: Z, rhs: Z) -> Bool {
return lhs.something == rhs.something
}
static func make() -> some X {
return Z()
}
}
let a = Z.make()
let b = Z.make()
a == b
I came cross this same issue and I figured that the == operator can be implemented in the global scope (as it used to be), as opposed to a static func inside the protocol's scope:
// This should go in the global scope
public func == (lhs: MyProtocol?, rhs: MyProtocol?) -> Bool { return lhs?.id == rhs?.id }
public func != (lhs: MyProtocol?, rhs: MyProtocol?) -> Bool { return lhs?.id != rhs?.id }
Note that if you use linters such as SwiftLint's static_operator, you'll have to wrap that code around // swiftlint:disable static_operator to silent linter warnings.
Then this code will start compiling:
let obj1: MyProtocol = ConcreteType(id: "1")
let obj2: MyProtocol = ConcreteType(id: "2")
if obj1 == obj2 {
print("They're equal.")
} else {
print("They're not equal.")
}
took some of the code from above and came with the following sollution.
it uses the IsEqual protocol instead of the Equatable protocol and with a few line codes you will be able to compare any two protocol objects with each other, wether they are optional or not, are in an array and even add comparing dates while I was at it.
protocol IsEqual {
func isEqualTo(_ object: Any) -> Bool
}
func == (lhs: IsEqual?, rhs: IsEqual?) -> Bool {
guard let lhs = lhs else { return rhs == nil }
guard let rhs = rhs else { return false }
return lhs.isEqualTo(rhs) }
func == (lhs: [IsEqual]?, rhs: [IsEqual]?) -> Bool {
guard let lhs = lhs else { return rhs == nil }
guard let rhs = rhs else { return false }
guard lhs.count == rhs.count else { return false }
for i in 0..<lhs.count {
if !lhs[i].isEqualTo(rhs[i]) {
return false
}
}
return true
}
func == (lhs: Date?, rhs: Date?) -> Bool {
guard let lhs = lhs else { return rhs == nil }
guard let rhs = rhs else { return false }
return lhs.compare(rhs) == .orderedSame
}
protocol Pet: IsEqual {
var age: Int { get }
}
struct Dog: Pet {
let age: Int
let favoriteFood: String
func isEqualTo(_ object: Any) -> Bool {
guard let otherDog = object as? Dog else { return false }
return age == otherDog.age && favoriteFood == otherDog.favoriteFood
}
}

Array.contains returns false

I have an array of users [User] called conversationUsers
User is defined as
public class User: NSManagedObject { ... }
and currUser is an User object
if I try
currUser == conversationUsers[0]
it results true
but
conversationUsers.contains(currUser)
results false
If instead I use
conversationUsers.contains({$0 == currUser})
it returns true
FYI I have also defined this:
public func ==(lhs: User, rhs: User) -> Bool {
let logicalExpression = lhs.email.lowercaseString == rhs.email.lowercaseString
return logicalExpression
}
Why contains returns false? Which is his default predicate?
Thank you for your help
UPDATE
In short, you should override NSObject.isEqual() method:
public class User: NSManagedObject {
...
override func isEqual(object: AnyObject?) -> Bool {
if let rhs = object as? User {
return self == rhs
}
return false
}
}
I made a little research. It seems that if class A is inherited from NSObject then collection methods like contains use NSObject.isEqual() method for determining equality of two objects. Class B is not inherited from NSObject so contains method is working as expected.
import Foundation
class A: NSObject {
var value: Int
init(_ value: Int) {
self.value = value
}
}
func ==(lhs: A, rhs: A) -> Bool {
return lhs.value == rhs.value
}
var a = A(1)
var array: [A] = [a, A(2)]
array.contains(a) // true
array.contains(A(1)) // false
array.contains { $0 == A(1) } // true
array[0] == A(1) // true
class B: Equatable {
var value: Int
init(_ value: Int) {
self.value = value
}
}
func ==(lhs: B, rhs: B) -> Bool {
return lhs.value == rhs.value
}
var b = B(1)
var array2: [B] = [b, B(2)]
array2.contains(b) // true
array2.contains(B(1)) // true
array2.contains { $0 == B(1) } // true
array2[0] == B(1) // true
So you need to override NSObject.isEqual() method:
class A: NSObject {
var value: Int
init(_ value: Int) {
self.value = value
}
override func isEqual(object: AnyObject?) -> Bool {
if let rhs = object as? A {
return self == rhs
}
return false
}
}
OLD ANSWER
User class should also conform to Equatable protocol:
extension User: Equatable {}
Or you can add it directly to class definition:
public class User: NSManagedObject, Equatable {}

Resources