Swift: How to compare two Dictionary<String, Any> structs? - ios

I'm using the Static framework written in Swift that uses typealias Context = [String: Any] for storing additional information for a row.
Now, I need to compare if two Context structs are equal in an extension Row:
public func ==(lhs: Row, rhs: Row) -> Bool {
// TODO: image, ...
return lhs.text == rhs.text
&& lhs.detailText == rhs.detailText
&& lhs.accessory == rhs.accessory
&& lhs.context == rhs.context
}
So I implement the == operator for Row.Context:
public func ==(lhs: Row.Context, rhs: Row.Context) -> Bool {
return Array(lhs.keys) == Array(rhs.keys)
// TODO: && Array(lhs.values) == Array(rhs.values)
}
but now, in order to lhs.context == rhs.context I need to extend the Row.Context type to conform to Equatable:
extension Row.Context: Equatable {}
Which is exactly where I'm stuck with the following error:
"Constrained extension must be declared on the unspecialized generic
type 'Dictionary' with constraints specified by a 'where' clause"
Ignoring the implementation of func == for Row.Context for now, how to make Row.Context conform to Equatable?

Related

Safe and elegant way to delete element in custom array, Swift [duplicate]

This question already has answers here:
Swift: Better way to remove a specific Object from an array?
(5 answers)
Closed 2 years ago.
I have this custom array:
var customArray = [CustomItem]()
Custom item is:
class CustomItem {
let firstItem: Enumeration
let data: Any
// ...
}
Enumeration contains an enumaration with different case. For example case example1, case example2 etc.
I add element with append with all the info like firstItem (enumeration etc).
What I need is to check if in my customArray I have a given item in my enumeration. Let's say I have to check if in customArray the enumeration .example1 exist (because I append it before) and in case delete it.
What is the safest and most elegant way to perform this?
Extra: what If i would like to add one element at the end of this custom arrray?
if you want find a specific item index you can use firstIndex(of: )
like this:
let index = customArray.firstIndex(of: CustomItem)
customArray.remove(at: index)
EDIT:
You have to make your object Equatable with an extension:
enter codextension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
} }
Now you can compare the objects with each other.
this is my code from playground:
enum Enumeration {
case opt1
case op2
}
struct CustomItem {
var firstItem: Enumeration
let data: Any
}
extension CustomItem: Equatable {
static func ==(lhs: CustomItem, rhs: CustomItem) -> Bool {
return lhs.firstItem == rhs.firstItem
}
}
class ViewController: UIViewController {
var customArray = [CustomItem]()
func searchAndDestory() {
let index = customArray.firstIndex(of: CustomItem.init(firstItem: .op2, data: 0)) ?? 0
customArray.remove(at: index)
}
}

~= operator in Swift

I recently downloaded the Advanced NSOperations sample app from Apple and found this code...
// Operators to use in the switch statement.
private func ~=(lhs: (String, Int, String?), rhs: (String, Int, String?)) -> Bool {
return lhs.0 ~= rhs.0 && lhs.1 ~= rhs.1 && lhs.2 == rhs.2
}
private func ~=(lhs: (String, OperationErrorCode, String), rhs: (String, Int, String?)) -> Bool {
return lhs.0 ~= rhs.0 && lhs.1.rawValue ~= rhs.1 && lhs.2 == rhs.2
}
It seems to use the ~= operator against Strings and Ints but I've never seen it before.
What is it?
Simply use as a shortcut to "range": you can construct a range and "~=" means "contains".
(other can add more theoretical details, but the sense is this). Read it as "contains"
let n: Int = 100
// verify if n is in a range, say: 10 to 100 (included)
if n>=10 && n<=100 {
print("inside!")
}
// using "patterns"
if 10...100 ~= n {
print("inside! (using patterns)")
}
try with some values of n.
Is used widely for example in HTTP response:
if let response = response as? HTTPURLResponse , 200...299 ~= response.statusCode {
let contentLength : Int64 = response.expectedContentLength
completionHandler(contentLength)
} else {
completionHandler(nil)
It is an operator used for pattern matching in a case statement.
You can take a look here to know how you can use and leverage it providing your own implementation:
http://oleb.net/blog/2015/09/swift-pattern-matching/
http://austinzheng.com/2014/12/17/custom-pattern-matching/
Here is a simple example of defining a custom one and using it:
struct Person {
let name : String
}
// Function that should return true if value matches against pattern
func ~=(pattern: String, value: Person) -> Bool {
return value.name == pattern
}
let p = Person(name: "Alessandro")
switch p {
// This will call our custom ~= implementation, all done through type inference
case "Alessandro":
print("Hey it's me!")
default:
print("Not me")
}
// Output: "Hey it's me!"
if case "Alessandro" = p {
print("It's still me!")
}
// Output: "It's still me!"
You can look into Define Swift
func ~=<I : IntervalType>(pattern: I, value: I.Bound) -> Bool
func ~=<T>(lhs: _OptionalNilComparisonType, rhs: T?) -> Bool
func ~=<T : Equatable>(a: T, b: T) -> Bool
func ~=<I : ForwardIndexType where I : Comparable>(pattern: Range<I>, value: I) -> Bool

Cannot invoke 'indexOf' with an argument list of type '(ChecklistItem)'

When I am writing code for finding an item from the array with the use of indexOf it shows me the above stated error.
Here is my code:-
func addItemViewController(controller: AddItemViewController, didFinishEditingItem item: ChecklistItem)
{
if let index = items.indexOf(item)
{
let indexPath = NSIndexPath(forRow: index, inSection: 0)
if let cell = tableView.cellForRowAtIndexPath(indexPath)
{
configureTextForCell(cell, withChecklistItem: item)
}
}
In order to use indexOf the ChecklistItem must adopt Equatable protocol. Only by adopting this protocol the list can compare an item with other items to find the desired index
indexOf can only be applied to Collections of Equatable types, your ChecklistItem doesn't conform to Equatable protocol (have an == operator).
To be able to use indexOf add this to the file containing ChecklistItem class in the global scope :
func ==(lhs: ChecklistItem, rhs: ChecklistItem) -> Bool {
return lhs === rhs
}
Swift3:
public static func ==(lhs: Place, rhs: Place) -> Bool {
return lhs === rhs
}
Please note it will make comparison by comparing instances addresses in memory. You may want to check equality by comparing members of the class instead.
In Swift 4 and Swift 3, update your Data Model to conform to "Equatable" protocol, and implement the lhs=rhs method , only then you can use ".index(of:...)", because you are comparing your custom object
Eg:
class Photo : Equatable{
var imageURL: URL?
init(imageURL: URL){
self.imageURL = imageURL
}
static func == (lhs: Photo, rhs: Photo) -> Bool{
return lhs.imageURL == rhs.imageURL
}
}
Usage:
let index = self.photos.index(of: aPhoto)
I realize this question already has an accepted answer, but I found another case that will cause this error so it might help someone else. I'm using Swift 3.
If you create a collection and allow the type to be inferred you may also see this error.
Example:
// UITextfield conforms to the 'Equatable' protocol, but if you create an
// array of UITextfields and leave the explicit type off you will
// also see this error when trying to find the index as below
let fields = [
tf_username,
tf_email,
tf_firstname,
tf_lastname,
tf_password,
tf_password2
]
// you will see the error here
let index = fields.index(of: textField)
// to fix this issue update your array declaration with an explicit type
let fields:[UITextField] = [
// fields here
]
The possible reason is you didn't tell the ChecklistItem type that it is equatable, maybe you forgot to mention ChecklistItem class is inherited from NSObject.
import Foundation
class ChecklistItem: NSObject {
var text = ""
var checked = false
func toggleChecked() {
checked = !checked
}
}
NSObject adopts or conforms to the equatable protocol

RLMObjects not recognised as the same in Realm Cocoa

I have a tableView containing RLMObjects and want to search for the row containing a specific RLMObject.
After casting the RLMResult object to its original type it is not the same as its original object:
// ... adding a todoA with !isCompleted to defaultRealm()
var firstItem = Todo.objectsWhere("isCompleted == false")[0] as! ToDo
if firstItem == todoA {
// todoA is != firstItem even-though they should be the same object
}
How can I compare two RLMObjects without having to implement the primaryKey allocation?
RLMObject does not conform to Swift's Equatable protocol, allowing == and != comparisons. Depending on the equality semantics you want for your objects, you can use the following extension on RLMObject:
extension RLMObject: Equatable {}
func == <T: RLMObject>(lhs: T, rhs: T) -> Bool {
return lhs.isEqualToObject(rhs)
}

Equatable implementation doesn't seem working with generics

I am still fighting with Swift generics. Today I discovered that my implementation of Equatable protocol doesn't work, if it's called from generic class.
My model class:
func ==(lhs: Tracking, rhs: Tracking) -> Bool {
// This method never executes if called from BaseCache
return lhs.id == rhs.id
}
class Tracking: NSObject, Equatable, Printable {
var id: String?
.....
}
Class, which uses generic type:
class BaseCache<T: NSObject where T: Equatable, T: Printable> {
.....
func removeEntities(entities: [T]) {
var indexesToRemove = [Int]()
for i in 0...allEntities.count - 1 {
let item = allEntities[i]
for entity in entities {
println("equal: \(entity == item)")
// FOR SOME REASONS THE STATEMENT BELOW IS ALWAYS FALSE
if entity == item {
indexesToRemove.append(i)
break
}
}
}
for index in indexesToRemove {
allEntities.removeAtIndex(index)
}
didRemoveEntities()
}
}
and it's subclass:
class TrackingCache<T: Tracking>: BaseCache<Tracking> {
}
When I call removeEntities method of TrackingCache instance, I always get equal: false in the output, even if ids are the same.
But if I move the method to TrackingCache class directly, it seems working fine!
Any ideas why this is happening and how to fix this?
Beware: since == is not a member function, it will not give you dynamic dispatch by default, including if you use it alongside a generic placeholder.
Consider the following code:
class C: NSObject, Equatable {
let id: Int
init(_ id: Int) { self.id = id }
}
// define equality as IDs are equal
func ==(lhs: C, rhs: C) -> Bool {
return lhs.id == rhs.id
}
// create two objects with the same ID
let c1 = C(1)
let c2 = C(1)
// true, as expected
c1 == c2
OK now create two variables of type NSObject, and assign the same values to them:
let o1: NSObject = c1
let o2: NSObject = c2
// this will be false
o1 == o2
Why? Because you are invoking the function func ==(lhs: NSObject, rhs: NSObject) -> Bool, not func ==(lhs: C, rhs: C) -> Bool. Which overloaded function to choose is not determined dynamically at runtime based on what o1 and o2 refer to. It is determined by Swift at compile time, based on the types of o1 and o2, which in this case is NSObject.
NSObject == is implemented differently to your equals – it calls lhs.isEqual(rhs), which falls back if not overridden to checking reference equality (i.e. are the two references pointing at the same object). They aren’t, so they aren’t equal.
Why does this happen with BaseCache but not with TrackingCache? Because BaseCache is defined as constraining only to NSObject, so T only has the capabilities of an NSObject – similar to when you assigned c1 to a variable of type NSObject, the NSObject version of == will be called.
TrackingCache on the other hand guarantees T will be at least a Tracking object, so the version of == for tracking is used. Swift will pick the more "specific" of all possible overloads – Tracking is more specific than it's base class, NSObject.
Here’s a simpler example, just with generic functions:
func f<T: NSObject>(lhs: T, rhs: T) -> Bool {
return lhs == rhs
}
func g<T: C>(lhs: T, rhs: T) -> Bool {
return lhs == rhs
}
f(c1, c2) // false
g(c1, c2) // true
If you want to fix this, you can override isEqual:
class C: NSObject, Equatable {
...
override func isEqual(object: AnyObject?) -> Bool {
return (object as? C)?.id == id
}
}
// this is now true:
o1 == o2
// as is this:
f(c1, c2)
This technique (having == call a dynamically-dispatched class method) is also a way to implement this behaviour for your non-NSObject classes. Structs, of course, don’t have this issue since they don’t support inheritance – score 1 for structs!

Resources