Swift: Is it possible to type erase a return type for use in a collection - swift

So here's something that's been bugging me for a while and I've been trying to find a good pattern to use. The problem occurs when I need to create a list of items that conform to a protocol, that has an associated type. For example:
protocol Setting {
associatedtype Value
var value: Value
}
struct ProjectSetting<T>: Setting {
var value: T
}
let setting1 = ProjectSetting(value: 1)
let setting1 = ProjectSetting(value: "abc")
The problem occurs when then trying to store ProjectSetting instances in an array.
let settings: /* ??? */ = [ProjectSetting(value: 1), ProjectSetting(value: "abc")]
Swift won't let me do let settings: [Setting] = ... because of the associated type, and it won't let me do let settings: [ProjectSetting<Any>] = ... either.
So I need to do some sort of type erasure to hide the type of the setting, but everything I've tried ends up needing the generic type exposed. I've tried to type erase by wrapping closures but I end up either exposing an Any or the generic type again.
Does anyone have a technique for wrapping a generic protocol so that it can be stored in an array regardless of the type being used?

Hopefully the following approach fits your needs. I did it only for getter just to simplify the demo, but the idea should be clear.
Note: used Xcode 11.2 / Swift 5.1 / Catalina
So here is your original entities
protocol Setting {
associatedtype Value
var value: Value { get }
}
struct ProjectSetting<T>: Setting {
let value: T
}
Now we need some helper protocols to hide your type differences, aka type erasers
private protocol TypeErasing {
var value: Any { get }
}
private struct TypeEraser<V: Setting>: TypeErasing {
let orinal: V
var value: Any {
return self.orinal.value
}
}
Now the core entity that wraps your concrete implementors holding different type values, but still allows to use those values and be stored in standard containers
struct AnySetting : Setting {
typealias Value = Any
private let eraser: TypeErasing
init<V>(_ setting: V) where V:Setting {
eraser = TypeEraser(orinal: setting)
}
var value: Any {
return eraser.value
}
}
Now testing your expectation
let settings = [AnySetting(ProjectSetting(value: 1)), AnySetting(ProjectSetting(value: "abc"))]
if let value = settings[0].value as? Int {
print("Stored value: \(value)")
}
if let value = settings[1].value as? String {
print("Stored value: \(value)")
}
PlayGround output
Stored value: 1
Stored value: abc

Related

Is there a way to compile with a generic type?

This is something that has vexed a number of developers including myself. Let say we have a protocol that defines a subscript which we apply to a simple class.
protocol Cache {
subscript<Value>(_: String) -> Value? { get set }
}
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript<Value>(key: String) -> Value? {
get {
cache[key] as? Value
}
set {
if let value = newValue {
cache[key] = value
} else {
cache.remove(key)
}
}
}
}
This works fine as long as we know the types:
cache["abc"] = 5
let x: Int? = cache["abc"]
but the developers want to do this:
cache["abc"] = nil
Which won't compile because the compiler cannot determine the Value generic type. This works however
cache["abc"] = nil as String?
I've tried a number of things but they all have drawbacks. Things like adding a second subscript with the Any type. Nothing seems to work well even though it would seem like a simple problem.
Has anyone found a solution that handles cache["abc"] = nil?
You can do this by changing your protocol requirements somewhat.
Have the protocol require a subscript that does not use generics, and returns an Any?.
protocol Cache {
subscript(key: String) -> Any? { get set }
}
This subscript will let you do the following:
cache["abc"] = 5
cache["abc"] = nil
let value = cache["abc"] // value is an `Any?`
but it will not let you do this:
let number: Int? = cache["abc"] // error
So, let's fix that by adding another subscript to Cache. This subscript is equivalent to your original subscript requirement, except it doesn't need a setter and will call the other subscript (the one required by the protocol):
extension Cache {
subscript<Value>(key: String) -> Value? {
self[key] as? Value
}
}
(If you're worried that this subscript calls itself, don't be. self[key] here actually calls the other subscript, not this one. You can confirm this in Xcode by command-clicking on the [ or the ] in self[key] to jump to the definition of the other subscript.)
Then, implement the required subscript in your class:
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript(key: String) -> Any? {
get { cache[key] }
set { cache[key] = newValue }
}
}
This will allow all of the following to compile:
let cache = InMemoryCache()
cache["abc"] = 5
let x: Int? = cache["abc"]
cache["abc"] = nil
There is a workaround to have your desire output.
Because this is a dictionary so you get assign nil directly in your InMemoryCache
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript<Value>(key: String) -> Value? {
get {
cache[key] as? Value
}
set {
if let value = newValue {
cache[key] = value
} else {
cache[key] = nil // make nil directly here
}
}
}
}
In here because of Value is a generic type. So you can not assign nil directly. It must have a specific type.
Instead you can do like this
let nilValue : Int? = nil // any type nil you want
cache["abc"] = nilValue
or directly cast it to nil of any tupe before assign to dictionary
cache["abc"] = (nil as String?)
It will refresh anything value is store in the key.
Example
// value
let nilValue : Int? = nil
var number : Int? = nil
var string : String? = nil
cache["abc"] = 5
number = cache["abc"] // Optional.some(5)
cache["abc"] = "abc"
number = cache["abc"] // nil
string = cache["abc"] // Optional.some("abc")
cache["abc"] = nilValue
number = cache["abc"] // nil
string = cache["abc"] // nil
The reason why you are having a hard time with this is because
cache["abc"] = nil
cannot be compiled. There is not enough information to infer the generic type of the subscript - or of the optional value. The compiler sees something like
cache<?>["abc"] = Optional<?>.none
How is it supposed to figure out what to put in place of the question marks?
There's another ambiguity. Your cache can contain any type, even Optional. When you are assigning nil to the subscript, how does anybody know if you want to remove the element or store an instance of Optional<Something>.none at the subscript?
When I find myself fighting the language in this way, I usually try to take a step back and ask if I am perhaps doing something fundamentally bad. I think, in this case, the answer is yes. You are trying to pretend something is more strictly typed than it really is.
I think your getter/setter should explicitly take a value that is of type Any. It works better and it has the advantage that it explicitly documents for the user that a Cache conforming type can store anything in it.
For this reason, I would say TylerP's solution is the best. However, I would not create a subscript in the extension, I would define a function
extension Cache
{
func value<Value>(at key: String) -> Value?
{
self[key] as? Value
}
}
The reason for this is that the compiler can get confused when you have multiple subscripts with similar signatures. With the extension above, I can conform Dictionary<String, Any> to the protocol and not need a new class.
extension Dictionary: Cache where Key == String, Value == Any {}
var dict: [String : Any] = [:]
dict["abc"] = 5
let y: Int? = dict.value(at: "abc")
dict["abc"] = nil
Obviously, the above won't be useful to you if you need reference semantics for your cache.
TylerP's solution was pretty much bang on the money. For completeness though, here's what the code now looks like:
protocol Cache {
/// Handles when we need a value of a specific type.
subscript<Value>(_: String) -> Value? { get }
/// Handles getting and setting any value.
/// The getter is rarely used because the generic getter above
/// is used. Setting a value compiles because we don't care what
/// type is it. Setting a `nil` also compiles for the same reason.
subscript(_: String) -> Any? { get set }
}
class InMemoryCache: Cache {
private var cache: [String: Any] = [:]
subscript(key: String) -> Any? {
get { cache[key] }
set {
if let value = newValue {
cache[key] = value
} else {
remove(key)
}
}
}
subscript<Value>(key: String) -> Value? {
cache[key] as? Value
}
}

Swift: Generic's type protocol not being recognized

Long time listener, first time caller.
I'm getting the following error:
Cannot convert value of type MyClass<Model<A>, OtherClass> to expected argument type MyClass<Protocol, OtherClass>
Despite the fact that MyClass<T> conforms to Protocol
I've attached a snippet that can be run in Playgrounds that resembles what I am actually trying to achieve.
protocol DisplayProtocol {
var value: String { get }
}
class DataBundle<T: CustomStringConvertible>: DisplayProtocol {
var data: T
var value: String {
return data.description
}
init(data: T) {
self.data = data
}
}
class Mapper<DisplayProtocol, Data> {
// ...
}
class MapperViewModel<Data> {
let mapper: Mapper<DisplayProtocol, Data>
init(mapper: Mapper<DisplayProtocol, Data>) {
self.mapper = mapper
}
}
let dataBundle = DataBundle<Int>(data: 100)
let mapper = Mapper<DataBundle<Int>, Bool>()
let viewModel = MapperViewModel<Bool>(mapper: mapper) // <- This fails w/error
Is this the expected behavior? If it is it feels like its breaking the contract of allowing me to have the DisplayProtocol as a type in Mapper.
This is caused by the fact that Swift generics are invariant in respect to their arguments. Thus MyClass<B> is not compatible with MyClass<A> even if B is compatible with A (subclass, protocol conformance, etc). So yes, unfortunately the behaviour is the expected one.
In your particular case, if you want to keep the current architecture, you might need to use protocols with associated types and type erasers.

How to reduce Swift enum conversion code

I have a variety of enums such as below.
enum PaperOrientation : Int { case portrait, landscape }
enum MetricType : Int { case inches, metric }
I made the enums of type Int, so that the values of instances could be saved as numbers with CoreData.
When retrieving the values from CoreData to use in the program, I end up with very similar conversion routines, like those shown below.
Typically, I want some default value - such as for the case where it is a new enum for the latest version of the program, and a value for that variable may not actually have been saved in CoreData. For example, the MetricType was added for the second rev of the program. Retrieving a paper created in rev 1 will not have a metric value saved. For the nil value, I want to use a default value the paper was originally assumed to have.
class ConversionRoutine {
class func orientationFor(_ num: NSNumber?) -> PaperOrientation {
if let iVal = num?.intValue {
if let val = PaperOrientation(rawValue: iVal) {
return val
}
}
return PaperOrientation(rawValue: 0)!
}
class func metricTypeFor(_ num: NSNumber?) -> MetricType {
if let iVal = num?.intValue {
if let val = MetricType(rawValue: iVal) {
return val
}
}
return MetricType(rawValue: 0)!
}
}
Is there a way to reduce the redundancy?
I present a way below that works pretty well. But welcome more refinements or improvements.
The Swift 4 example below uses a Defaultable protocol based on RawRepresentable. The first step is creating a defaultValue that can be used when the initializer fails. Note that the Defaultable protocol is not limited to Int enums. A String enum could also use it.
protocol Defaultable : RawRepresentable {
static var defaultValue : Self { get }
}
protocol IntDefaultable : Defaultable where RawValue == Int {
}
extension IntDefaultable {
static func value(for intValue : Int) -> Self {
return Self.init(rawValue: intValue) ?? Self.defaultValue
}
static func value(for num : NSNumber?) -> Self {
if let iVal = num?.intValue {
return self.value(for: iVal)
}
return Self.defaultValue
}
}
After the Defaultable protocol is defined, I can create an IntDefaultable protocol that will be used for Int enums.
In an extension to IntDefaultable, I can create the generic code to handle the conversion. First, I create a function that takes an Int. Then I create a function that takes an NSNumber optional.
Next, look at how one of the enums is built:
enum MetricType : Int, Codable, IntDefaultable { case inches, metric
static var defaultValue: MetricType = .inches
}
I also decided to declare the enum Codable, which may be useful. When I add the IntDefaultable protocol, it becomes fairly easy to add the defaultValue line of code with code completion - go to the new line and type “def”-tab, then “ = .”, and then choose one of the values from the popup. Note that often I want to pick the first enum value, but the default value could be any one.
The last thing is calling the conversion routine for getting a value from CoreData
let units = MetricType.value(for: self.metricType) // where self.metricType is the NSManagedObject member.
You can add an initializer in enum.
enum PaperOrientation : Int {
case portrait, landscape
init(number: NSNumber) {
self = PaperOrientation(rawValue: number.intValue) ?? .portrait
}
}

Reference Types/Subclassing, and Changes to Swift 4 Codable & encoder/decoders

I'm struggling to understand class/reference type behavior and how this relates to changes as I try to upgrade and reduce code using Codable in Swift 4.
I have two classes – a SuperClass with all of the data that will be persistent and that I save to UserDefaults (a place name & string with coordinates), and a SubClass that contains additional, temporary info that I don't need (weather data for the SuperClass coordinates).
In Swift 3 I used to save data like this:
func saveUserDefaults() {
var superClassArray = [SuperClass]()
// subClassArray is of type [SubClass] and contains more data per element.
superClassArray = subClassArray
let superClassData = NSKeyedArchiver.archivedData(withRootObject: superClassArray)
UserDefaults.standard.set(superClassData, forKey: " superClassData")
}
SuperClass conformed to NSObject & NSCoding
It also included the required init decoder & the encode function.
It all worked fine.
In trying to switch to Swift 4 & codable I've modified SuperClass to conform to Codable.
SuperClass now only has one basic initializer and none of the encoder/decoder stuff from Swift 3. There is no KeyedArchiving happening with this new approach (below). SubClass remains unchanged. Unfortunately I crash on the line where I try? encoder.encode [giving a Thread 1: EXC_BAD_ACCESS (code=1, address=0x10)]. My assumption is that the encoder is getting confused with identical reference types where one is SuperClass and one SubClass (subClassArray[0] === superClassArray[0] is true).
I thought this might work:
func saveUserDefaults() {
var superClassArray = [SuperClass]()
superClassArray = subClassArray
// assumption was that the subclass would only contain parts of the superclass & wouldn't produce an error when being encoded
let encoder = JSONEncoder()
if let encoded = try? encoder.encode(superClassArray){
UserDefaults.standard.set(encoded, forKey: " superClassArray ")
} else {
print("Save didn't work!")
}
}
Then, instead of creating an empty superClassArray, then using:
superClassArray = subClassArray, as shown above, I replace this with the single line:
let superClassArray: [SuperClass] = subClassArray.map{SuperClass(name: $0.name, coordinates: $0.coordinates)}
This works. Again, assumption is because I'm passing in the values inside of the class reference type & haven't made the superClassArray = subClassArray. Also, as expected, subClassArray[0] === superClassArray[0] is false
So why did the "old stuff" in Swift 3 work, even though I used the line superClassArray = subClassArray before the let superClassData = NSKeyedArchiver.archivedData(withRootObject: superClassArray)
? Am I essentially achieving the same result by creating the array in Swift 4 that was happening with the old Swift 3 encoder/decoder? Is the looping / recreation
Thanks!
Polymorphic persistence appears to be broken by design.
The bug report SR-5331 quotes the response they got on their Radar.
Unlike the existing NSCoding API (NSKeyedArchiver), the new Swift 4 Codable implementations do not write out type information about encoded types into generated archives, for both flexibility and security. As such, at decode time, the API can only use the concrete type your provide in order to decode the values (in your case, the superclass type).
This is by design — if you need the dynamism required to do this, we recommend that you adopt NSSecureCoding and use NSKeyedArchiver/NSKeyedUnarchiver
I am unimpressed, having thought from all the glowing articles that Codable was the answer to some of my prayers. A parallel set of Codable structs that act as object factories is one workaround I'm considering, to preserve type information.
Update I have written a sample using a single struct that manages recreating polymorphic classes. Available on GitHub.
I was not able to get it to work easily with subclassing. However, classes that conform to a base protocol can apply Codable for default encoding. The repo contains both keyed and unkeyed approaches. The simpler is unkeyed, copied below
// Demo of a polymorphic hierarchy of different classes implementing a protocol
// and still being Codable
// This variant uses unkeyed containers so less data is pushed into the encoded form.
import Foundation
protocol BaseBeast {
func move() -> String
func type() -> Int
var name: String { get }
}
class DumbBeast : BaseBeast, Codable {
static let polyType = 0
func type() -> Int { return DumbBeast.polyType }
var name:String
init(name:String) { self.name = name }
func move() -> String { return "\(name) Sits there looking stupid" }
}
class Flyer : BaseBeast, Codable {
static let polyType = 1
func type() -> Int { return Flyer.polyType }
var name:String
let maxAltitude:Int
init(name:String, maxAltitude:Int) {
self.maxAltitude = maxAltitude
self.name = name
}
func move() -> String { return "\(name) Flies up to \(maxAltitude)"}
}
class Walker : BaseBeast, Codable {
static let polyType = 2
func type() -> Int { return Walker.polyType }
var name:String
let numLegs: Int
let hasTail: Bool
init(name:String, legs:Int=4, hasTail:Bool=true) {
self.numLegs = legs
self.hasTail = hasTail
self.name = name
}
func move() -> String {
if numLegs == 0 {
return "\(name) Wriggles on its belly"
}
let maybeWaggle = hasTail ? "wagging its tail" : ""
return "\(name) Runs on \(numLegs) legs \(maybeWaggle)"
}
}
// Uses an explicit index we decode first, to select factory function used to decode polymorphic type
// This is in contrast to the current "traditional" method where decoding is attempted and fails for each type
// This pattern of "leading type code" can be used in more general encoding situations, not just with Codable
//: **WARNING** there is one vulnerable practice here - we rely on the BaseBeast types having a typeCode which
//: is a valid index into the arrays `encoders` and `factories`
struct CodableRef : Codable {
let refTo:BaseBeast //In C++ would use an operator to transparently cast CodableRef to BaseBeast
typealias EncContainer = UnkeyedEncodingContainer
typealias DecContainer = UnkeyedDecodingContainer
typealias BeastEnc = (inout EncContainer, BaseBeast) throws -> ()
typealias BeastDec = (inout DecContainer) throws -> BaseBeast
static var encoders:[BeastEnc] = [
{(e, b) in try e.encode(b as! DumbBeast)},
{(e, b) in try e.encode(b as! Flyer)},
{(e, b) in try e.encode(b as! Walker)}
]
static var factories:[BeastDec] = [
{(d) in try d.decode(DumbBeast.self)},
{(d) in try d.decode(Flyer.self)},
{(d) in try d.decode(Walker.self)}
]
init(refTo:BaseBeast) {
self.refTo = refTo
}
init(from decoder: Decoder) throws {
var container = try decoder.unkeyedContainer()
let typeCode = try container.decode(Int.self)
self.refTo = try CodableRef.factories[typeCode](&container)
}
func encode(to encoder: Encoder) throws {
var container = encoder.unkeyedContainer()
let typeCode = self.refTo.type()
try container.encode(typeCode)
try CodableRef.encoders[typeCode](&container, refTo)
}
}
struct Zoo : Codable {
var creatures = [CodableRef]()
init(creatures:[BaseBeast]) {
self.creatures = creatures.map {CodableRef(refTo:$0)}
}
func dump() {
creatures.forEach { print($0.refTo.move()) }
}
}
//: ---- Demo of encoding and decoding working ----
let startZoo = Zoo(creatures: [
DumbBeast(name:"Rock"),
Flyer(name:"Kookaburra", maxAltitude:5000),
Walker(name:"Snake", legs:0),
Walker(name:"Doggie", legs:4),
Walker(name:"Geek", legs:2, hasTail:false)
])
startZoo.dump()
print("---------\ntesting JSON\n")
let encoder = JSONEncoder()
encoder.outputFormatting = .prettyPrinted
let encData = try encoder.encode(startZoo)
print(String(data:encData, encoding:.utf8)!)
let decodedZoo = try JSONDecoder().decode(Zoo.self, from: encData)
print ("\n------------\nAfter decoding")
decodedZoo.dump()
Update 2020-04 experience
This approach continues to be more flexible and superior to using Codable, at the cost of a bit more programmer time. It is used very heavily in the Touchgram app which provides rich, interactive documents inside iMessage.
There, I need to encode multiple polymorphic hierarchies, including different Sensors and Actions. By storing signatures of decoders, it not only provides with subclassing but also allows me to keep older decoders in the code base so old messages are still compatible.

Swift 3 Generic Extension Arguments

In Swift 2.x, I had a nice little setup that allowed me to store and retrieve dictionary values using enum members:
public enum UserDefaultsKey : String {
case mainWindowFrame
case selectedTabIndex
case recentSearches
}
extension Dictionary where Key : String {
public subscript(key: UserDefaultsKey) -> Value? {
get { return self[key.rawValue] }
set { self[key.rawValue] = newValue }
}
}
This allowed me to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[.recentSearches] as? [String] {
// Populate "Recent" menu items
}
… instead of having to access values like this:
let dict = userDefaults.dictionaryForKey("SearchPrefs")
if let recentSearches = dict?[UserDefaultsKey.recentSearches.rawValue] as? [String] {
// Populate "Recent" menu items
}
Note: The use of a string literal to access the dictionary from NSUserDefaults is for example purposes only. I wouldn't actually go out of my way to use an enum for dictionary keys, only to use a string literal to access the dictionary itself. :-)
Anyway, this has worked great for my needs, and it made reading and maintaining code involving NSUserDefaults a lot more pleasant.
Since migrating my project to Swift 3, however, I'm getting the following error:
extension Dictionary where Key: String {
public subscript(key: UserDefaultsKey) -> Value? { <---- Use of undeclared type 'Value'
~~~~~~
get {
return self[key.rawValue]
}
set {
self[key.rawValue] = newValue
}
}
}
I looked at the generated headers for Dictionary, and the generic Key and Value arguments are still present in the Generic Argument Clause of the Dictionary struct, so I'm not too sure what the issue is.
Do I need to rewrite the where clause to conform to some new Swift 3 grammar I'm unaware of? Or … can one no longer access generic placeholder types in extensions?
I just don't know what to do!
My project has only 28 migration errors left to resolve. I'm so close to actually getting to use Swift 3, so I'd love any pointers (as long as they're not Unsafe and/or Raw).
Thanks!
A generic parameter of a concrete type cannot be constrained to a concrete type, currently. This means that something like
extension Dictionary where Key == String
won't compile. It's a limitation of the generics system, and it hopefully won't be a problem in Swift 4.
There is a workaround though, but it's a bit hacky:
protocol StringConvertible {
init(_ string: String)
}
extension String: StringConvertible {}
extension Dictionary where Key: StringConvertible {
subscript(key: UserDefaultsKey) -> Value? {
get { return self[Key(key.rawValue)] }
set { self[Key(key.rawValue)] = newValue }
}
}