I have a small chat app here.
I can have 2 types of messages:
- text
- video
I am using polymorphism while decoding the JSON like so:
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")
}
}
}
I am also using the MVVM approach like so:
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,
]
......
}
}
In order to be able to use a Diffing library like Differ, the ChatViewModel should conform to the Equatable protocol.
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
}
}
}
The problem here is for the case let (.loaded(l), .loaded(r)): return l == r, Message, as a protocol, doesn't conform to Equatable.
Making it conform to Equatable like
protocol Message: Decodable, Equatable {
static var type: MessageType { get }
var id: String { get }
var user: User { get}
var timestamp: String { get }
}
produce an error Protocol 'Message' can only be used as a generic constraint because it has Self or associated type requirements for the MessageWrapper:
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
}
}
...
}
Any idea or suggestion to have a clean way to solve this? I saw some post about Type Erasure but after some tests I am not sure that it is actually solving the problem.
You don't have to conform to Equatable in order to be able to use the == operator. You can just define an operator like that yourself, without conforming to Equatable.
For convenience's sake, I'll assume that TextMessage and VideoMessage already conforms to Equatable.
First, write a method that compares 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
}
Then a the == operator for [Message]:
func ==(messages1: [Message], messages2: [Message]) -> Bool {
return messages1.count == messages2.count &&
zip(messages1, messages2).allSatisfy(messageEqual)
}
Now l == r should compile.
Related
I have below kind of response model, where the body is decided by another variable. How can i confirm equatable to this Model
public struct Model {
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType {
var items: [String]?
}
struct Response2: ResponseType {
var item: String?
}
What i want to achive:
extension Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
// How to equate the body?
}
}
When im trying to add Equatable to ResponseType protocol it says below error.
Protocol 'ResponseType' can only be used as a generic constraint because it has Self or associated type requirements
You need to implement == manually. swift doesn't know that body can only be two types, which are?
public struct Model: Equatable {
public static func == (lhs: Model, rhs: Model) -> Bool {
if lhs.type != rhs.type {
return false
}
if let lhsBody = lhs.body as? Response1, let rhsBody = rhs.body as? Response1 {
return lhsBody == rhsBody
} else if let lhsBody = lhs.body as? Response2, let rhsBody = rhs.body as? Response2 {
return lhsBody == rhsBody
} else {
return false
}
}
let type: String? // can be type1 or type2
let body: ResponseType?
}
protocol ResponseType: Codable {
}
struct Response1: ResponseType, Equatable {
var items: [String]?
}
struct Response2: ResponseType, Equatable {
var item: String?
}
It might be easier if you change Model into an enum:
enum Model: Codable, Equatable {
case type1(items: [String]?)
case type2(item: String)
var type: String {
switch self {
case .type1: return "type1"
case .type2: return "type2"
}
}
}
You probably need to change the Codable implementation so that it encodes and decodes the way you want to.
Does swift type inference not work with function return types?
protocol Vehicle {
func numberOfWheels() -> Int
}
struct Car: Vehicle {
func numberOfWheels() -> Int {
return 4
}
}
struct Bike: Vehicle {
func numberOfWheels() -> Int {
return 2
}
}
struct Truck: Vehicle {
func numberOfWheels() -> Int {
return 8
}
}
struct VehicleFactory {
static func getVehicle<T: Vehicle>(_ vehicleType: T.Type = T.self) -> T? {
let id = identifier(for: T.self)
switch id {
case "Car":
return Car() as? T
case "Bike":
return Bike() as? T
default:
return nil
}
}
private static func identifier(for type: Any.Type) -> String {
String(describing: type)
}
}
let v: Bike = VehicleFactory.getVehicle() // ERROR HERE: Cannot convert value of type 'T?' to specified type 'Bike'
print(v.numberOfWheels())
I am trying this in playground. Why is there an error in above line?
Shouldnt the compiler infer the type to be Bike from the let v: Bike declaration?
The problem is that getVehicle returns an optional, you have to declare
let v: Bike? = VehicleFactory.getVehicle()
Further you have to unwrap v in the print line
Not a direct answer to your question. Vadian has already answered but a few notes on your implementation:
(_ vehicleType: T.Type = T.self) is pointless. You can just omit it.
Second I would simply add init() to your protocol requirements, get rid of the identifier method, change number of wheels to a computed property:
protocol Vehicle {
init()
var numberOfWheels: Int { get }
}
struct Car: Vehicle {
let numberOfWheels = 4
}
struct Bike: Vehicle {
let numberOfWheels = 2
}
struct Truck: Vehicle {
let numberOfWheels = 8
}
struct VehicleFactory {
static func getVehicle<T: Vehicle>() -> T { .init() }
}
let v: Bike = VehicleFactory.getVehicle()
print(v.numberOfWheels) // "2\n"
I have code that resembles this
I created custom subscript to do the unwrapping for me to make things easier.
enum MyEnum {
case one
case two
case three
}
struct MyStruct {
static var empty: Self {
return .init()
}
//Some Variables Here
}
class MyClass {
var index = 0
var data: [MyEnum: MyStruct] = [
.two: .empty,
.three: .empty,
.one: .empty
]
var allTypes: [MyEnum] {
switch Bool.random() {
case true:
return [.two, .three]
case false:
return [.one]
}
}
var currentSelectedType: MyEnum {
return allTypes[index]
}
func myMethod(type: MyEnum) {
let one: MyStruct = data[type]!
let two: MyStruct = data[currentSelectedType]!
let three: MyStruct = data[allTypes[index]]
let four: MyStruct = data[.one]
}
}
fileprivate extension Dictionary {
subscript(_ key: Key) -> Value where Key == MyEnum, Value == MyStruct {
get {
return self[key]!
}
set {
self[key] = newValue
}
}
}
in my MyClass myMethod why I have to forcely unwrapp one and two but not three and four otherwise I get a compile time error
let one: MyStruct = data[type] // Error Value of optional type 'MyStruct?' must be unwrapped to a value of type 'MyStruct'
let two: MyStruct = data[currentSelectedType] //Error Value of optional type 'MyStruct?' must be unwrapped to a value of type 'MyStruct'
Is there something I'm missing here?
I don't have an answer on why compiler isn't picking the expected overload in this situation.
I would recommend clarifying the overload you wish to use at call site, like following.
fileprivate extension Dictionary {
subscript(safe key: Key, defaultValue: Value = .empty) -> Value where Key == MyEnum, Value == MyStruct {
get { return self[key, default: defaultValue] }
set { self[key] = newValue }
}
}
With above, you can tell compiler explicitly to use your preferred overload.
func myMethod(type: MyEnum) {
let one: MyStruct = data[safe: type]
let two: MyStruct = data[safe: currentSelectedType]
let three: MyStruct = data[safe: allTypes[index]]
let four: MyStruct = data[safe: .one]
}
The following enum works fine without any error.
enum EnumOptions: CaseIterable {
case none
case mild
case moderate
case severe
case unmeasurable
}
When I try to add an associated value to one of the cases, it throws the following error "Type 'EnumOptions' does not conform to protocol 'CaseIterable'. Do you want to add protocol stubs?"
enum OedemaOptions: CaseIterable {
case none
case mild
case moderate
case severe
case unmeasurable(Int)
}
After adding stubs,
enum OedemaOptions: CaseIterable {
typealias AllCases = <#type#>
case none
case mild
case moderate
case severe
case unmeasurable(Int)
What should be filled up in the placeholder to make the Enum conform to CaseIterable, since there is only 1 case with associated value and not all the cases?
Automatic synthesis does not work for enums with associated values. You need to provide a custom implementation of the allCases property. Try,
enum OedemaOptions: CaseIterable {
static var allCases: [OedemaOptions] {
return [.none, .mild, .moderate, .severe, .unmeasurable(-1)]
}
case none
case mild
case moderate
case severe
case unmeasurable(Int)
}
You forgot to account for all 18,446,744,073,709,551,616 Ints.
Also, each one is an Option, not an Options.
static var allCases: [OedemaOption] {
[.none, .mild, .moderate, .severe]
+ (.min...(.max)).map(unmeasurable)
}
Compiler doesn't support automatic synthesis for CaseIterable in case with associated value(s).
This is my alternative solution to CaseIterable
import Foundation
protocol CustomCaseIterable {
associatedtype AllCustomCases: Hashable
static var allCustomCases: [AllCustomCases: Self] { get }
}
extension CustomCaseIterable {
init(_ string: String?, defaultCase: Self) {
self = Self(string) ?? defaultCase
}
init?(_ string: String?) {
guard
let string = string,
let caseValue = Self.allCustomCases.first(where: { "\($0.0)" == string })?.value
else {
return nil
}
self = caseValue
}
}
Example
enum MyEnum {
case one(Bool)
case two(Int)
case three(String)
case four
}
extension MyEnum: CustomCaseIterable {
static var allCustomCases: [String : Self] = [
"one_true": .one(true),
"two_zero": .two(.zero),
"three_empty": .three(""),
"four": .four
]
}
for (key, _) in MyEnum.allCustomCases {
print(key)
}
Extending that concept to RawRepresentable
extension CustomCaseIterable where AllCustomCases: RawRepresentable {
init(_ string: String?, defaultCase: Self) {
self = Self(string) ?? defaultCase
}
init?(_ string: String?) {
guard
let string = string,
let caseValue = Self.allCustomCases.first(where: { "\($0.0.rawValue)" == string })?.value
else {
return nil
}
self = caseValue
}
}
Example
enum MapStyle {
case primary(border: Bool)
case secondary(border: Bool)
case placeholder
}
enum JSONMapStyle: String, CaseIterable {
case primary
case primaryWithBorder = "PRIMARY_WITH_BORDER"
case secondary
case secondaryWithBorder = "SECONDARY_WITH_BORDER"
case placeholder
}
extension MapStyle: CustomCaseIterable {
static var allCustomCases: [JSONMapStyle: MapStyle] = [
.primary: .primary(border: false),
.primaryWithBorder: .primary(border: true),
.secondary: .secondary(border: false),
.secondaryWithBorder: .secondary(border: true),
.placeholder: .placeholder
]
}
for (key, _) in MapStyle.allCustomCases {
print(key.rawValue)
}
Here, we have a scenario where I am facing issue for parsing the model class using "JSONDecoder".
Let me share you what I have done in this example and where I am facing issue:
There's a model protocol derived through the Codable.
A struct model and A class GenericExample
GenericExample class have following things:
shared instance
typeContainer which is the type of Protocol
There's two more methods staticClassParsing and dynamicClassParsing with one parameter dynamicType
In the last, One generic method which return the parsed data model.
Calling method for parsing the model:
GenericExample.shared.dynamicClassParsing(Numbers.self,data: "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]")
Compile Time Error:
Generic parameter 'T' could not be inferred
Occurred here
returnModelType: typeContainer.self
**For more details, please go through following code: **
protocol BaseMapModel : Codable { }
struct Numbers: BaseMapModel {
var one: String?
var two: String?
}
class GenericExample: NSObject {
static let shared = GenericExample()
var typeContainer : BaseMapModel.Type?
private override init() {
super.init()
}
}
extension GenericExample {
// Static Class Parsing passed through the conversion
func staticClassParsing() {
let dataJson = "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]"
convertTypeContainer(data: Data(dataJson.utf8), returnModelType: Numbers.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
// Dynamic Class Parsing can't passed through the conversion
// Error:- Generic parameter 'T' could not be inferred
// Error Parameter:- in "returnModelType: typeContainer.self"
func dynamicClassParsing(_ dynamicType: BaseMapModel.Type, data:String) {
typeContainer = dynamicType.self
convertTypeContainer(data: Data(data.utf8), returnModelType: typeContainer.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
}
extension GenericExample {
private func convertTypeContainer<T : BaseMapModel>(data:Data, returnModelType: T.Type, completion: ((_ result:T?)->Void)) {
guard let responseModel = try? JSONDecoder().decode(returnModelType.self, from: data) else {
completion(nil)
return
}
completion(responseModel)
}
}
typeContainer must be a concrete type, it cannot be a protocol. And the completion handler is pointless as JSONDecoder works synchronously.
To decode JSON with generics you have to use something like this, it's highly recommended to handle also the Decoding error
struct Numbers: Decodable {
var one: String?
var two: String?
}
class GenericExample: NSObject {
static let shared = GenericExample()
}
extension GenericExample {
func dynamicClassParsing<T : Decodable>(_ dynamicType: T.Type, data: String) -> Result<T,Error> {
return Result { try JSONDecoder().decode(T.self, from: Data(data.utf8)) }
}
}
let dataJson = """
[{"one": "1"},{"two":"2"}]
"""
let result = GenericExample.shared.dynamicClassParsing([Numbers].self, data: dataJson)
switch result {
case .success(let numbers): print(numbers)
case .failure(let error): print(error)
}
First of all, Thanks to All for your support. And Yes, I would like to post answer to my question.
class BaseMapModel : Codable { }
class Numbers: BaseMapModel {
var one: String?
var two: String?
enum CodingKeys: String, CodingKey {
case one = "one"
case two = "two"
}
// Decoding
required init(from decoder: Decoder) throws {
let response = try decoder.container(keyedBy: CodingKeys.self)
one = try? response.decode(String.self, forKey: .one)
two = try? response.decode(String.self, forKey: .two)
let superDecoder = try response.superDecoder()
try super.init(from: superDecoder)
}
}
class GenericExample: NSObject {
static let shared = GenericExample()
var defaultTypeContainer : Numbers.Type!
var typeContainer : BaseMapModel.Type?
private override init() {
super.init()
}
}
extension GenericExample {
// Static Class Parsing passed through the conversion
func staticClassParsing() {
let dataJson = "[\r\n{\"one\": \"1\"},\r\n{\"two\":\"2\"}\r\n]"
convertTypeContainer(data: Data(dataJson.utf8), returnModelType: Numbers.self) { (mappedResult) in
print(mappedResult?.one ?? "")
print(mappedResult?.two ?? "")
}
}
// Dynamic Class Parsing passed through the conversion
func dynamicClassParsing(_ dynamicType: BaseMapModel.Type, data:String) {
typeContainer = dynamicType.self
convertTypeContainer(data: Data(data.utf8), returnModelType: (typeContainer ?? defaultTypeContainer).self) { (mappedResult) in
print((mappedResult as? Numbers)?.one ?? "")
print((mappedResult as? Numbers)?.two ?? "")
}
}
}
extension GenericExample {
private func convertTypeContainer<T : BaseMapModel>(data:Data, returnModelType: T.Type, completion: ((_ result:T?)->Void)) {
guard let responseModel = try? JSONDecoder().decode(returnModelType.self, from: data) else {
completion(nil)
return
}
completion(responseModel)
}
}