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]()
Related
I'm trying to create public interface for my class operating on generics.
I've created interface
public protocol StorageProtocol {
associatedtype StorageObject: Codable
func store(storeObject: StorageObject)
func get() -> StorageObject?
}
and implementation
public class Storage<T: Codable>: StorageProtocol {
public func store(storeObject: T) {}
public func get() -> T?
Now, when I try to create instance it forces me any keyword
let myStorage: any StorageProtocol = Storage<Credentials>()
and I can't call storage.store(storeObject: Credentials()) as I'm getting error:
Associated type 'StorageObject' can only be used with a concrete type or generic parameter base.
What I'm missing here?
The compiler does not know what type to require in the parameter storeObject when you constrain myStorage to be any StoreageProtocol, as the generic StorageObject could be any Codable object. A workaround could be to explicitly add a parameter for the type of the generic.
public protocol StorageProtocol<StorageObject> { // add a generic parameter
associatedtype StorageObject: Codable
func store(storeObject: StorageObject)
func get() -> StorageObject?
}
and then when you define the variable
let myStorage: any StorageProtocol<Credentials> = Storage<Credentials>()
Then the compiler will know you want to store Credentials for this variable and allow you to call store as you specialized the protocol to the correct type.
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]()
I'm trying to add an extension to Observable.
The code looks like this:
extension Observable where Element == ApiResponse<ItemsContainer<T>>, T:Codable
I'm receiving the following exception: Use of undeclared type T.
So apparently this doesn't work.
The only thing missing is to constrain the generic inside ItemsContainer to conform to Codable.
Could be as simple as a syntactical issue or maybe I'm just not good enough with generics. Any help is appreciated!
Edit: To give the idea - ApiResponse and ItemsContainer look like this
public struct ApiResponse<ApiModel> {
public let data: ApiModel?
}
struct ItemsContainer<Items>: Codable where Items: Codable {
let items: [Items]
}
Issue
You cannot constraint extensions to a Model Type which holds generic values, without specifying the Model Type of the generic value.
You only constrain protocols based on their associatedtypes or generics based on their generic type, on the extension signature. Therefore T is not recognized, because none of the protocols or generic declare it.
Solution
So by keeping in mind what I said above, a Model Type needs to be fully defined on the extension context. But wait that does not satisfy our requirement, we want it to be generic!
Then we do not need a Model Type, we need a protocol!
We have two Model Types (ApiResponse and ItemsContainer) which we need to know the generic type, therefore we need two protocols for each of them.
ApiResponse
Let's create one named ApiResponseProtocol
public protocol ApiResponseProtocol {
associatedtype Model
var data: Model? { get }
}
Cool, the associatedtype Model will play our role as the generic value for the ApiModel on the object. Let's make ApiResponse conform to ApiResponseProtocol
public struct ApiResponse<ApiModel>: ApiResponseProtocol {
public let data: ApiModel?
}
Generic ApiModel here can be defined as Model from the protocol.
ItemsContainer
Next steps would be the same for the ItemsContainer
public protocol ItemsContainerProtocol {
associatedtype Item
var items: [Item] { get }
}
public struct ItemsContainer<Items>: Codable, ItemsContainerProtocol where Items: Codable {
public let items: [Items]
}
Extension
Now since we can access each of the generic types from the protocol (associatedtypes), the output would become something like this:
// This would be for example ApiResponse<ItemsContainer<Model>> where Model is a Model Type conforming to Codable
extension Observable where Element: ApiResponseProtocol, Element.Model: ItemsContainerProtocol, Element.Model.Item: Codable {}
* Short version *
How can I conform a class (extension) to a generic protocol function?
* Long version *
This is a small part of a data structure to support a paginated collection,
protocol Pageable {
//an object whose can be in a collection
}
protocol Page{ //a page of a collection that can be paginated
associatedtype PageItemType
func itemAt<PageItemType:Pageable>(index: Int) -> PageItemType
}
//Bonus question
//class PagedCollection<PageType:Page, ItemType:Pageable> {
//...
//}
Here is the implementation of the protocols with a "real" case:
class Person : Pageable{}
class People {
var people: [Person]?
}
//Mark: - Page
extension People: Page{ /*** error 1 ***/
typealias PageItemType = Person
func itemAt(index: Int) -> Person{
let person : Person = self.people![index]
return person
}
}
Obtaining the following error (1):
Type 'People' does not conform to protocol 'Page'
Protocol requires nested type 'PageItemType'
I also tried making it explicit but i just got a different error:
//Mark: - Page
extension People: Page{
typealias PageItemType = Person
func itemAt<PageItemType:Pageable>(index: Int) -> PageItemType{
let person : Person = self.people![index]
return person /*** error 2 ***/
}
}
Obtaining the following error (2):
Cannot convert return expression of type 'Person' to return type 'PageItemType'
So: *How can i let itemAt function return a valid type for the PageItemType typealias?
* Bonus *
Bonus question worth a 50 bounty (if answer is longer than a row i'll open a new question):
Referring to the first code snippet PagedCollection
given that each Page implementation has always a known implementation of Pageable protocol objet type
is there a way to avoid declaring ItemType:Pageable ? Or at least enforce it with a where clause?
It looks like you're conflating associated types with generic functions.
Generic functions allow you to provide a type to replace a given generic placeholder at the call site (i.e when you call the function).
Associated types allow types conforming to protocols to provide their own type to replace a given placeholder type in the protocol requirements. This is done per type, not at the call site of any function. If you wish to enforce a conformance requirement for an associatedtype, you should do so directly on its declaration (i.e associatedtype PageItemType : Pageable).
If I understand your requirements correctly, your itemAt(index:) function should be non-generic (otherwise the associatedtype in your protocol is completely redundant). The type that it returns is defined by the implementation of the type that conforms to Page, rather than the caller of the function. For example, your People class defines that the PageItemType associated type should be a Person – and that is what itemAt(index:) should return.
protocol Pageable {/* ... */}
protocol Page {
// any conforming type to Page will need to define a
// concrete type for PageItemType, that conforms to Pageable
associatedtype PageItemType : Pageable
// returns the type that the implementation of the protocol defines
// to be PageItemType (it is merely a placeholder in the protocol decleration)
func itemAt(index: Int) -> PageItemType
}
class Person : Pageable {/* ... */}
class People {
var people: [Person]?
}
extension People : Page {
// explicitly satisfies the Page associatedtype requirement.
// this can be done implicitly be the itemAt(index:) method,
// so could be deleted (and annotate the return type of itemAt(index:) as Person)
typealias PageItemType = Person
// the itemAt(index:) method on People now returns a Person
func itemAt(index: Int) -> PageItemType {
// I would advise against force unwrapping here.
// Your people array most likely should be non-optional,
// with an initial value of an empty array
// (do you really need to distinguish between an empty array and no array?)
let person = self.people![index]
return person
}
}
In regards to your implementation of a PagedCollection – as the PageItemType associated type in your Page protocol conforms to Pageable, I see no need for an ItemType generic parameter. This can simply be accessed through the associated type of the given PageType generic parameter.
As an example:
class PagedCollection<PageType:Page> {
// PageType's associatedtype, which will conform to Pageable.
// In the case of the PageType generic parameter being People,
// PageType.PageItemType would be of type Person
var foo : PageType.PageItemType
init(foo: PageType.PageItemType) {
self.foo = foo
}
}
// p.foo is of type Person, and is guarenteed to conform to Pageable
let p = PagedCollection<People>(foo: Person())
This is merely a formatted discussion from my chat with Hamish.
I'm writing it here, because chatrooms can get archived and sometimes a more direct QA can convey things differently
Me:
protocol Provider {
associatedtype Input
associatedtype Output
func value(forKey _key: Input) -> Output{}
}
vs.
protocol Provider{
func value<Input, Output>(forKey _key: Input) -> Output{}
}
Hamish: Essentially the difference is where the placeholders are satisfied – associated types are satisfied at type level, whereas generic placeholders on a function are satisfied at the call-site of said function.
Me: I understand that level of difference. But is there any byproduct due to that difference :D
Hamish: What do you mean? they express different things – the former is a protocol where the conforming type only deals with one specific type of input and output, the latter is a protocol where the conforming type can deal with any given input and output types.
Me: The former is a protocol where the conforming type only deals with one specific type of input and output. What do you mean by specific? Can't the typeAlias be anything I like?
Hamish: It can, but it can only be satisfied once per type – once I define
struct S : Provider {
func value(forKey: String) -> Int
}
S now can only deal with String inputs and Int outputs.
Whereas with the latter, I would define
struct S : Provider {
func value<Input, Value>(forKey _key: Input) -> Output{}
}
now S has to be able to deal with any input and output type.
I'm trying to use Protocol-Oriented Pgrogramming for model layer in my application.
I've started with defining two protocols:
protocol ParseConvertible {
func toParseObject() -> PFObject?
}
protocol HealthKitInitializable {
init?(sample: HKSample)
}
And after implementing first model which conforms to both I've noticed that another model will be basically similar so I wanted to create protocol inheritance with new one:
protocol BasicModel: HealthKitInitializable, ParseConvertible {
var value: AnyObject { get set }
}
A you can see this protocol has one additional thing which is value but I want this value to be type independent... Right now I have models which use Double but who knows what may show up in future. If I leave this with AnyObject I'm sentenced to casting everything I want to use it and if I declare it as Double there's no sense in calling this BasicModel but rather BasicDoubleModel or similar.
Do you have some hints how to achieve this? Or maybe I'm trying to solve this the wrong way?
You probably want to define a protocol with an "associated type",
this is roughly similar to generic types.
From "Associated Types" in the Swift book:
When defining a protocol, it is sometimes useful to declare one or
more associated types as part of the protocol’s definition. An
associated type gives a placeholder name (or alias) to a type that is
used as part of the protocol. The actual type to use for that
associated type is not specified until the protocol is adopted.
Associated types are specified with the typealias keyword.
In your case:
protocol BasicModel: HealthKitInitializable, ParseConvertible {
typealias ValueType
var value: ValueType { get set }
}
Then classes with different types for the value property can
conform to the protocol:
class A : BasicModel {
var value : Int
func toParseObject() -> PFObject? { ... }
required init?(sample: HKSample) { ... }
}
class B : BasicModel {
var value : Double
func toParseObject() -> PFObject? { ... }
required init?(sample: HKSample) { ... }
}
For Swift 2.2/Xcode 7.3 and later, replace typealias in the
protocol definition by associatedtype.