1

I am using the equatable protocol in order to compare two custom objects based on one property named mediaUID.
Is there a way to switch between comparing on different properties?
In func fetchNotificationsRemovedsometimes I need to compare by mediaUID or by likeUID.

 var notificationsArray = [NotificationInformation]()

class NotificationInformation {

    let type: String
    let mediaUID: String?
    let commentUID: String?
    let likeUID:String?    
}


extension NotificationInformation {
    func fetchNotificationsRemoved(query: DatabaseQuery) {

    NotificationInformation.observeNewNotificationsChildRemoved(query: query) { [weak self] (newNotification: NotificationInformation?) in

            guard let strongSelf = self else {return}
            guard let notification = newNotification else {return}

        if notification.type == "like" {

            // How to compare based on likeUID using equatable?
            //compare based on likeUID
            //get the index of the item of type 'like' in notificationsArray and do something with it
            guard let index = strongSelf.notificationsArray.index(of: notification) else {return}

        }else if notification.type == "media" {
            // How to compare based on mediaUID using equatable?
            //compare based on mediaUID
            //get the index of the item of type 'media' in notificationsArray
            guard let index = strongSelf.notificationsArray.index(of: notification) else {return}
        } else if if notification.type == "commentUID" {
           ....
   }


            guard let index = strongSelf.notificationsArray.index(of: notification) else {return}

            strongSelf.notificationsArray.remove(at: index)

            let visibleIndexes = strongSelf.tableView.indexPathsForVisibleRows
            let indexPathOfRemovedNotification = IndexPath(row: index, section: 0)

            if let indexes = visibleIndexes,
                indexes.contains(indexPathOfRemovedNotification) {
                strongSelf.tableView.deleteRows(at: [indexPathOfRemovedNotification], with: .fade)
            }
        }
    }

}//end extension

//enables us to compare two objects of type NotificationInformation
extension NotificationInformation: Equatable { }

func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
    guard let mediaUIDLeft = lhs.mediaUID else {return false}
    guard let mediaUIDRight = rhs.mediaUID else {return false}
    return mediaUIDLeft == mediaUIDRight
}
bibscy
  • 2,598
  • 4
  • 34
  • 82

3 Answers3

3

You could use a static var to establish the field you want to use for comparison:

class NotificationInformation: Equatable {
    enum CompareField {
        case type, mediaUID, commentUID, likeUID
    }

    static var compareField: CompareField = .mediaUID

    let type: String
    let mediaUID: String?
    let commentUID: String?
    let likeUID:String?

    init(type: String, mediaUID: String? = nil, commentUID: String? = nil, likeUID: String? = nil) {
        self.type = type
        self.mediaUID = mediaUID
        self.commentUID = commentUID
        self.likeUID = likeUID
    }

    static func ==(lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
        switch NotificationInformation.compareField {
        case .type:
            return lhs.type == rhs.type
        case .mediaUID:
            return lhs.mediaUID == rhs.mediaUID
        case .commentUID:
            return lhs.commentUID == rhs.commentUID
        case .likeUID:
            return lhs.likeUID == rhs.likeUID
        }
    }
}

Example:

let a = NotificationInformation(type: "foo", mediaUID: "123")
let b = NotificationInformation(type: "bar", mediaUID: "123")

NotificationInformation.compareField = .type
if a == b {
    print("same type")
}

NotificationInformation.compareField = .mediaUID
if a == b {
    print("same mediaUID")
}

Output:

same mediaUID

Comparing multiple fields using an OptionSet

If you replace the enum with an OptionSet, you could select multiple fields to compare:

struct CompareFields: OptionSet {
    let rawValue: Int

    static let type       = CompareFields(rawValue: 1 << 0)
    static let mediaUID   = CompareFields(rawValue: 1 << 1)
    static let commentUID = CompareFields(rawValue: 1 << 2)
    static let likeUID    = CompareFields(rawValue: 1 << 3)
}

static var compareFields: CompareFields = .mediaUID

static func ==(lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
    var equal = true

    if NotificationInformation.compareFields.contains(.type) {
        equal = equal && (lhs.type == rhs.type)
    }
    if NotificationInformation.compareFields.contains(.mediaUID) {
        equal = equal && (lhs.mediaUID == rhs.mediaUID)
    }
    if NotificationInformation.compareFields.contains(.commentUID) {
        equal = equal && (lhs.commentUID == rhs.commentUID)
    }
    if NotificationInformation.compareFields.contains(.likeUID) {
        equal = equal && (lhs.likeUID == rhs.likeUID)
    }

    return equal
}

Example

let a = NotificationInformation(type: "foo", mediaUID: "123", commentUID: "111")
let b = NotificationInformation(type: "bar", mediaUID: "123", commentUID: "111")

NotificationInformation.compareFields = .mediaUID
if a == b {
    print("same mediaUID")
}

NotificationInformation.compareFields = [.mediaUID, .commentUID]
if a == b {
    print("same mediaUID and commentUID")
}

Output

same mediaUID
same mediaUID and commentUID

Multithreaded issue

There is an issue if your code is modifying the compareFields value in another thread. The meaning of equals would change for all threads. One possible solution is to only change and use equality for NotificationInformation in the main thread.

...
} else if notification.type == "media" {
    DispatchQueue.main.async {
        NotificationInformation.compareFields = .mediaUID

        guard let index = strongSelf.notificationsArray.index(of: notification) else {return}

        // use index
        ...
    }
}
...
vacawama
  • 150,663
  • 30
  • 266
  • 294
  • One problem though with the first example. If there are multiple functions trying to simultaneously set `static var compareFields` to a different value, it will become corrupted. Example: in `viewDidLoad`, I will have several database observers `NotificationInformation.observeNewNotificationsChildRemoved(query: query)`, `NotificationInformation.observeNewNotificationsChildAdded(query: query)` , `NotificationInformation.observeNewNotificationsChildChanged(query: query)` – bibscy Jun 02 '19 at 13:38
  • Yes, that could be a problem with multiple threads. If you restrict the changing of `compareFields` and your comparisons to the main thread you'd be OK. – vacawama Jun 02 '19 at 13:42
  • Could you please show a solution for this case too? Thanks – bibscy Jun 02 '19 at 13:44
1

Change your func to this:

          func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
            guard let mediaUIDLeft = lhs.mediaUID else {return false}
            guard let mediaUIDRight = rhs.mediaUID else {return false}

            return (mediaUIDLeft == mediaUIDRight || lhs.likeUID == rhs.likeUID)
         }

This means that two NotificationInformation are equal if they have the same mediaUID OR the same likeUID

If you need a conditional check, you can introduce a boolean variable:

    class NotificationInformation {

      let type: String
      let mediaUID: String?
      let commentUID: String?
      let likeUID:String?    

      let checkByMediaUID: Bool = true

    }

So change your Equatable:

      func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
          guard let mediaUIDLeft = lhs.mediaUID else {return false}
          guard let mediaUIDRight = rhs.mediaUID else {return false}

          return (lhs.checkByMediaUID || rhs.checkByMediaUID) ? mediaUIDLeft == mediaUIDRight : lhs.likeUID == rhs.likeUID
      }

In more readable way:

   func ==(lhs: NotificationInformation ,rhs: NotificationInformation) -> Bool {
          guard let mediaUIDLeft = lhs.mediaUID else {return false}
          guard let mediaUIDRight = rhs.mediaUID else {return false}

          if lhs.checkByMediaUID || rhs.checkByMediaUID{
              return mediaUIDLeft == mediaUIDRight
          }

          return lhs.likeUID == rhs.likeUID
      }

This means that if you want to check by mediaUID, just compare two object. If you want to check by likeUID, just change the variable of one of the object.

Example

     let a: NotificationInformation = NotificationInformation()
     let b: NotificationInformation = NotificationInformation()

     //Check by `mediaUID`
     if a == b{
        ....
     }

     //Check by `likeUID`
     a.checkByMediaUID = false

     if a == b{
        ....
     }
Andrew21111
  • 868
  • 8
  • 17
1

You can check the type property of the NotificationInformation objects and compare objects according to that.

extension NotificationInformation: Equatable {
    static func == (lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
        guard lhs.type == rhs.type  else {
            print("Types of lhs and rhs are not same ")
            return false
        }
        switch lhs.type {
        case "like": return lhs.likeUID == rhs.likeUID
        case "media": return lhs.mediaUID == rhs.mediaUID
        case "commentUID": return lhs.commentUID == rhs.commentUID
        default: return false
        }
    }
}


And you can use enum for the type property

class NotificationInformation {
    enum NotificationType: String {
        case like
        case media
        case commentUID
    }
    let type: NotificationType
    let mediaUID: String?
    let commentUID: String?
    let likeUID:String?
}

extension NotificationInformation: Equatable {
    static func == (lhs: NotificationInformation, rhs: NotificationInformation) -> Bool {
        guard lhs.type == rhs.type  else {
            print("Types of lhs and rhs are not same ")
            return false
        }
        switch lhs.type {
        case .like: return lhs.likeUID == rhs.likeUID
        case .media: return lhs.mediaUID == rhs.mediaUID
        case .commentUID: return lhs.commentUID == rhs.commentUID
        }
    }
}

Usage

extension NotificationInformation {
    func fetchNotificationsRemoved(query: DatabaseQuery) {
        NotificationInformation.observeNewNotificationsChildRemoved(query: query) { [weak self] newNotification in
            guard let strongSelf = self else {return}
            guard let notification = newNotification else {return}
            guard let index = strongSelf.notificationsArray.index(of: notification) else {return}
            strongSelf.notificationsArray.remove(at: index)
            let visibleIndexes = strongSelf.tableView.indexPathsForVisibleRows
            let indexPathOfRemovedNotification = IndexPath(row: index, section: 0)
            if let indexes = visibleIndexes, indexes.contains(indexPathOfRemovedNotification) {
                strongSelf.tableView.deleteRows(at: [indexPathOfRemovedNotification], with: .fade)
            }
        }
    }
}
RajeshKumar R
  • 15,445
  • 2
  • 38
  • 70