How to make a dynamic generic? - swift

I'm trying to write a protocol that allows me to version models in my app. In order to do that I wrote the following VersionManager.
class VersionManager<Type: Decodable> {
private var database: Database
init(database: Database) {
self.database = database
}
var versions: [Type] {
return []
}
}
After that I wrote a protocol that I can add to models:
protocol Versionable {
}
extension Versionable {
private var manager: VersionManager<Restaurant> {
return VersionManager<Restaurant>(database: Database.shared)
}
public var versions: [Restaurant] {
return manager.versions
}
}
Now, the problem I'm facing is that I tried passing the type dynamically instead of hardcoded, like I have now for Restaurant.
So I tried changing the protocol to this:
protocol Versionable {
var kind: Decodable.Type { get }
}
Then I wanted to pass kind to VersionManager. However, when I try that Xcode throws this error: Expected '>' to complete generic argument list.
Is there any other way to do this?

If you want to use generics inside a protocol, you need to use an associatedtype
protocol Versionable {
associatedtype Model: Decodable
}
extension Versionable {
private var manager: VersionManager<Model> {
return VersionManager<Model>(database: Database.shared)
}
public var versions: [Model] {
return manager.versions
}
}
The model that is going to implement the Versionable protocol will have to resolve this type:
struct SomeModel: Versionable {
typealias Model = Int
}
SomeModel().versions // [Int]
I'm guessing Restaurant in your example refers to the model that implements Versionable. In that case, you can just use the Self reference inside your protocol extension:
protocol Versionable: Decodable {
}
extension Versionable {
private var manager: VersionManager<Self> {
return VersionManager<Self>(database: Database.shared)
}
public var versions: [Self] {
return manager.versions
}
}
struct SomeModel: Versionable {}
SomeModel().versions // [SomeModel]
Please note that the Versionable protocol now requires the Decodable conformance because of the VersionManager<Type: Decodable> generic constraint.

Related

Protocol with generics throws error when used as a property to call a method

I have a protocol SomeObjectFactory whose method createSomeObjectWithConfiguration(_ config: SomeObjectConfiguration<T>) is used inside the class Builder. When I tried to compile this code with Swift 5.7 I ran into an error
Member 'configWithExperience' cannot be used on value of type 'any
configurationFactory'; consider using a generic constraint instead
Here is the implementation below
import Combine
import Foundation
final class SomeObject<T: Combine.Scheduler> {}
struct Experience {
let id: String
}
struct SomeObjectConfiguration<T: Combine.Scheduler> {
let scheduler: T
}
protocol SomeObjectFactory {
associatedtype T: Combine.Scheduler
func createSomeObjectWithConfiguration(_ config: SomeObjectConfiguration<T>) -> SomeObject<T>
}
protocol ConfigurationFactory {
associatedtype T: Combine.Scheduler
func configWithExperience(_ experience: Experience) -> SomeObjectConfiguration<T>
}
final class Builder<T: Combine.Scheduler> {
private let configurationFactory: any ConfigurationFactory
init(configurationFactory: any ConfigurationFactory) {
self.configurationFactory = configurationFactory
}
func createSomeObject(_ experience: Experience) {
let someObjectConfiguration: SomeObjectConfiguration<T> = configurationFactory.configWithExperience(experience)
}
}
I was hoping to create a someObjectConfiguration from the configurationFactory instance of the Builder.
any ConfigurationFactory really does mean, any ConfigurationFactory. There's no guarantee the ConfigurationFactory.T of that factory will be the same as the Builder.T that contains it. You need to add a constraint that those two should match.
One way to do that is with a primary associated type, like so:
// Make `T` a primary associated type
// https://github.com/apple/swift-evolution/blob/main/proposals/0346-light-weight-same-type-syntax.md
+protocol ConfigurationFactory<T> {
-protocol ConfigurationFactory {
associatedtype T: Combine.Scheduler
func configWithExperience(_ experience: Experience) -> SomeObjectConfiguration<T>
}
final class Builder<T: Combine.Scheduler> {
+ // Constant the `T` of the factory to be the `T` of this builder
+ private let configurationFactory: any ConfigurationFactory<T>
- private let configurationFactory: any ConfigurationFactory
// Likewise, update the initializer to match
+ init(configurationFactory: any ConfigurationFactory<T>) {
- init(configurationFactory: any ConfigurationFactory) {
self.configurationFactory = configurationFactory
}
func createSomeObject(_ experience: Experience) {
let someObjectConfiguration: SomeObjectConfiguration<T> = configurationFactory.configWithExperience(experience)
}
}

Class bound protocol with private(set) requirement

I have a Nameable protocol meant to be used on NSManagedObjects. This protocol is declared in its own Swift package and is meant to be imported by many other packages, so the protocol needs to be public. I also need to maintain the protocol approach rather than using a base class with inheritance. I would like to validate my names and throw errors using the below default implementation, but there seems to be no way to enforce that a developer uses set(_ name: String).
import CoreData
public protocol Nameable: NSManagedObject {
/// The name property of the entitiy
/// - Warning: **don't** set this directly, use `set(_ name: String)` to ensure the name is validated
///
/// - Note: This needs to be an `#NSManaged`
var name: String { get set }
static var defaultName: String { get }
static var maxNameLength: Int { get }
func set(_ name: String) throws
}
public extension Nameable {
// TODO: Localize
static var defaultName: String { "Untitled" }
static var maxNameLength: Int { 128 }
func set(_ name: String) throws {
guard !name.isEmpty else { throw NameError.nameEmpty }
guard name.count <= Self.maxNameLength else { throw NameError.nameTooLong }
self.name = name
try managedObjectContext?.save()
}
}
public enum NameError: Error {
case nameEmpty
case nameTooLong
}
I would like to use the protocol like so:
#objc(MyObject)
class MyObject: NSManagedObject, Nameable {
#NSManaged public private(set) var name: String
}
But since the protocol is public, name also needs to be publicly settable. The only solutions that I have come up with are "soft" (ie. warning comments, or something like #NSManaged var unsafeName: String { get set }). Is there any way to achieve the desired results that can be enforced by the compiler?

How to conform to a protocol with a generic superscript affordance in Swift?

TL;DR
How can I conform to the supscript function of a protocol in my implementation?
Protocol:
protocol DataStore {
//...
subscript<T>(id: T.ID) -> T where T: Identifiable { get set }
}
Neither
subscript<T>(id: T.ID) -> T where T : Identifiable {
get { Project() }//just to return anything for the time being…
set {}
}
nor
subscript(id: Task.ID) -> Task {
get { Project() }//just to return anything for the time being…
set {}
}
work...
The details:
I have developed a habit of creating specific data stores for my models. They all have the same functionality. A specific example could look like this:
final class ProjectDataStore: ObservableObject {
{
static let shared = ProjectDataStore()
let persistence = AtPersistLocally.shared // that's from a personal package I made that handles saving and loading locally as json files
#Published var projects: [Project] {
didSet { //save }
}
private init(projects: [Project]? = nil) {
//load from persistence
}
subscript(id: Project.ID) -> Project? {
get { //return project with id }
set { //set project at id }
}
func getBinding(by id: Project.ID) -> Binding<Project> {
//return Binding
}
func getProjectBy(taskId: Task.ID) -> Project {
//return instance
}
private func getIndex(by id: Project.ID) -> Int? {
//return index in array
}
private func load() -> [Project] {
//load from persistence
}
private func save() {
//save from persistence
}
}
While this works as expected, I'd like to be able to introduce a protocol that I could when adding new functionality / models to have a blueprint on what's necessary for my DataStore.
Here is my first attempt:
protocol DataStore {
static var shared: Self { get }
}
extension DataStore {
var persistence: AtPersistLocally {
get {
AtPersistLocally.shared
}
}
}
To also conform to ObservableObject, I introduce a typealias
typealias ObservableObjectDataStore = DataStore & ObservableObject
and change my model to conform to this new typealias:
final class ProjectDataStore: ObservableObjectDataStore {
//...
}
With this, I already have a static instance and access to persistence available.
Next step of course is to move more and more properties to the protocol–which is what I am struggling with right now.
Let's look at superscript first of all: I guess I understand what needs to be added to the protocol:
protocol DataStore {
//...
subscript<T>(id: T.ID) -> T where T: Identifiable { get set }
}
My problem now is that I don't know how to go about conforming to this subscript now while also getting access to a concrete model from the generic T from the implementation. This attempt…
final class ProjectDataStore: ObservableObjectDataStore {
//...
subscript<T>(id: T.ID) -> T where T : Identifiable {
get { Project() }//just to return anything for the time being…
set {}
}
}
…leads to the error message Cannot convert return expression of type 'Task' to return type 'T'.
If I go with…
final class ProjectDataStore: ObservableObjectDataStore {
//...
subscript(id: Task.ID) -> Task {
get { Project() }//just to return anything for the time being…
set {}
}
}
…the error message changes to Type 'TaskDataStore' does not conform to protocol 'DataStore'.
So I guess basically what I am asking is: how can I conform to my protocol's generic superscript in my implementation of ProjectDataStore?
I have a feeling that I am not too far of, but a critical info is obviously missing…
subscript<T>(id: T.ID) -> T where T: Identifiable { get set }
This says that the caller may pick any Identifiable T, and this method promise return a value of that type. This can't be implemented (other than calling fatalError() and crashing). Identifiable doesn't even promise that the type has an init. Your protocol is impossible.
What you probably meant to write is, which says that a given DataStore will return some specific Identifiable type (not "whatever type the caller requests"):
protocol DataStore {
associatedType Item: Identifiable
subscript(id: Item.ID) -> Item { get set }
}
But I expect this would be much better implemented without a protocol, and just use a generic struct:
struct DataStore<Item: Identifiable> { ... }

Property must be declared public because it matches a requirement in public protocol?

public protocol CDViewModel: class {
var cancellableList: [AnyCancellable] { get set }
}
public extension CDViewModel {
func subscribe(_ callback: #escaping (Project) -> Void) {
cancellableList.append( /*...*/)
}
}
I have a protocol that will be used outside of the module. So I have to declare it public.
But I want the class conforming to that to implement cancellableList with private access.
class MyClass: CDViewModel {
private var cancellableList: [AnyCancellable] = [AnyCancellable]()
}
Property 'cancellableList' must be declared public because it matches a requirement in public protocol 'CDViewModel'
Is this possible?
One workaround to this is to create an intermediate object class that can protect access to its variables using fileprivate. You declare that 'wrapper object type' in your MyClass instance thereby conforming to MyClass's required interface.
Now you have full access to the wrapped object (wrappedList) properties from within the protocol extension, but not from outside the module.
CDViewModel.swift
import Combine
class CDViewModelList {
fileprivate var cancellableList: [AnyCancellable] = [AnyCancellable]()
}
protocol CDViewModelProtocol: AnyObject {
var wrappedList: CDViewModelList { get }
}
extension CDViewModelProtocol {
func subscribe(_ callback: Int) {
self.wrappedList.cancellableList.append(/****/)
}
}
MyClass.swift
class MyClass: CDViewModelProtocol {
let wrappedList = CDViewModelList()
func doStuff () {
self.subscribe(24)
self.wrappedList.cancellableList // 'cancellableList' is inaccessible due to 'fileprivate' protection level
}
}
Thanks for the question it brought me to a good read here:
FULL CREDIT
You cannot have some requirements of a protocol being publicly satisfied and hide other ones, you have to have it all or nothing. More, since the protocol extension needs to work with cancellableList, then this property needs to be public on the type that conforms to the protocol.
If you want to hide the cancellableList property, to avoid extrinsic factors messing with it, one approach I can think of is to split the protocol in two, move the cancellableList to the new protocol, and privately declare a property in MyClass.
CDViewModel.swift
public protocol CDViewModel: class {
// other view model requirements
}
public protocol CDViewModelSource {
var cancellableList: [AnyCancellable] { get set }
}
public extension CDViewModelSource {
func subscribe(_ callback: #escaping (Project) -> Void) {
//cancellableList.append( /*...*/)
}
}
MyClass.swift
class MyClass {
private var source = MyClassSource()
func subscribe(_ callback: #escaping (Project) -> Void) {
source.subscribe(callback)
}
}
fileprivate class MyClassSource: CDViewModelSource {
var cancellableList: [AnyCancellable] = []
}
The downside would be that you'd have to talk to the source every time you need to access the list of cancellables.

Swift Protocol Extension - Can't Access Func

If I have a series of protocols like so:
protocol Customer {
var name:String { get set }
var age: Int { get set }
var startDate:Date { get set }
var meals:Array<String> { get set }
var market:Int { get set }
}
protocol Vegan:Customer {
}
protocol Vegetarian:Customer {
}
protocol Paleo:Customer {
}
and extension like so:
extension Customer where Self:Vegan, Self:Vegetarian {
func getMeals() -> Array<String> {
return ["VeganMeal1", "VeganMeal2", "VeganMeal3", "VeganMeal4"]
}
}
extension Customer where Self:Vegetarian {
func getMeals() -> Array<String> {
return ["VegetarianMeal1", "VegetarianMeal2", "VegetarianMeal3", "VegetarianMeal4"]
}
}
extension Customer where Self:Paleo {
func getMeals() -> Array<String> {
return ["PaleoMeal1", "PaleoMeal2", "PaleoMeal3", "PaleoMeal4"]
}
}
and this struct
struct aCustomer:Customer, Vegan {
var name:String
var age: Int
var startDate:Date
var meals:Array<String>
var market:Int
}
when I create a new object based on that struct
var newCustomer = aCustomer(name:"William", age:40, startDate:Date(), meals:[], market:1)
how come I can't access the getMeals function in the extensions? I get an error stating getMeals is an ambiguous reference.
extension Customer where Self:Vegan, Self:Vegetarian {
extends Customer in the case where the adopter or Customer also both adopts Vegan and Vegetarian. Your aCustomer (a struct type starting with a small letter?? the horror, the horror) does not do that, so the extension doesn't apply to it.
If the goal is to inject the same code either when the adopter adopts Vegan or when the adopter adopts Vegetarian, use two extensions, or, if you don't like the repetition, have them both adopt some "higher" protocol that is extended with the desired code to be injected.