符合 Equatable for Diffing 的协议
Protocol conforming to Equatable for Diffing
我这里有一个小聊天应用。
我可以有两种类型的消息:
- 文本
- 视频
我在解码 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 == r
、Message
作为一个协议,不符合 Equatable
。
使其符合 Equatable
就像
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
的 post 但经过一些测试后我不确定它是否真的解决了问题。
您不必遵守 Equatable
即可使用 ==
运算符。你可以自己定义一个这样的运算符,而不符合 Equatable
.
为方便起见,我假设 TextMessage
和 VideoMessage
已经符合 Equatable
。
首先写一个比较Message
s的方法:
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
应该编译了。
我这里有一个小聊天应用。
我可以有两种类型的消息: - 文本 - 视频
我在解码 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 == r
、Message
作为一个协议,不符合 Equatable
。
使其符合 Equatable
就像
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
的 post 但经过一些测试后我不确定它是否真的解决了问题。
您不必遵守 Equatable
即可使用 ==
运算符。你可以自己定义一个这样的运算符,而不符合 Equatable
.
为方便起见,我假设 TextMessage
和 VideoMessage
已经符合 Equatable
。
首先写一个比较Message
s的方法:
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
应该编译了。