Cannot use protocol to define common initializers - swift

I'm trying to create a generic class that is able to convert models to their associated view models. Unfortunately the compiler does not allow it and fails with the following error:
Cannot convert value of type 'Model' (generic parameter of generic class 'Store') to expected argument type 'ViewModel.Model' (associated type of protocol 'StoreViewModel')
I'm using the following simplified code:
protocol StoreViewModel {
associatedtype Model
init(model: Model)
}
class Store<Model, ViewModel: StoreViewModel> {
var models = [Model]()
var results = [ViewModel]()
func update() {
results = models.map {
ViewModel(model: $0)
// Cannot convert value of type 'Model' (generic parameter of generic class 'Store') to expected argument type 'ViewModel.Model' (associated type of protocol 'StoreViewModel')
}
}
}
class Foo: NSManagedObject {}
class FooViewModel: StoreViewModel {
var model: Foo
required init(model: Foo) {
self.model = model
}
}
let store = Store<Foo, FooViewModel>()
I've already read about type erasure, but I'm wondering of there is no simpler solution for this problem.

As it is, your current code allows things like:
let store = Store<Int, FooViewModel>()
Clearly that makes no sense, but your code allows it because there is no constraint on Model. Model can be anything, can't it?
Let's put a constraint on:
class Store<Model, ViewModel: StoreViewModel> where Model == ViewModel.Model {
Now we see that there is no point of having two generic parameters! Why use Model, when we can just use ViewModel.Model?
class Store<ViewModel: StoreViewModel> {
var models = [ViewModel.Model]()
or, if you hate long type names, use a typealias:
typealias Model = ViewModel.Model
var models = [Model]()

Related

Swift Initialize struct based on typealias

I want my code to be as reusable as it can be. Writer and JsonProperties are Protocols that define plenty of the functionality the related objects require. JsonProperties conforms to Codable protocol, but I want to implement a custom method for Core Data implementation, through a Writer, the question is:
Can I initialize an object that implements a Writer protocol through the typealias of JsonProperties?
Right now I'm getting this error:
Cannot convert value of type '[Model]' to expected argument type '[Model.WriterType.Model]'
Here's the code:
protocol Writer {
associatedtype Model: JsonProperties
...
init(in dataStack: DataStack)
}
struct GenericWriter<Model: JsonProperties>: Writer { ... }
protocol JsonProperties: Codable {
associatedtype WriterType: Writer
...
}
struct ConversationProperties: JsonProperties {
typealias WriterType = GenericWriter<Self>
...
}
The implementation I was looking for, but got the error was:
func networkFetch<Model: JsonProperties>(type: Model.Type) -> AnyPublisher<Bool, Error> {
let writer = Model.WriterType(in: dataStack)
...
var objects = [Model]()
...
writer.insert(objects) <- Error here!
My guess this is not the correct implementation of the init() of a typealias struct.
The problem you're seeing stems from the fact that you not haven't constrained the associate type WriterType within JsonProperties.
Currently, it accepts any WriterType type conforming to Writer, regardless of what its Model is.
What you probably want is for the WriterType type to have its Model be the same as the type being conformed to JsonProperties protocol - so you need to constrain it:
protocol JsonProperties: Codable {
associatedtype WriterType: Writer where WriterType.Model == Self
}
The insert method accepts a [Model], where Model is the associated type of Writer.
writer is of type Model.WriterType where WriterType is a Writer, so writer.insert accepts Model.WriterType.Model. Here, the first Model is the generic parameter of networkFetch, whereas the second Model is the associated type of the Writer protocol.
However, you have created a [Model]. This Model refers to the generic parameter of networkFetch, not the associated type.
There is actually no guarantee that your generic parameter Model is the same type as Model.WriterTypeModel.Model. For example, I could do:
struct FooProperties: JsonProperties {
typealias WriterType = GenericWriter<ConversationProperties>
}
And if I pass FooProperties.self to networkFetch, the generic parameter Model would be FooProperties, but Model.WriterType.Model would be ConversationProperties.
There are many ways to fix this.
You can constrain the WriterType associated type to forbid me from creating a FooProperties in the first place:
protocol JsonProperties: Codable {
associatedtype WriterType: Writer where WriterType.Model == Self
}
You can constraint the generic parameter Model:
func networkFetch<Model: JsonProperties>(type: Model.Type) -> AnyPublisher<Bool, Error>
where Model.WriterType.Model == Model {
You can create an array of Model.WriterType.Model instead (this will not work if you are deserialising objects of type Model and putting them into this array)
var objects = [Model.WriterType.Model]()

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.

Generic constrained type default value

Consider the following code:
protocol JSONParserType {
associatedtype Element
}
// MARK: - Entities
struct Item {}
// MARK: - Parsers
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init(innerParser: T = InnerParser()) {
self.innerParser = innerParser
}
}
struct InnerParser: JSONParserType {
typealias Element = Item
}
The OuterParser has a child parser that should be constrained to a specific type. Unfortunately providing a default value in the initializer (or in the property definition itself) does lead to the compiler throwing a "Default argument value of type 'InnerParser' cannot be converted to type 'T'".
If I remove the default value assignment and just instantiate the OuterParser providing the InnerParser explicitly, everything is fine.
let outerParser = OuterParser(innerParser: InnerParser())
My question is what's the reason that the approach providing a default value that actually meets the constraints does not work.
The problem is that the actual type of T isn't defined by the class – it's defined by the code that uses the class. It will therefore be defined before you do anything in your class (at either instance or static level). You therefore can't assign InnerParser to T, as T has already been defined to be a given type by that point, which may well not be InnerParser.
For example, let's consider that you have another parser struct:
struct AnotherParser: JSONParserType {
typealias Element = Item
}
and let's assume that your current code compiles. Now consider what would happen when you do this:
let parser = OuterParser<AnotherParser>()
You've defined the generic type to be AnotherParser – but the initialiser will try to assign InnerParser to your property (now of type AnotherParser). These types don't match, therefore it cannot possibly work.
Following the same logic, this implementation also won't work:
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init() {
self.innerParser = InnerParser()
}
init(innerParser: T) {
self.innerParser = innerParser
}
}
As there's no guarantee that the generic type T will be the same type as InnerParser. Sure, you can force downcast to T – but that'll just make you code crash if the types aren't compatible.
Unfortunately, there's no real clean solution to this problem. I think the best your best option is probably to create two factory methods for creating your OuterParser instance.
enum Parser {
static func createParser() -> OuterParser<InnerParser> {
return OuterParser(innerParser:InnerParser())
}
static func createParser<T>(innerParser:T) -> OuterParser<T> {
return OuterParser(innerParser:innerParser)
}
}
let innerParser = Parser.createParser() // OuterParser<InnerParser>
let anotherParser = Parser.createParser(AnotherParser()) // OuterParser<AnotherParser>
We're using an caseless enum here to avoid polluting the global namespace with extra functions.
Although this isn't very Swifty, and for that reason I would also recommend maybe rethinking your logic for how you define your parsers.
type T more like a child protocol of JSONParserType you can convert it:
init(innerParser: T = InnerParser() as! T) {
self.innerParser = innerParser
}

Specify a settable property/variable in a protocol

I would like my protocol to declare that there is a read/write property available. I have attempted it, but this does not work:
protocol EdibleThing {
var eaten: Bool { get set }
}
class Pickle: EdibleThing { var eaten = false }
class RusticGrapefruit: EdibleThing { var eaten = false }
class Jar {
let contents: [EdibleThing] = [Pickle(), RusticGrapefruit()]
var nextItem: EdibleThing {
return contents.last ?? Pickle() // Lazy pickle generation technology
}
func eat() {
let food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
}
What am I doing wrong? I think I've declared that the protocol has a get/set var called eaten, so why can't I set it?
The protocol might be implemented by either classes and structs - that prevents you from changing the internal status of an instance of a class or struct implementing that protocol using an immutable variable.
To fix the problem you have to either declare the food variable as mutable:
func eat() {
var food = nextItem
food.eaten = true // (!) ERROR: Cannot assign to result of this expression
}
or declare the EdibleThing protocol to be implementable by classes only:
protocol EdibleThing : class {
var eaten: Bool { get set }
}
Note that this happens because food is a variable of EdibleThing type - the compiler doesn't know if the actual instance is a value or reference type, so it raises an error. If you make it a variable of a class type, like this:
let food: Pickle = nextItem as! Pickle
the compiler knows without any ambiguity that it's a reference type, and in that case it allows the assignment. But I guess that breaks your app logic... so consider it just as an example
You're mutating food.
Replace let food = nextItem with var food = nextItem
The problem is that you can't mutate a property on a value type defined by let.
Even though both of RusticGrapefruit and Pickle are class implementations (reference types), the protocol could be assigned to a value type like a struct. The compiler detects a potential problem and stops us.
Two solutions:
Change let to var (in my case, this would mean changing a lot of code that refers to objects of this type. Also, I like the semantic value and possible compiler optimizations from let)
Declare the protocol as only valid for classes: protocol EdibleThing: class { }

Calling a method from a struct within the same class

I'd like to logically organize class properties to signify that they are one logical unit and to distinguish them from other class properties that are less tightly related.
I thought of doing this using a struct within my class. However, it seems that I can't call class methods from the struct property setters. I get what seems to be an inappropriate compile error: "missing argument for parameter #1 in call"
This seems to be different from calling method from struct in swift
where the function is within the struct. In my case, the methods are generic and don't just apply to my struct but to all class properties. Therefore, I don't want to move them within the struct.
Do you have ideas on how to organize properties into tight(er) logical units within classes?
class MyClass {
struct MyStruct {
static var aVarInMyStruct: String? {
didSet {
anotherVarInMyStruct = foo() // This gives compile error "missing argument for parameter #1 in call"
}
}
static var anotherVarInMyStruct: String?
}
func foo() {
println("I am foo()")
}
}
The inner type MyStruct knows nothing about its outer type MyClass. So there is no foo function to call from MyStruct. To better organize your code I suggest you to use // MARK: - whatever this section is comments. Types are not here to organize codes. Types are here to create right abstractions for the program.
I fix your bug:
class MyClass {
struct MyStruct {
static var aVarInMyStruct: String? {
didSet {
anotherVarInMyStruct = MyClass.foo() // This gives compile error "missing argument for parameter #1 in call"
}
}
static var anotherVarInMyStruct: String?
}
static func foo()->String {
println("I am foo()")
return "it is ok"
}
}