Hashable == method does not detect difference between two objects swift - ios

I implemented the class below:
class Table : Hashable {
var uid : Int
var timeRemaining : Int?
var currentPrice : Double?
var hashValue: Int {
return uid.hashValue
}
static func ==(lhs: Table, rhs: Table) -> Bool {
return lhs.uid == rhs.uid && lhs.timeRemaining == rhs.timeRemaining && lhs.currentPrice == rhs.currentPrice
}
init (uid: Int, timeRemaining: Int?, currentPrice: Double?) {
self.uid = uid
self.timeRemaining = timeRemaining
self.currentPrice = currentPrice
}
}
I've also defined an array of objects of this class:
private var tables = [Table]()
Next, I have the following method which runs every second:
func updateAuctions() {
let oldItems = tables
let newItems = oldItems
for table in newItems {
let oldPrice = table.currentPrice!
let timeRemaining = table.timeRemaining!
table.currentPrice = oldPrice + 0.50
table.timeRemaining = timeRemaining - 1
}
let changes = diff(old: oldItems, new: newItems)
collectionView.reload(changes: changes, section: 0) { (complete) in
if (complete) {
self.tables = newItems
}
}
}
This uses the DeepDiff framework described here: https://github.com/onmyway133/DeepDiff
My goal is to refresh the UICollectionView with the changes made to the tables array, however no changes are detected by the framework, even though my == method checks that the timeRemaining and currentPrice match.

let newItems = oldItems
Since both array contain object instances, wouldn't they just point to the same objects? So when you iterate through newItems and changing values, you are essentially changing values of oldItems too. You can verify this by printing the values of both array after the for loop.
Maybe you can try something similar to the following?
func updateAuctions() {
let oldItems = tables
let newItems = [Table]()
for item in oldItems {
newItems.append(Table(uid: item.uid, timeRemaining: item.timeRemaining! - 1, currentPrice: item.currentPrice! + 0.50))
}
let changes = diff(old: oldItems, new: newItems)
collectionView.reload(changes: changes, section: 0) { (complete) in
if (complete) {
self.tables = newItems
}
}
}

Related

How to remove duplicate items from an array with multiple criteria?

Objective: I have to delete object from array which has same title, vicinity, coordinate.latitude and coordinate.longitude
class Place {
var placeID: String?
var title: String?
var vicinity: String?
var detailsUrl: String?
var openingHours: OpeningHours?
var position: [Double]
var coordinate: CLLocationCoordinate2D {
return CLLocationCoordinate2DMake(position.first ?? 0, position.last ?? 0)
}
One way i have tried that -
extension Array {
func removingDuplicates <T: Hashable>(byKey key: (Element) -> T) -> [Element] {
var result = [Element]()
var seen = Set<T>()
for value in self {
if seen.insert(key(value)).inserted {
result.append(value)
}
}
return result
}
}
let array = list.removingDuplicates(byKey: { "\($0.coordinate.latitude)" + "\($0.coordinate.longitude)" + ($0.title ?? " ") + ($0.vicinity ?? " ") })
But i really don't like above solution. What is the most appropriate way to handle it ?
Add Equatable to the Place class
class Place: Equatable {
static func == (lhs: Place, rhs: Place) -> Bool {
return lhs.title == rhs.title && lhs.vicinity == rhs.vicinity &&
lhs.coordinate.latitude == rhs.coordinate.latitude && lhs.coordinate.longitude == rhs.coordinate.longitude
}
//...
}
And filter the array elements with the place/places to delete
var list = [Place]()
//Delete multiple places
let placesToDelete = [Place]()
let result = list.removeAll { placesToDelete.contains($0) }
//Delete one place
let placeToDelete = Place()
let result = list.removeAll { $0 == placeToDelete }

Realm add(_, update: true) removes existing relationships

I am facing an issue where I am unable to keep existing relationships after calling add(_, update: true) function.
I wrote a TaskSync class that is responsible for creating/updating Task objects:
class TaskSync: ISync {
typealias Model = Task
func sync(model: Task) {
let realm = try! Realm()
let inWrite = realm.isInWriteTransaction
if !inWrite {
realm.beginWrite()
}
let _task = realm.object(ofType: Task.self, forPrimaryKey: model.id)
// Persist matches as they are not getting fetched with the task
if let _task = _task {
print("matches: \(_task.matches.count)")
model.matches = _task.matches
}
realm.add(model, update: true)
if _task == nil {
var user = realm.object(ofType: User.self, forPrimaryKey: model.getUser().id)
if (user == nil) {
user = model.getUser()
realm.add(user!, update: true)
}
user!.tasks.append(model)
}
if !inWrite {
try! realm.commitWrite()
}
}
func sync(models: List<Task>) {
let realm = try! Realm()
try! realm.write {
models.forEach { task in
sync(model: task)
}
}
}
}
When a model is to be synced, I check if it already exists in the Realm and if so, I fetch it and try to include the matches property as this one is not included in the model.
Right before the call realm.add(model, update: true), model contains list of matches, however right after the realm.add is executed, the matches list is empty.
Here are the two models:
class Task: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var title: String = ""
dynamic var desc: String = ""
dynamic var price: Float = 0.0
dynamic var calculatedPrice: Float = 0.0
dynamic var location: String = ""
dynamic var duration: Int = 0
dynamic var date: String = ""
dynamic var category: Category?
dynamic var currency: Currency?
dynamic var longitude: Double = 0.0
dynamic var latitude: Double = 0.0
dynamic var state: Int = 0
dynamic var userId: Int = 0
// Existing images
var imagesExisting = List<URLImage>()
// New images
var imagesNew = List<Image>()
// Images deleted
var imagesDeleted = List<URLImage>()
private let users = LinkingObjects(fromType: User.self, property: "tasks")
var user: User?
var matches = List<Match>()
dynamic var notification: Notification?
override static func ignoredProperties() -> [String] {
return ["imagesExisting", "imagesNew", "imagesDeleted", "user", "tmpUser"]
}
override static func primaryKey() -> String? {
return "id"
}
func getImageMain() -> URLImage? {
for image in imagesExisting {
if image.main {
return image
}
}
return imagesExisting.first
}
func getSection() -> Int {
return state
}
func getSectionFieldName() -> String? {
return "state"
}
func getId() -> Int {
return id
}
func getURL() -> URL? {
if let image = getImageMain() {
return image.getResizedURL()
}
return nil
}
func getState() -> TaskOwnState {
return TaskOwnState(rawValue: state)!
}
func getUser() -> User {
return (user != nil ? user : users.first)!
}
}
class Match: Object, ElementPreloadable, ElementImagePreloadable, ItemSectionable {
dynamic var id: Int = 0
dynamic var state: Int = -1
dynamic var priorityOwnRaw: Int = 0
dynamic var priorityOtherRaw: Int = 0
dynamic var user: User!
var messages = List<Message>()
private let tasks = LinkingObjects(fromType: Task.self, property: "matches")
var task: Task?
dynamic var notification: Notification?
override static func primaryKey() -> String? {
return "id"
}
override static func ignoredProperties() -> [String] {
return ["task"]
}
func getId() -> Int {
return id
}
func getSection() -> Int {
return 0
}
func getURL() -> URL? {
if let image = user.getImageMain() {
return image.getResizedURL()
}
return nil
}
func getPriorityOwn() -> PriorityType {
if priorityOwnRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getPriorityOther() -> PriorityType {
if priorityOtherRaw == PriorityType.normal.rawValue {
return PriorityType.normal
}
else {
return PriorityType.favorite
}
}
func getSectionFieldName() -> String? {
return nil
}
func getTask() -> Task {
return (task != nil ? task : tasks.first)!
}
}
I spent hours trying to figure out why I am unable to keep the matches relationship when updating the task. Every advice will be highly appreciated!
This question was also asked upon Realm's GitHub issue tracker. For posterity, here is the solution.
List properties should always be declared as let properties, as assigning to them does not do anything useful. The correct way to copy all objects from one List to another is model.tasks.append(objectsIn: _user.tasks).

Unique Objects inside a Array Swift

I have an array, with custom objects.
I Would like to pop the repeated objects, with the repeated properties:
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
in this case, pop the product2 or product3
Here is an Array extension to return the unique list of objects based on a given key:
extension Array {
func unique<T:Hashable>(map: ((Element) -> (T))) -> [Element] {
var set = Set<T>() //the unique list kept in a Set for fast retrieval
var arrayOrdered = [Element]() //keeping the unique list of elements but ordered
for value in self {
if !set.contains(map(value)) {
set.insert(map(value))
arrayOrdered.append(value)
}
}
return arrayOrdered
}
}
using this you can so this
let unique = [product,product2,product3].unique{$0.subCategory}
this has the advantage of not requiring the Hashable and being able to return an unique list based on any field or combination
You can use Swift Set:
let array = [product,product2,product3]
let set = Set(array)
You have to make Product conform to Hashable (and thus, Equatable) though:
class Product : Hashable {
var subCategory = ""
var hashValue: Int { return subCategory.hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.subCategory == rhs.subCategory
}
And, if Product was a NSObject subclass, you have to override isEqual:
override func isEqual(object: AnyObject?) -> Bool {
if let product = object as? Product {
return product == self
} else {
return false
}
}
Clearly, modify those to reflect other properties you might have in your class. For example:
class Product : Hashable {
var category = ""
var subCategory = ""
var hashValue: Int { return [category, subCategory].hashValue }
}
func ==(lhs: Product, rhs: Product) -> Bool {
return lhs.category == rhs.category && lhs.subCategory == rhs.subCategory
}
If Product conforms to Equatable, where a product is equal based on it's subcategory (and you don't care about order), you can add the objects to a set, and take an array from that set:
let array = [product,product2,product3]
let set = NSSet(array: array)
let uniqueArray = set.allObjects
or
let array = [product,product2,product3]
let set = Set(array)
let uniqueArray = Array(set)
If your class conforms to protocol Hashable and you would like to keep the original array order you can create an extension as follow:
extension Array where Element: Hashable {
var uniqueElements: [Element] {
var elements: [Element] = []
for element in self {
if let _ = elements.indexOf(element) {
print("item found")
} else {
print("item not found, add it")
elements.append(element)
}
}
return elements
}
}
class Product {
var subCategory: String = ""
}
let product = Product()
product.subCategory = "one"
let product2 = Product()
product2.subCategory = "two"
let product3 = Product()
product3.subCategory = "two"
let array = [product,product2,product3]
extension Product : Hashable {
var hashValue: Int {
return subCategory.hashValue
}
}
func ==(lhs: Product, rhs: Product)->Bool {
return lhs.subCategory == rhs.subCategory
}
let set = Set(array)
set.forEach { (p) -> () in
print(p, p.subCategory)
}
/*
Product one
Product two
*/
if an item is part of set or not doesn't depends on hashValue, it depends on comparation. if your product conform to Hashable, it should conform to Equatable. if you need that the creation of the set depends solely on subCategory, the comparation should depends solely on subCategory. this can be a big trouble, if you need to compare your products some other way
Here is a KeyPath based version of the Ciprian Rarau' solution
extension Array {
func unique<T: Hashable>(by keyPath: KeyPath<Element, T>) -> [Element] {
var set = Set<T>()
return self.reduce(into: [Element]()) { result, value in
guard !set.contains(value[keyPath: keyPath]) else {
return
}
set.insert(value[keyPath: keyPath])
result.append(value)
}
}
}
example usage:
let unique = [product, product2, product3].unique(by: \.subCategory)

Avoiding duplicating tuples in Swift

In my watchOS2 app I have array of tuples like this:
var medicines = [(String, String?, String?)]()
And in refreshing function i'd like to clear this array of tuples to append it with new String items. How can i do this ? I want to avoid having the same things in my array. Or maybe there is a better idea ?
My refresh function:
let iNeedCoreData = ["Value": "CoreData"]
session.sendMessage(iNeedCoreData, replyHandler: { (content: [String: AnyObject]) -> Void in
if let meds = content["reply"] as? [String: [String]] {
self.medicines = [(String, String?, String?)]()
if let medicineNames = meds["medicines"], amountNames = meds["amount"], timeNames = meds["time"] {
if medicineNames.count != 0 {
self.addMedicines(medicineNames)
self.addQuantities(amountNames)
self.addTime(timeNames)
self.table.setHidden(false)
self.reloadTable()
} else {
self.alertLabel.setHidden(false)
}
}
}
}) { (error) -> Void in
print("We got an error from our watch device:" + error.domain)
}
Adding to tuple funcs:
func reloadTable() {
self.table.setNumberOfRows(medicines.count, withRowType: "tableRowController")
var rowIndex = 0
for item in medicines {
if let row = self.table.rowControllerAtIndex(rowIndex) as? tableRowController {
row.medicineLabel.setText(item.0)
if let quantity = item.1, time = item.2 {
row.amountLabel.setText(quantity)
row.timeLabel.setText(time)
}
rowIndex++
}
}
}
func addMedicines(medicineNames: [String]) {
for name in medicineNames {
medicines.append((name, nil, nil))
}
}
func addQuantities(quantities: [String]) {
guard medicines.count == quantities.count else { return }
for i in 0..<medicines.count {
medicines[i].1 = quantities[i]
}
}
func addTime(timeNames: [String]) {
guard medicines.count == timeNames.count else { return }
for i in 0..<medicines.count {
medicines[i].2 = timeNames[i]
}
}
Once the var has been declared, type hints are no longer needed.
self.medicines = []
I've tried to think of a few ways to overcome your problem here, but your code is very inflexible and needs to be refactored.
You are at the limit for the utility of tuples and need to turn medicine into a class or struct (use a struct) which supports Equatable.
In addition, you need to create an array of new objects, which can be merged into the existing self.medicines, building the new objects directly in self.medicines is very limiting.
Here is the tuple as a struct
struct Medicine: Equatable {
let name: String
let amount: String
let time: String
}
func == (lhs: Medicine, rhs: Medicine) -> Bool {
return lhs.name == rhs.name && lhs.amount == rhs.amount && lhs.time == rhs.time
}
Here is adding new values without removing old values or having duplicates
if let names = meds["medicines"], amounts = meds["amount"], times = meds["time"]
where names.count == amounts.count && names.count == times.count
{
for i in 0..<names.count {
let medicine = Medicine(name: names[i], amount: amounts[i], time: times[i])
if !medicines.contains(medicine) {
medicines.append(medicine)
}
}
}

Saving to CoreData in a loop

I have a list of json objects that I want to write into CoreData. Json objects are coming from API. I have a method to Upsert a data into CoreData. The code for that is the following.
public class func upsertSetting (resultsArr : NSArray, settingTypeId: Int, countryId: Int) {
if let moc = CoreDataHelper().managedObjectContext {
var settings : [Setting]
settings = resultsArr as [Setting]
var existingSettings : [SettingCoreDataModel]?
existingSettings = fetchAllSettings(settingTypeId, countryId: countryId)
for result in settings {
var key = result.key
var value = result.value
if (value == nil)
{
value = ""
}
var id = result.id
var settingTypeId = result.settingTypeId
var countryId = result.countryId
var settingCoreData: [SettingCoreDataModel]?
var existingSetting: SettingCoreDataModel?
if(existingSettings? == nil){
existingSetting = nil
}
else{
settingCoreData = filter(existingSettings!) { (e:SettingCoreDataModel) in e.key == key }
if(settingCoreData? != nil && settingCoreData?.count > 0){
existingSetting = settingCoreData?.first
}
}
if(existingSetting == nil){
SettingCoreDataModel.createInManagedObjectContext(moc, key: key!, value: value!, id: id!, settingTypeId: settingTypeId!, countryId: countryId!)
}
else if(existingSetting?.value != value!){
existingSetting?.value = value!
save()
}
}
}
}
public class func save() {
var error : NSError?
if !CoreDataHelper().managedObjectContext!.save(&error) {
println("Could not save \(error), \(error?.userInfo)")
}
}
fetchAllSettings looks like this:
public class func fetchAllSettings (settingTypeId: Int, countryId: Int) -> [SettingCoreDataModel]?
{
let fetchRequest = NSFetchRequest(entityName: "SettingCoreDataModel")
let predicate = NSPredicate(format: "settingTypeId == %d AND countryId == %d", settingTypeId, countryId)
fetchRequest.predicate = predicate
if let fetchResults = CoreDataHelper().managedObjectContext!.executeFetchRequest(fetchRequest, error: nil) as? [SettingCoreDataModel] {
return fetchResults
}
else{
return []
}
}
And here is the SettingCoreDataModel with createInManagedObjectContext :
public class SettingCoreDataModel: NSManagedObject {
#NSManaged var key: String
#NSManaged var value: String
#NSManaged var id: NSNumber
#NSManaged var settingTypeId: NSNumber
#NSManaged var countryId: NSNumber
class func createInManagedObjectContext(moc: NSManagedObjectContext, key: NSString, value: NSString, id: int, settingTypeId: int, countryId: int) -> SettingCoreDataModel {
let newItem = NSEntityDescription.insertNewObjectForEntityForName("SettingCoreDataModel", inManagedObjectContext: moc) as? SettingCoreDataModel
newItem?.key = key
newItem?.value = value
newItem?.id = id
newItem?.settingTypeId = settingTypeId
newItem?.countryId = countryId
return newItem!
}
}
My problem is, when I call createInManagedObjectContext it does not seem to be saving into CoreData. I couldn't figure the issue and I'm fairly new to Swift and XCode
Edit: One thing I forgot to mention is that, I'm trying to create a plugin with this. All my code is in Pod but my data model is in example project. I'm not sure if that makes any difference to that, but while fetching and everything it seems to be working fine.
Using save() didn't help either by the way.
I call upsertSetting on application didFinishLaunchingWithOptions. After that, on my first view I try to get a single record by key and use it on the table view:
var textColorSetting: Setting?
override func viewDidLoad() {
super.viewDidLoad()
textColorSetting = SettingCoreData.fetchSetting("text_color", countryId: 3)
}
Table cells:
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
let cell: UITableViewCell = tableView.dequeueReusableCellWithIdentifier(kCellIdentifier) as UITableViewCell
textColorSetting = SettingCoreData.fetchSetting("text_color", countryId: 3)
var textColor = textColorSetting?.value
if (textColor? == nil){
textColor = "#ff00ff"
}
let color = SettingCoreDataModel.hexStringToUIColor(textColor!)
cell.textLabel?.textColor = color
}
Fetching single setting:
public class func fetchSetting(key : NSString, countryId: Int) -> Setting? {
let fetchRequest = NSFetchRequest(entityName: "SettingCoreDataModel")
var existingSetting: SettingCoreDataModel?
let predicate = NSPredicate(format: "key == %# AND countryId == %d", key, countryId)
fetchRequest.predicate = predicate
var error: NSError?
if let fetchResults = CoreDataHelper().managedObjectContext!.executeFetchRequest(fetchRequest, error: &error) as? [SettingCoreDataModel] {
println(fetchResults.count)
if (!fetchResults.isEmpty && fetchResults.count > 0) {
existingSetting = fetchResults.first!
var setting: Setting?
setting?.id = existingSetting?.id as? Int
setting?.key = existingSetting?.key
setting?.value = existingSetting?.value
setting?.settingTypeId = existingSetting?.settingTypeId as? Int
setting?.countryId = existingSetting?.countryId as? Int
return setting
} else {
return nil
}
} else {
return nil
}
}
In here println(fetchResults.count) always prints 0. That's why I assume save() is not working.
In the case where you create a new setting, you need to invoke save (or better yet, take save out of the inside of the loop and just call it once after the loop)
if(existingSetting == nil){
SettingCoreDataModel.createInManagedObjectContext(moc, key: key!, value: value!, id: id!, settingTypeId: settingTypeId!, countryId: countryId!)
save() // this is missing
}
Some other observations and simplifications:
fetchAllSettings will always return a valid array (possibly empty) of settings, declare it as -> [SettingCoreDataModel] instead of declaring it as an optional:
public class func fetchAllSettings (settingTypeId: Int, countryId: Int) -> [SettingCoreDataModel]
var existingSettings = fetchAllSettings(settingTypeId, countryId: countryId)
having done that you can use the fact that first returns an optional to simplify the logic to find an existing setting to:
var existingSetting = existingSettings.filter({ (e:SettingCoreDataModel) in e.key == key }).first
To insure that value always has a valid value, so you don't have to worry about unwrapping it, use nil checking:
var value = result.value ?? ""

Resources