I'm trying to create a protocol that has a static method that returns a generic type. In most cases what I have seems to work reasonably well. The challenge comes in when I want to use an extension to return this generic value. Here's what I have. This code can be placed into a playground.
Here's the first protocol I want which contains the associatedtype
protocol AWSerializable {
associatedtype T
static func deserialize(dictionary: [String : Any]) -> T?
func serialize() -> [String : Any]
}
Then I created another protocol which allows me to create instances that do the action of deserializing:
protocol AWDeserializer {
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> T?
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> [T]?
}
Here is an example that happily implements the AWSerializable
protocol:
class FooBar: AWSerializable {
typealias T = FooBar
var foo = ""
var bar = ""
static func deserialize(dictionary: [String : Any]) -> FooBar? {
let fooBar = FooBar()
fooBar.foo = (dictionary["foo"] as? String) ?? ""
fooBar.bar = (dictionary["bar"] as? String) ?? ""
return fooBar
}
func serialize() -> [String : Any] {
var serialized = [String : Any]()
serialized["foo"] = foo
serialized["bar"] = bar
return serialized
}
}
So far so good. The challenge comes in when I want to create an extension
on UserDefaults
to implement the AWDeserializer
protocol.
extension UserDefaults: AWDeserializer {
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> T? {
if let serialized = UserDefaults.standard.object(forKey: key) as? [String : Any] {
return T.deserialize(dictionary: serialized)
}
return nil
}
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> [T]? {
if let data = UserDefaults.standard.array(forKey: key) as? [[String : Any]] {
var values = [T]()
for entry in data {
if let value = T.deserialize(dictionary: entry) {
values.append(value)
}
}
return values
}
return nil
}
}
The problem here is with T.deserialize(dictionary: serialized)
. I get the following error:
This is easily fixed by applying the suggested solution, or preferably by changing the line to return T.deserialize(dictionary: serialized) as? T
But I don't like the fact that this optional cast is required to begin with. Is there a way to define the protocols that doesn't require this cast?
Maybe it's much better to use this protocol AWDeserializer
:
protocol AWDeserializer {
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> T? where T.T == T
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> [T]? where T.T == T
}
Instead of:
protocol AWDeserializer {
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> T?
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> [T]?
}
And here is the rest of code:
protocol AWSerializable {
associatedtype T
static func deserialize(dictionary: [String : Any]) -> T?
func serialize() -> [String : Any]
}
protocol AWDeserializer {
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> T? where T.T == T
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> [T]? where T.T == T
}
class FooBar: AWSerializable {
typealias T = FooBar
var foo = ""
var bar = ""
static func deserialize(dictionary: [String : Any]) -> FooBar? {
let fooBar = FooBar()
fooBar.foo = (dictionary["foo"] as? String) ?? ""
fooBar.bar = (dictionary["bar"] as? String) ?? ""
return fooBar
}
func serialize() -> [String : Any] {
var serialized = [String : Any]()
serialized["foo"] = foo
serialized["bar"] = bar
return serialized
}
}
extension UserDefaults: AWDeserializer {
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> T? where T.T == T {
if let serialized = UserDefaults.standard.object(forKey: key) as? [String : Any] {
return T.deserialize(dictionary: serialized)
}
return nil
}
func deserializeWithKey<T: AWSerializable>(_ key: String!) -> [T]? where T.T == T {
if let data = UserDefaults.standard.array(forKey: key) as? [[String : Any]] {
var values = [T]()
for entry in data {
if let value = T.deserialize(dictionary: entry) {
values.append(value)
}
}
return values
}
return nil
}
}