Swift Extension to Observable with Generic Type Constraint - swift

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 {}

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]()

Protocol conforming to type with associated value

I've got the following snippet:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
var test: [MyProtocol] = []
Protocol 'MyProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Why doesn't this work? Shouldn't the where ID == UUID remove the ambiguity the error is concerned with? Am I missing something here?
I think this question is similar to this one: Usage of protocols as array types and function parameters in swift
However, I would have assumed that adding where ID == UUID should fix the problem? Why is that not the case?
Thanks!
Edit
So, this problem has occurred while experimenting with SwiftUI and struct data models. I've always used classes for any kind of data model but it seems like SwiftUI wants to get you to use structs as often as possible (I still don't see how that's realistically possible but that's why I'm experimenting with it).
In this particular case, I tried to have a manager that contains structs that all conform to MyProtocol. For example:
protocol MyProtocol: Identifiable where ID == UUID {
var id: UUID { get }
}
struct A: MyProtocol { // First data model
var id: UUID = UUID()
}
struct B: MyProtocol { // Second data model
var id: UUID = UUID()
}
class DataManager: ObservableObject {
var myData: [MyProtocol]
}
...
I don't actually have to declare Identifiable on MyProtocol but I thought it would be nicer and cleaner.
Because this is not a current feature of Swift. Once there is an associated type, there is always an associated type. It doesn't go away just because you constrain it. And once it has an associated type, it is not concrete.
There is no way to "inherit" protocols this way. What you mean is:
protocol MyProtocol {
var id: UUID { get }
}
And then you can attach Identifiable to structs that require it:
struct X: MyProtocol, Identifiable {
var id: UUID
}
(note that no where clause is required.)
There is no Swift feature today that allows you to say "types that conform to X implicitly conform to Y." There is also no Swift feature today that allows for an Array of "things that conform to Identifiable with ID==UUID." (That's called a generalized existential, and it's not currently available.)
Most likely you should go back to your calling code and explore why you require this. If you post the code that iterates over test and specifically requires the Identifiable conformance, then we may be able to help you find a design that doesn't require that.

Swift Generics - Attempting to make a generic protocol concrete fails when attempting to use specialised sub-protocol as variable

I want to know why my SomeResourceRepository is still generic, even though it is only defined in one case only, which is when I set ResourceType = SomeResource, which XCode formats as below with the where clause. Code below which shows the exact setup I'm trying to achieve, written in a Playground.
I am trying to define a generic protocol for any given ResourceType such that the ResourceTypeRepository protocol then automatically requires the same set of functions, without having to copy-paste most of GenericRepository only to manually fill in the ResourceType for each Repository I make. The reason I need this as a protocol is because I want to be able to mock this for testing purposes later. So I'll provide an implementation of said protocol somewhere else in the actual app.
My interpretation of the code below is that it should work, because both SomeResourceLocalRepository and SomeResourceRemoteRepository are concrete, as I have eliminated the associated type by defining them "on top of" SomeResourceRepository, which is only defined where ResourceType == SomeResource.
import Foundation
struct SomeResource: Identifiable {
let id: String
let name: String
}
struct WhateverResource: Identifiable {
let id: UUID
let count: UInt
}
protocol GenericRepository: class where ResourceType: Identifiable {
associatedtype ResourceType
func index() -> Array<ResourceType>
func show(id: ResourceType.ID) -> ResourceType?
func update(resource: ResourceType)
func delete(id: ResourceType.ID)
}
protocol SomeResourceRepository: GenericRepository where ResourceType == SomeResource {}
protocol SomeResourceLocalRepository: SomeResourceRepository {}
protocol SomeResourceRemoteRepository: SomeResourceRepository {}
class SomeResourceLocalRepositoryImplementation: SomeResourceLocalRepository {
func index() -> Array<SomeResource> {
return []
}
func show(id: String) -> SomeResource? {
return nil
}
func update(resource: SomeResource) {
}
func delete(id: String) {
}
}
class SomeResourceService {
let local: SomeResourceLocalRepository
init(local: SomeResourceLocalRepository) {
self.local = local
}
}
// Some Dip code somewhere
// container.register(.singleton) { SomeResourceLocalRepositoryImplementation() as SomeResourceLocalRepository }
Errors:
error: Generic Protocols.xcplaygroundpage:45:16: error: protocol 'SomeResourceLocalRepository' can only be used as a generic constraint because it has Self or associated type requirements
let local: SomeResourceLocalRepository
^
error: Generic Protocols.xcplaygroundpage:47:17: error: protocol 'SomeResourceLocalRepository' can only be used as a generic constraint because it has Self or associated type requirements
init(local: SomeResourceLocalRepository) {
I will probably have to find another way to accomplish this, but it is tedious and quite annoying as we will produce a lot of duplicate code, and when we decide to change the API of our repositories, we will have to manually change it for all the protocols as we don't follow a generic "parent" protocol in this work-around.
I have read How to pass protocol with associated type as parameter in Swift and the related question found in an answer to this question, as well as Specializing Generic Protocol and others.
I feel like this should work, but it does not. The end goal is a concrete protocol that can be used for dependency injection, e.g. container.register(.singleton) { ProtocolImplementation() as Protocol } as per Dip - A simple Dependency Injection Container, BUT without copy-pasting when the protocol's interface clearly can be made generic, like in the above.
As swift provides a way to declare generic protocols (using associatedtype keyword) it's impossible to declare a generic protocol property without another generic constraint. So the easiest way would be to declare resource service class generic - class SomeResourceService<Repository: GenericRepository>.
But this solution has a big downside - you need to constraint generics everywhere this service would be involved.
You can drop generic constraint from the service declaration by declaring local as a concrete generic type. But how to transit from generic protocol to the concrete generic class?
There's a way. You can define a wrapper generic class which conforms to GenericRepository. It does not really implements its methods but rather passes to an object (which is real GenericRepository) it wraps.
class AnyGenericRepository<ResourceType: Identifiable>: GenericRepository {
// any usage of GenericRepository must be a generic argument
init<Base: GenericRepository>(_ base: Base) where Base.ResourceType == ResourceType {
// we cannot store Base as a class property without putting it in generics list
// but we can store closures instead
indexGetter = { base.index() }
// and same for other methods or properties
// if GenericRepository contained a generic method it would be impossible to make
}
private let indexGetter: () -> [ResourceType] {
indexGetter()
}
// ... other GenericRepository methods
}
So now we have a concrete type which wraps real GenericRepository. You can adopt it in SomeResourceService without any alarm.
class SomeResourceService {
let local: AnyGenericRepository<SomeResource>
}

How to make a struct conforms to a protocol which has a property conforms to another protocol in swift 4?

I was going to reflect some JSON data from web service into swift struct. So I created a protocol which conforms to decodable protocol and planed to create some structs to conform it. This is the protocol I had created:
protocol XFNovelApiResponse: Decodable {
var data: Decodable {get}
var error: NovelApiError {get}
}
struct NovelApiError: Decodable {
let msg: String
let errorCode: String
}
It was compiled. But when I started to write my struct I got an error. The struct's code is here:
struct XFNovelGetNovelsApiResponse: XFNovelApiResponse {
let data: NovelsData
let error: NovelApiError
struct NovelsData: Decodable {
}
}
The error says type 'XFNovelGetNovelsApiResponse' does not conform to protocol 'XFNovelApiResponse'. I know the 'data' property should be implemented in wrong way. How can I fix it? Thanks.
You are asking to describe the kind of type that data can hold, rather than the actual type. That means it needs to be an associatedtype:
protocol XFNovelApiResponse: Decodable {
associatedtype DataType: Decodable
var data: DataType {get}
var error: NovelApiError {get}
}
Note that protocols with associated types can generate a lot of complexity, so you should carefully consider if this protocol is really necessary, or if XFNovelApiResponse could, for example, be generic instead. It depends on what other types implement this protocol.
For example, another implementation of a similar set of data structures without protocols would be:
struct XFNovelApiResponse<DataType: Decodable>: Decodable {
var data: DataType
var error: NovelApiError
}
struct NovelsData: Decodable {
}
struct NovelApiError: Decodable {
let msg: String
let errorCode: String
}
let novels = XFNovelApiResponse(data: NovelsData(),
error: NovelApiError(msg: "", errorCode: ""))
Alternately, you can implement this with classes and subclasses, which allow inheritance. Structs do not inherit from protocols, they conform to protocols. If you really mean inheritance, classes are the right tool. (But I expect generics are the better solution here.)

Statically typed properties in Swift protocols

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.