Swift. Protocol type as dictionary key - swift

I have been thinking about a way to use a Protocol type as a dictionary key. I made a simple test code in the playground:
//Custom Protocol conforms to Hashable
protocol Transport: Hashable {
var name: String {get set}
var quantity: Int {get set}
}
//Transport Protocol extension
extension Transport {
var hashValue: Int {
return name.hashValue ^ quantity.hashValue
}
}
//Equatable Protocol (Since Hashable conforms to Equatable)
func ==<T:Transport>(lhs: T, rhs: T) -> Bool {
return lhs.hashValue == rhs.hashValue
}
//Struct Car
struct Car: Transport {
var name: String = ""
var quantity: Int = 1
init(name: String, quantity: Int) {
self.name = name
self.quantity = quantity
}
}
//Struct Plane
struct Plane: Transport {
var name: String = ""
var quantity: Int = 1
init(name: String, quantity: Int) {
self.name = name
self.quantity = quantity
}
}
let t1 = Car(name: "Ford", quantity: 10)
let t2 = Plane(name: "Airbus", quantity: 2)
Next line gives an error: Binary operator '==' cannot be applied to operands of type 'Car' and 'Plane'
if t1 == t2 {
print("It's the same transport")
}
Next line gives this error: Using 'Transport' as a concrete type conforming to protocol 'Hashable' is not supported
var arrayOfTransports = [Transport:[Int]]()
What I am trying to do is to compare structures that conform to 'Transport' Protocol, and be able to use these structs as Dictionary keys.

Related

Is reflection on Metatype?

While it's possible to get a list of properties from an instance: it requires an instance which is troublesome if the Type isn't trivial and doesn't have a straightforward .init(). Similarly, can't we test the existence of a nested type?
struct SomeStruct: Codable {
var x: Int
var y: Int
}
class CoolClass: Codable {
var thing: SomeStruct
var name: String
enum CodingKeys: String, CodingKey {
case name = "title", name = "stuff"
}
}
func testA(_ type: Any.Type) {
if type is Codable.Protocol {
print("is Codable")
} else {
print("is NOT Codable")
}
let mirror = Mirror(reflecting: type)
mirror.children // << empty collection!!
}
func testB<T>(_ type: T.Type) where T: Codable {
// test if `type` defines `CodingKeys` or if it is synthesized.
}
testA(SomeStruct.self) // "is NOT Codable", can't get list of ["x", "y"]
// , nor can't get CodingKeys...
If there is no reflection for metatypes, my attempts at piggybacking off of Codable's CodingKeys (explicit or synthesized) have all failed. Is this possible?
I don't know how to solve your main problem, but you are not checking correctly if type is codable. Since type is already a structure/class type (not an object), you should try to cast it from Any.Type to Codable.Type, which will only succeed if type implements Codable:
if type as? Codable.Type != nil {
print("is Codable")
} else {
print("is NOT Codable")
}

Genericize method to encode a `Codable` protocol

import Foundation
protocol ProtocolA: Codable {
var var1: String { get }
}
struct Type1: ProtocolA {
var var1: String
var var2: Int
}
struct Type2: ProtocolA {
var var1: String
var var2: Bool
}
func encode<T: ProtocolA & Encodable>(object: T) throws -> Data {
return try JSONEncoder().encode(object as! T.Type)
}
Putting the above in a playground results in error: argument type 'T.Type' does not conform to expected type 'Encodable'
Why does this happen when I am saying that T has to conform to Encodable?
return try JSONEncoder().encode(object as! T.Type)
This means to convert object to the metatype of T. The type of the type. For example, 1 is an Int. But "Int" itself has a type, which is Int.Type, which is a metatype. Metatypes do not conform to Encodable.
You meant:
return try JSONEncoder().encode(object as! T)
But you really just meant:
return try JSONEncoder().encode(object)
since object is always of type T. That's its explicit type from the function signature. Since you also don't rely on ProtocolA for this algorithm, this all boils down to:
func encode<T: Encodable>(object: T) throws -> Data {
return try JSONEncoder().encode(object)
}
Your compiler is actually complaining about the type cast your doing at the end which is converting object to the metatype of T, in your example either Type1.Type or Type2.Type.
From the encoding perspective, what the compiler needs to know is the model confirms to Encodable which is implicit in the T: Codable statement.
import Foundation
protocol ProtocolA: Codable {
var var1: String { get }
}
struct Type1: ProtocolA {
var var1: String
var var2: Int
}
struct Type2: ProtocolA {
var var1: String
var var2: Bool
}
func encode<T: Codable>(object: T) throws -> Data {
return try JSONEncoder().encode(object)
}
let type2 = Type2(var1: "test1", var2: true)
print(type2)

Get all keys in the Codable (or Decodable)

Given the following codable struct:
struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey {
case b = "B"
case z = "ZOOM"
}
}
How can i get an array of strings like so: ["B", "ZOOM"] without an instance of MyBanana?
Additionally, is there a way to get this from auto-synthesised CodingKeys, e.g.
struct MyBanana: Codable {
var b: String?
var z: String?
}
would return ["b","z"]
I tried the following for the first question:
func whatever<T, Key>(keyedBy: Key.Type) -> [T] where T: Codable, Key: CodingKey
{
let allKeys: [Key] = [Key]()
for k in keyedBy {
allKeys.append(k)
}
return [T]()
}
But i get
Type 'Key.Type' does not conform to protocol 'Sequence'
Update
For declared CodingKeys #Cameron Porter's answer works fine (add CaseIterable to the enum)
For synthesized ones this works, albeit pretty manual still:
extension Decodable {
func getAllCodingKeys(_ activator: (String) -> String) -> [String] {
let mirror = Mirror(reflecting: self)
return mirror.children.filter({ $0.label != nil }).map({ activator($0.label!) })
}
}
Then in your Codable, add
func getAllKeys() -> [String] {
return self.getAllCodingKeys { CodingKeys(stringValue: $0)!.stringValue }
}
struct MyBanana: Codable {
var b: String?
var z: String?
enum CodingKeys: String, CodingKey, CaseIterable {
case b = "B"
case z = "ZOOM"
}
static var allCases: [String] {
return CodingKeys.allCases.map { $0.rawValue }
}
}
And then you can get your array of coding keys as
MyBanana.allCases
Doesn't work for auto-synthesised coding keys however

How to declare enums in Swift of a particular class type?

I am trying to create a class and use that class as the type for my new enum like shown below.
class Abc{
var age = 25
var name = "Abhi"
}
enum TestEnum : Abc {
case firstCase
case secondCase
}
I am getting following error in playground .
error: raw type 'Abc' is not expressible by any literal
So i tried conforming to RawRepresentable protocol like this.
extension TestEnum : RawRepresentable{
typealias RawValue = Abc
init?(rawValue:RawValue ) {
switch rawValue {
case Abc.age :
self = .firstCase
case Abc.name :
self = .secondCase
}
}
var rawValue : RawValue {
switch self{
case .firstCase :
return Abc.age
case .secondCase :
return Abc.name
}
}
}
I am getting following errors after this :
error: raw type 'Abc' is not expressible by any literal
error: instance member 'age' cannot be used on type 'Abc'
error: instance member 'name' cannot be used on type 'Abc'
What is the proper way to declare enums of a certain class type, not getting clear idea on this. Anyone help?
I'm not really sure what do you want to achieve, but take a look at my implementation, approach that I use in my projects:
class Abc {
var age: Int
var name: String
init(age: Int, name: String) {
self.age = age
self.name = name
}
}
enum TestEnum {
case firstCase
case secondCase
var instance: Abc {
switch self {
case .firstCase: return Abc(age: 25, name: "John")
case .secondCase: return Abc(age: 20, name: "Marry")
}
}
}
//Usage:
let abc = TestEnum.secondCase.instance
print(abc.age) //prints '20'
From Docs
In particular, the raw-value type must conform to the Equatable
protocol and one of the following protocols:
ExpressibleByIntegerLiteral for integer literals,
ExpressibleByFloatLiteral for floating-point literals,
ExpressibleByStringLiteral for string literals that contain any number
of characters, and ExpressibleByUnicodeScalarLiteral or
ExpressibleByExtendedGraphemeClusterLiteral for string literals that
contain only a single character.
So make your class Abc to conform to Equatable and one of the above mentioned protocols. Here is an example
public class Abc : Equatable,ExpressibleByStringLiteral{
var age = 25
var name = "Abhi"
public static func == (lhs: Abc, rhs: Abc) -> Bool {
return (lhs.age == rhs.age && lhs.name == rhs.name)
}
public required init(stringLiteral value: String) {
let components = value.components(separatedBy: ",")
if components.count == 2 {
self.name = components[0]
if let age = Int(components[1]) {
self.age = age
}
}
}
public required convenience init(unicodeScalarLiteral value: String) {
self.init(stringLiteral: value)
}
public required convenience init(extendedGraphemeClusterLiteral value: String) {
self.init(stringLiteral: value)
}
}
enum TestEnum : Abc {
case firstCase = "Jack,29"
case secondCase = "Jill,26"
}
Now you can initialize your enum like
let exEnum = TestEnum.firstCase
print(exEnum.rawValue.name) // prints Jack
For detailed discussion and example you can refer
https://swiftwithsadiq.wordpress.com/2017/08/21/custom-types-as-raw-value-for-enum-in-swift/
Have a look at Associated Values.
For your example:
class Abc {
var age = 25
var name = "Abhi"
}
enum TestEnum {
case age(Int)
case name(String)
}
Then you can use it like this:
var person = Abc()
...
var value = TestEnum.age(person.age)
switch value {
case .age(let age):
print("Age: \(age).")
case .name(let name):
print("Name: \(name).")
}
And for convenience you can write extension for enum, that will take your Abc object and convert it to enum value:
static func fromAbc(_ object: Abc) -> TestEnum? {
if object.age {
return TestEnum.age(object.age)
}
if object.name {
return TestEnum.name(object.name)
}
return nil
}
Note: in func fromAbc(object: Abc) -> TestEnum? you should replace conditions in if's to something that can be expressed as Bool (age > 0, etc).
As for the row values - in the doc it is stated that
Raw values can be strings, characters, or any of the integer or floating- point number types. Each raw value must be unique within its enumeration declaration.
And I'm not sure you can fit class there.

Declaring a typealias that conforms to a protocol and isn't a specific type [duplicate]

I have a protocol, Address, which inherits from another protocol, Validator, and Address fulfills the Validator requirement in the extension.
There is another protocol, FromRepresentable, which has an associatedType (ValueWrapper) requirement which should be Validator.
Now if I try to use Address as associatedType, then it does not compile. It says,
Inferred type 'Address' (by matching requirement 'valueForDetail') is
invalid: does not conform to 'Validator'.
Is this usage illegal? Shouldn't we be able to use Address in place of Validator, as all Addresses are Validator.
Below is the piece of code I am trying.
enum ValidationResult {
case Success
case Failure(String)
}
protocol Validator {
func validate() -> ValidationResult
}
//Address inherits Validator
protocol Address: Validator {
var addressLine1: String {get set}
var city: String {get set}
var country: String {get set}
}
////Fulfill Validator protocol requirements in extension
extension Address {
func validate() -> ValidationResult {
if addressLine1.isEmpty {
return .Failure("Address can not be empty")
}
return .Success
}
}
protocol FormRepresentable {
associatedtype ValueWrapper: Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
// Shipping Address conforming to Address protocol.
// It should also implicitly conform to Validator since
// Address inherits from Validator?
struct ShippingAddress: Address {
var addressLine1 = "CA"
var city = "HYD"
var country = "India"
}
// While compiling, it says:
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform
// to 'Validator'.
// But Address confroms to Validator.
enum AddressFrom: Int, FormRepresentable {
case Address1
case City
case Country
func valueForDetail(valueWrapper: Address) -> String {
switch self {
case .Address1:
return valueWrapper.addressLine1
case .City:
return valueWrapper.city
case .Country:
return valueWrapper.country
}
}
}
Update: Filed a bug.
The problem, which David has already alluded to, is that once you constrain a protocol's associatedtype to a specific (non #objc) protocol, you have to use a concrete type to satisfy that requirement.
This is because protocols don't conform to themselves – therefore meaning that you cannot use Address to satisfy the protocol's associated type requirement of a type that conforms to Validator, as Address is not a type that conforms to Validator.
As I demonstrate in my answer here, consider the counter-example of:
protocol Validator {
init()
}
protocol Address : Validator {}
protocol FormRepresentable {
associatedtype ValueWrapper: Validator
}
extension FormRepresentable {
static func foo() {
// if ValueWrapper were allowed to be an Address or Validator,
// what instance should we be constructing here?
// we cannot create an instance of a protocol.
print(ValueWrapper.init())
}
}
// therefore, we cannot say:
enum AddressFrom : FormRepresentable {
typealias ValueWrapper = Address
}
The simplest solution would be to ditch the Validator protocol constraint on your ValueWrapper associated type, allowing you to use an abstract type in the method argument.
protocol FormRepresentable {
associatedtype ValueWrapper
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
enum AddressFrom : Int, FormRepresentable {
// ...
func valueForDetail(valueWrapper: Address) -> String {
// ...
}
}
If you need the associated type constraint, and each AddressFrom instance only expects a single concrete implementation of Address as an input – you could use generics in order for your AddressFrom to be initialised with a given concrete type of address to be used in your method.
protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
enum AddressFrom<T : Address> : Int, FormRepresentable {
// ...
func valueForDetail(valueWrapper: T) -> String {
// ...
}
}
// replace ShippingAddress with whatever concrete type you want AddressFrom to use
let addressFrom = AddressFrom<ShippingAddress>.Address1
However, if you require both the associated type constraint and each AddressFrom instance must be able to handle an input of any type of Address – you'll have use a type erasure in order to wrap an arbitrary Address in a concrete type.
protocol FormRepresentable {
associatedtype ValueWrapper : Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
struct AnyAddress : Address {
private var _base: Address
var addressLine1: String {
get {return _base.addressLine1}
set {_base.addressLine1 = newValue}
}
var country: String {
get {return _base.country}
set {_base.country = newValue}
}
var city: String {
get {return _base.city}
set {_base.city = newValue}
}
init(_ base: Address) {
_base = base
}
}
enum AddressFrom : Int, FormRepresentable {
// ...
func valueForDetail(valueWrapper: AnyAddress) -> String {
// ...
}
}
let addressFrom = AddressFrom.Address1
let address = ShippingAddress(addressLine1: "", city: "", country: "")
addressFrom.valueForDetail(AnyAddress(address))
You've got a several issues:
First of all, you don't actually declare that Address implements Validator
//Address inherits Validator
protocol Address : Validator {
var addressLine1: String {get set}
var city: String {get set}
var country: String {get set}
}
And you don't declare the associated type for ValueWrapper:
typealias ValueWrapper = ShippingAddress
And you seem to actually be wanting AddressFrom.valueForDetail to take a ShippingAddress:
func valueForDetail(valueWrapper: ShippingAddress) -> String {
switch self {
case .Address1:
return valueWrapper.addressLine1
case .City:
return valueWrapper.city
case .Country:
return valueWrapper.country
}
}
Altogether, it looks like:
enum ValidationResult {
case Success
case Failure(String)
}
protocol Validator {
func validate() -> ValidationResult
}
//Address inherits Validator
protocol Address : Validator {
var addressLine1: String {get set}
var city: String {get set}
var country: String {get set}
}
////Fulfill Validator protocol requirements in extension
extension Address {
func validate() -> ValidationResult {
if addressLine1.isEmpty {
return .Failure("Address can not be empty")
}
return .Success
}
}
protocol FormRepresentable {
associatedtype ValueWrapper: Validator
func valueForDetail(valueWrapper: ValueWrapper) -> String
}
// Shipping Address conforming to Address protocol.
// It should also implicity conform to Validator since
// Address inherits from Validator?
struct ShippingAddress: Address {
var addressLine1 = "CA"
var city = "HYD"
var country = "India"
}
// While compiling, it says:
// Inferred type 'Address' (by matching requirement 'valueForDetail') is invalid: does not conform
// to 'Validator'.
// But Address confroms to Validator.
enum AddressFrom: Int, FormRepresentable {
case Address1
case City
case Country
// define associated type for FormRepresentable
typealias ValueWrapper = ShippingAddress
func valueForDetail(valueWrapper: ShippingAddress) -> String {
switch self {
case .Address1:
return valueWrapper.addressLine1
case .City:
return valueWrapper.city
case .Country:
return valueWrapper.country
}
}
}