Compare two instances of an object in Swift - class

Given the following class, how can all the values in two instances be compared to each other?
// Client Object
//
class PLClient {
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
convenience init (copyFrom: PLClient) {
self.init()
self.name = copyFrom.name
self.email = copyFrom.email
self.mobile = copyFrom.mobile
self.companyId = copyFrom.companyId
self.companyName = copyFrom.companyName
}
}
var clientOne = PLClient()
var clientTwo = PLClient(copyFrom: clientOne)
if clientOne == clientTwo { // Binary operator "==" cannot be applied to two PLClient operands
println("No changes made")
} else {
println("Changes made. Updating server.")
}
The use-case for this is in an application which presents data from a server. Once the data is converted into an object, a copy of the object is made. The user is able to edit various fields etc.. which changes the values in one of the objects.
The main object, which may have been updated, needs to be compared to the copy of that object. If the objects are equal (the values of all properties are the same) then nothing happens. If any of the values are not equal then the application submits the changes to the server.
As is shown in the code sample, the == operator is not accepted because a value is not specified. Using === will not achieve the desired result because these will always be two separate instances.

Indicate that your class conforms to the Equatable protocol, and then implement the == operator.
Something like this:
class PLClient: Equatable
{
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
//The rest of your class code goes here
public static func ==(lhs: PLClient, rhs: PLClient) -> Bool{
return
lhs.name == rhs.name &&
lhs.id == rhs.id &&
lhs.email == rhs.email &&
lhs.mobile == rhs.mobile &&
lhs.companyId == rhs.companyId &&
lhs.companyName == rhs.companyName
}
}

Working off of Duncan C's answer, I have come up with an alternative which is slightly clearer that it is being used in a custom way:
// Client Object
//
class PLClient {
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
convenience init (copyFrom: PLClient) {
self.init()
self.name = copyFrom.name
self.email = copyFrom.email
self.mobile = copyFrom.mobile
self.companyId = copyFrom.companyId
self.companyName = copyFrom.companyName
}
func equals (compareTo:PLClient) -> Bool {
return
self.name == compareTo.name &&
self.email == compareTo.email &&
self.mobile == compareTo.mobile
}
}
var clientOne = PLClient()
var clientTwo = PLClient(copyFrom: clientOne)
if clientOne.equals(clientTwo) {
println("No changes made")
} else {
println("Changes made. Updating server.")
}

You could loop through fields by using keypath
I haven't tested this, but the general idea is there. Give a list of valid fields and loop through them instead of writing every single equatable. So it's the same as #duncan-c suggested but with looping.
Something like:
class PLClient:Equatable {
var name = String()
var id = String()
var email = String()
var mobile = String()
var companyId = String()
var companyName = String()
public static func ==(lhs: PLClient, rhs: PLClient) -> Bool{
let keys:[KeyPath<PLClient, String>] = [\.name, \.id, \.email, \.mobile, \.companyId, \.companyName]
return keys.allSatisfy { lhs[keyPath: $0] == rhs[keyPath: $0] }
}
}

Try "is" keyword in swift,
e.g.
if self.navigationController.topViewController is TestViewController {
//Logic here
}

Related

How to get the property names for `UIFont` (and other UIKit classes)?

This simple code:
func getProperties(object: AnyObject) -> [String] {
var result = [String]()
let mirror = Mirror(reflecting: object)
mirror.children.forEach { property in
guard let label = property.label else {
return
}
result.append(label)
}
return result
}
works for custom classes, e.g.:
class A {
var one: String
var two: Int
init() {
one = "hello"
two = 1
}
}
var a = A()
print(getProperties(object: a))
// prints ["one", "two"]
However the same code for UIFont (and other UIKit classes), returns nothing:
var font = UIFont.systemFont(ofSize: 10)
print(getProperties(object: font))
// prints []
I also tried an old-school extension for NSObject:
extension NSObject {
var propertyNames: [String] {
var result = [String]()
let clazz = type(of: self)
var count = UInt32()
guard let properties = class_copyPropertyList(clazz, &count) else {
return result
}
for i in 0..<Int(count) {
let property: objc_property_t = properties[i]
guard let name = NSString(utf8String: property_getName(property)) else {
continue
}
result.append(name as String)
}
return result
}
}
And again, it works for my custom classes (if they extend NSobject and the properties are marked as #objc):
class B: NSObject {
#objc var one: String
#objc var two: Int
override init() {
one = "hello"
two = 1
}
}
var b = B()
print(b.propertyNames)
// prints ["one", "two"]
but doesn't find any properties for UIFont.
If I look at the UIFont definition, the properties are seems defined like regular properties (or at least this is what Xcode shows:
open class UIFont : NSObject, NSCopying, NSSecureCoding {
//...
open var familyName: String { get }
open var fontName: String { get }
// etc
The type of the class is UICTFont though, not sure if it matters.
I would like to understand why this is not working, but my main question is more generic: is there any way to obtain the properties of the UIKit class (e.g. UIFont)?

How to filter by grand parent value when deleting multiple child objects in realm?

I'm trying to delete all WSR objects that are associated with all Exercise objects within a particular workout object.
I just have some trouble filtering out all the WSR objects. The way it's set up right now, if I have multiple Exercise objects, only the WSR values associated with the first Exercise object get deleted, but not the rest.
How could I filter out all WSR objects that are associated with all Exercise objects within a particular workout?
class Days : Object {
#objc dynamic var weekday : String = ""
let workout = List<Workouts>()
}
class Workouts : Object {
#objc dynamic var title : String = ""
var parentDay = LinkingObjects(fromType: Days.self, property: "workout")
let exercise = List<Exercises>()
}
class Exercises : Object {
#objc dynamic var exerciseName : String = ""
var parentWorkout = LinkingObjects(fromType: Workouts.self, property: "exercise")
let wsr = List<WeightSetsReps>()
}
class WeightSetsReps : Object {
#objc dynamic var weight = 0
#objc dynamic var reps = 0
var parentExercise = LinkingObjects(fromType: Exercises.self, property: "wsr")
}
if days?[indexPath.section].workout[indexPath.row].exercise.isEmpty == false {
if let selectedWorkout = days?[indexPath.section].workout[indexPath.row] {
let thisWorkoutsExercises = realm.objects(Exercises.self).filter("ANY parentWorkout == %#", selectedWorkout)
// Filter function to get all wsr's associated with the selected workout...
let thisWorkoutsWsr = realm.objects(WeightSetsReps.self).filter("ANY parentExercise == %#", days?[indexPath.section].workout[indexPath.row].exercise[indexPath.row])
realm.delete(thisWorkoutsWsr)
realm.delete(thisWorkoutsExercises)
realm.delete((days?[indexPath.section].workout[indexPath.row])!)
}
} else {
realm.delete((days?[indexPath.section].workout[indexPath.row])!)
}
The problem is this line:
let thisWorkoutsWsr = realm.objects(WeightSetsReps.self).filter("ANY parentExercise == %#", days?[indexPath.section].workout[indexPath.row].exercise[indexPath.row])
By using ANY parentExercise == ... .exercise[indexPath.row]) you request only WSRs from exercise at one index. Imho this could even crash, if this workouts exercise count is lower than the days workout count.
By using the ANY .. IN .. operator, you request all exercises in the exercise array.
Try this:
guard let workout = days?[indexPath.section].workout[indexPath.row] else { return }
if workout.exercise.isEmpty {
realm.delete(workout)
return
}
let thisWorkoutsExercises = realm.objects(Exercises.self).filter("ANY parentWorkout == %#", workout)
// Filter function to get all wsr's associated with the selected workout...
let thisWorkoutsWsr = realm.objects(WeightSetsReps.self).filter("ANY parentExercise IN %#", thisWorkoutsExercises)
realm.delete(thisWorkoutsWsr)
realm.delete(thisWorkoutsExercises)
realm.delete(workout)

Is it possible to exclude certain property (Like class type) from being copied during struct copy?

I have the following struct, which contains class.
import Foundation
func generateRichText(body: String?) -> NSMutableAttributedString? {
if body == nil {
return nil
}
// TODO: Some complex logic to decorate body string will be added soon...
let myAttrString = NSMutableAttributedString(string: body!)
return myAttrString
}
struct Note {
var body: String?
// Technique described in https://stackoverflow.com/a/25073176/72437
var bodyAsRichText: NSMutableAttributedString? {
mutating get {
if (cachedBodyAsRichText == nil) {
cachedBodyAsRichText = generateRichText(body: body)
}
return cachedBodyAsRichText
}
}
// TODO: This is a class. I don't want it to be copied over during struct copy.
// If it is copied during struct copy, both struct will be sharing the same
// class instance.
private var cachedBodyAsRichText: NSMutableAttributedString?
}
var note = Note()
note.body = "hello"
print("note.bodyAsRichText = \(Unmanaged.passUnretained(note.bodyAsRichText!).toOpaque())")
var note_copy = note
print("note_copy.bodyAsRichText = \(Unmanaged.passUnretained(note_copy.bodyAsRichText!).toOpaque())")
For the above code, the output will be
note.bodyAsRichText = 0x000055c035cfce70
note_copy.bodyAsRichText = 0x000055c035cfce70
What my desired output is, different struct instance, should be having their very own class instance (cachedBodyAsRichText)
Hence, is there a way, to exclude cachedBodyAsRichText from being copied over, during struct copy?
Your solution is incomplete. Here is a complete and correct solution.
struct Note {
var body: String = "" {
didSet {
cachedBodyAsRichText = nil
}
}
var bodyAsRichText: NSAttributedString {
mutating get {
if (cachedBodyAsRichText == nil) {
cachedBodyAsRichText = generateRichText(body: body)
}
return cachedBodyAsRichText!.copy() as! NSAttributedString
}
}
private var cachedBodyAsRichText: NSAttributedString? = nil
}
You need to clear out the cache every time the body is modified. Once you do that, it won't matter if the object is shared among structs.

Property observers Swift 4

struct Kitchen // Coordinator
{
var foodItems = [FoodItem] () // Collection of FoodItem objects.
var wastedItems = [WastedItem]() // Collection of WastedItem
var totalSpend = Double()
var currentSpend = Double()
var weeklyHouseholdFoodBudget = Double()
var wastageCost = Double()
init(aTotal:Double,aCurrent:Double,aBudget:Double,aWastage:Double)
{
self.totalSpend = aTotal
self.currentSpend = aCurrent
self.weeklyHouseholdFoodBudget = aBudget
self.wastageCost = aWastage
}
struct FoodItem : Equatable
{
var itemName = String()
var itemQuantity = Double()
var dateOfUse = String()
var unitOfMeasurement = String()
init(aName:String,aQuantity:Double,aDateOfUse:String,aUnit:String)
{
self.itemName = aName
self.itemQuantity = aQuantity
self.dateOfUse = aDateOfUse
self.unitOfMeasurement = aUnit
}
mutating func listFoodItems()
{
for item in foodItems
{
print("Item Name:", item.getName(),",",
"Qunatity:",item.getItemQuantity(),",",
"Unit:",item.getUnitOfMeasurement(),",",
"Date of use:",item.getDateOfUse())
}
}
mutating func removeFoodItem(aFood:FoodItem)
{
if let anIndex = foodItems.index(of: aFood)
{
foodItems.remove(at: anIndex)
print(aFood.getName(),"Has been removed")
}
}
mutating func useFood(aFoodItem:inout FoodItem,inputQty:Double)
{
if (aFoodItem.getItemQuantity()) - (inputQty) <= 0
{
self.removeFoodItem(aFood: aFoodItem)
}
else
{
aFoodItem.useFoodItem(aQty: inputQty)
}
}
***Updated****
The issue I am having is when I use a func listFoodItems() the updated attribute itemQuantity does not change. I would like to know how to update the collection so when I call the func listFoodItems() it displays value changes.
The removal is ok, when the func runs the collection removes the object.
The issue must be because I am using for item in foodItems to display, I need to reload it with updated values before I do this?
Thank you for any assistance.
Property observers are used on properties.
The simplest example of a property observer.
class Foo {
var bar: String = "Some String" {
didSet {
print("did set value", bar)
}
}
}
Now if you want to display some changes, you will need your own code in didSet method in order to do that. For example call reloadData().

Need to know variable name of Inner class that was defined in Outer class

I have these two classes:
class User:Obj
{
var firstBook:Book?
var secondBook:Book?
}
class Book:Obj
{
func getMyName() -> String
{
// Something need to do here
// return name
}
}
let user = User()
let book_1 = Book()
user.firstBook = book_1
let book_2 = Book()
user.secondBook = book_2
print(book_2.getMyName()) //Expected: secondBook
print(book_1.getMyName()) //Expected: firstBook
As you understand, I need to get the variable name of parent class.
Will be great if will be possible also to get parent class.Type
You can achieve something similar using reflection. You need to know about the user object inside the book object, so I've added a parent variable. It needs to be weak, to avoid retain cycles.
class User: Obj {
var firstBook: Book? {
didSet {
firstBook?.parent = self
}
}
var secondBook: Book? {
didSet {
secondBook?.parent = self
}
}
}
class Book: Obj {
weak var parent: Obj!
func getMyName() -> String {
let mirror = Mirror(reflecting: parent)
let variableName = mirror.children.filter { $0.value as? Book === self }.first?.label
return variableName!
}
}
You can create property in Book class as name and set the name property to firstBook and secondBook and get it by retrieving name property