2

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:

enter image description here

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?

jervine10
  • 3,039
  • 22
  • 35

1 Answers1

2

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
    }
}
beeth0ven
  • 1,857
  • 15
  • 18
  • This is a possibility and I did have this originally. I ran into 2 problems. The first is I can't implement this protocol on an Extension of a class. The `init` requirement makes this impossible as far as I know. The other problem is when extending a class the `init` method can have some side effects with required initializers in the super class. – jervine10 Sep 29 '16 at 15:06
  • 1
    If so the `as? T` is required, Because `AWSerializable.T` and `AWSerializable` maybe different Type, Use this just cast `AWSerializable.T` to `AWSerializable`. – beeth0ven Sep 29 '16 at 15:18
  • Is this the only way to declare these protocols? Is there a better way to declare `AWDeserializer` so that cast is not required? – jervine10 Sep 29 '16 at 15:23
  • Or you can constrain this to the function, Answer updated. – beeth0ven Sep 29 '16 at 15:27
  • That's exactly what I was looking for. Perfect! Thank you! – jervine10 Sep 29 '16 at 15:59