Search code examples
swiftequatable

How to compare custom objects based on different properties using Equatable?


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
}

Solution

  • 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
            ...
        }
    }
    ...