如何使用 Equatable 比较基于不同属性的自定义对象?

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

我正在使用 equatable 协议来比较基于一个名为 mediaUID.
的 属性 的两个自定义对象 有没有办法在比较不同的属性之间切换?
func fetchNotificationsRemoved 中,有时我需要通过 mediaUID 或 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
}

您可以使用 static var 来建立要用于比较的字段:

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

示例:

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")
}

输出:

same mediaUID

使用 OptionSet

比较多个字段

如果将 enum 替换为 OptionSet,则可以 select 多个字段进行比较:

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
}

例子

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")
}

输出

same mediaUID
same mediaUID and commentUID

多线程问题

如果您的代码在另一个线程中修改 compareFields 值,则会出现问题。对于所有线程,equals 的含义都会改变。一种可能的解决方案是仅在主线程中更改和使用 NotificationInformation 的相等性。

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

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

        // use index
        ...
    }
}
...

将您的 func 更改为:

          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

如果需要条件检查,可以引入布尔变量:

    class NotificationInformation {

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

      let checkByMediaUID: Bool = true

    }

所以改变你的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
      }

以更具可读性的方式:

   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.

例子

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

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

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

     if a == b{
        ....
     }

你可以查看NotificationInformation个对象的type属性,并据此比较对象。

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


您可以使用 enum 作为 type 属性

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

用法

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)
            }
        }
    }
}