DRY code: how to call an implementation from another protocol - swift

I have some code I want to DRY up, I don't want to repeat/maintain the same code at multiple places.
protocol Programable {
var log: String { get }
}
protocol Convertable {
var status: String { get set }
}
extension Programable where Self: NSManagedObject {
var log: String {
return <managed object related stuff>
}
}
extension Programable where Self: NSManagedObject, Self: Convertable {
var log: String {
return <managed object related stuff> + status
}
}
How can I call the first extension's log in the second extension, so I don't have to repeat the details in the code?

It is impossible to call the same overload if only the constraints differ. Instead, move the commonality into something private. There is no naming convention for this.
extension Programmable where Self: AnyObject {
var log: String { where_Self_is_AnyObject_log }
private var where_Self_is_AnyObject_log: String { "where Self: AnyObject" }
}
extension Programmable where Self: AnyObject & Convertible {
var log: String { "\(where_Self_is_AnyObject_log) & Convertible" }
}

Related

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

how to extend a swift protocol with an extension so that classes get an implemented variable defined by the protocol

Circumstances
The following class:
class User {
var name = "Max"
var surname = "Mustermann"
}
and the following protocol:
protocol Sharable {
func share(name: String)
var isSharable: Bool { get set }
}
Intention
Extend the protocol sharable in a way so that subclasses of User get an implemented version of share(:) and isSharable.
The following extension attempts to do this:
extension Sharable where Self: User {
func share(name: String) { print(name) }
var isSharable: Bool { return !name.isEmpty }
}
Where the problem occurs
The created subclass Admin should get all functionality of Sharable for free without storing and implementing variables and functions.
class Admin: User, Sharable {
}
But Xcode will print the error:
'Admin' does not conform to protocol 'Sharable'
Xcode proposes the following:
class Admin: User, Sharable {
var isSharable: Bool
}
Now Xcode prints the error:
class 'Admin' has no initializers
Question
Is there a way to extend the protocol Sharable so that Admin has a variable isSharable without defining or initializing it? Basically like it works with the function share(:), which has not to be implemented by the subclass because it is implemented by the extension.
Desired Call site
class ArbitraryObject: User, Sharable {
// no implementation or initializing of isSharable or share(:)
}
let arbitraryObject = ArbitraryObject()
if arbitraryObject.isSharable {
arbitraryObject.share(name: arbitraryObject.name)
}
Currently your protocol defines isSharable as { get set }
protocol Sharable {
func share(name: String)
var isSharable: Bool { get set }
}
This means that when you construct your extension it is not actually conforming to the protocol
extension Sharable where Self: User {
func share(name: String) { print(name) }
var isSharable: Bool { return !name.isEmpty }
}
This is due to the fact that you have defined isSharable in your extension as a computed property. Computed properties are get only.
You can make your extension conform to the Sharable protocol by removing the set requirement. So your protocol would become:
protocol Sharable {
func share(name: String)
var isSharable: Bool { get }
}

How to make a dynamic generic?

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.

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.

Swift use protocol extension defaults

I have
protocol ErrorContent {
var descriptionLabelText: String { get set }
}
extension ErrorContent {
var descriptionLabelText: String { return "Hi" }
}
struct LoginErrorContent: ErrorContent {
var descriptionLabelText: String
init(error: ApiError) {
...
}
}
and xcode is complaining that "Return from initializer without initializing all stored properties." What I want here is to just use the default value that I gave the descriptionLabelText in the protocol extension. Isn't that the point of protocol extensions? Anyways I'd like to understand why this is wrong and what I can do to use my default value.
Almost correct, just a couple of issues with your code:
You don't need to declare the variable in LoginErrorContent, as the implementation is already in the ErrorContent extension. Declaring it again overrides the extension implementation
If you want to use the extension computed property for descriptionLabelText, you can't specify that it is a setter, as it only returns a value.
Example:
protocol ErrorContent {
var descriptionLabelText: String { get }
}
extension ErrorContent {
var descriptionLabelText: String { return "Hi" }
}
struct LoginErrorContent: ErrorContent {
// Overriding the extension behaviour
var descriptionLabelText: String { return "Hello" }
init(error: ApiError) {
...
}
}