符合 Equatable for Diffing 的协议

Posted

技术标签:

【中文标题】符合 Equatable for Diffing 的协议【英文标题】:Protocol conforming to Equatable for Diffing 【发布时间】:2019-10-27 13:50:53 【问题描述】:

我这里有一个小型聊天应用程序。

我可以有两种类型的消息: - 文本 - 视频

我在解码 JSON 时使用多态性,如下所示:

import Foundation

enum MessageType: Int, Decodable 
    case text
    case video


protocol Message: Decodable 
    static var type: MessageType  get 
    var id: String  get 
    var user: User  get
    var timestamp: String  get 


struct TextMessage: Message 
    static var type: MessageType = .text
    var id: String
    var user: User
    var timestamp: String
    let text: String


struct VideoMessage: Message 
    static var type: MessageType = .video
    var id: String
    var user: User
    var timestamp: String
    let text: String
    let link: String
    let poster: String


enum MessageWrapper: Decodable 

    enum CodingKeys: String, CodingKey 
        case type
    

    case text(TextMessage)
    case video(VideoMessage)

    var item: Message 
        switch self 
        case .text(let item): return item
        case .video(let item): return item
        
    

    init(from decoder: Decoder) throws 
        let values = try decoder.container(keyedBy: CodingKeys.self)
        let type = try values.decode(Int.self, forKey: .type)
        switch type 
        case MessageType.text.rawValue: self = .text(try TextMessage(from: decoder))
        case MessageType.video.rawValue: self = .video(try VideoMessage(from: decoder))
        default:
            throw DecodingError.dataCorruptedError(forKey: .type,
                                                   in: values,
                                                   debugDescription: "Invalid type")
        
    


我也像这样使用 MVVM 方法:

struct ChatViewModel 

    enum ViewModelType 
        case loading
        case text(TextMessageViewModel)
        case video(VideoMessageViewModel)
        case failure(ErrorViewModel)
    

    enum State 
        case initialized
        case loading
        case loaded([Message])
        case failed(Error)
    

    let state: State
    let viewModels: [ViewModelType]

    init(with state: State) 
        self.state = state
        switch state 
        case .initialized:
            viewModels = []
        case .loading:
            viewModels = [
                .loading,
            ]
        ......
    


为了能够使用像Differ 这样的Diffing 库,ChatViewModel 应该符合Equatable 协议。

extension ChatViewModel: Equatable 
    static func == (lhs: ChatViewModel, rhs: ChatViewModel) -> Bool 
        return lhs.state == rhs.state
    


extension ChatViewModel.State: Equatable 
    static func == (lhs: ChatViewModel.State, rhs: ChatViewModel.State) -> Bool 
        switch (lhs, rhs) 
            case (.initialized, .initialized): return true
            case (.loading, .loading): return true
            case let (.loaded(l), .loaded(r)): return l == r
            case let (.failed(l), .failed(r)): return l.localizedDescription == r.localizedDescription
            default: return false
        
    

这里的问题是case let (.loaded(l), .loaded(r)): return l == rMessage作为协议不符合Equatable

使其符合Equatablelike

protocol Message: Decodable, Equatable 
    static var type: MessageType  get 
    var id: String  get 
    var user: User  get
    var timestamp: String  get 

MessageWrapper 产生错误Protocol 'Message' can only be used as a generic constraint because it has Self or associated type requirements

enum MessageWrapper: Decodable 

    ...

    var item: Message  // Protocol 'Message' can only be used as a generic constraint because it has Self or associated type requirements
        switch self 
        case .text(let item): return item
        case .video(let item): return item
        
    

    ...

有什么想法或建议可以有一个干净的方法来解决这个问题吗?我看到了一些关于Type Erasure 的帖子,但经过一些测试,我不确定它是否真的解决了问题。

【问题讨论】:

【参考方案1】:

您不必遵守Equatable 即可使用== 运算符。你可以自己定义一个这样的运算符,而不符合Equatable

为方便起见,我假设TextMessageVideoMessage 已经符合Equatable

首先,写一个比较Messages的方法:

func messageEqual(m1: Message, m2: Message) -> Bool 
    if let textMessage1 = m1 as? TextMessage, let textMessage2 = m2 as? TextMessage 
        return textMessage1 == textMessage2
    
    if let videoMessage1 = m1 as? VideoMessage, let videoMessage2 = m2 as? VideoMessage 
        return videoMessage1 == videoMessage2
    
    return false

然后是== 运算符[Message]

func ==(messages1: [Message], messages2: [Message]) -> Bool 
    return messages1.count == messages2.count && 
        zip(messages1, messages2).allSatisfy(messageEqual)

现在l == r 应该可以编译了。

【讨论】:

但这不是要解决的问题。 “为了能够使用像 Differ 这样的 Diffing 库,ChatViewModel 应该符合 Equatable 协议。” @matt OP 已将ChatViewModelEquatable 一致。只是l == r 不编译,因为Message 不符合Equatable。要解决的问题是l == r没有编译。

以上是关于符合 Equatable for Diffing 的协议的主要内容,如果未能解决你的问题,请参考以下文章

加载数据后不符合 Equatable 的 Swift 对象

Swift,Equatable 协议错误?

RealmObject Equatable 冗余消息

CLLocation 如何实现 Equatable 协议?

RealmObject Equatable冗余消息

Swift 5:在使用协议实现 Equatable 的结构上实现通用数组操作