I was reading about type erasure in swift. After reading an article, I decided to write a simple implementation of "chain of responsibility" pattern and came up with this solution:
protocol Responsibility {
associatedtype ParameterType
associatedtype ResultType
func process(_ data: ParameterType) -> ResultType?
}
struct AnyResponsibility<ParameterType, ResultType>: Responsibility {
private let processData: (ParameterType) -> ResultType?
init<R: Responsibility>(_ responsibility: R) where R.ParameterType == ParameterType, R.ResultType == ResultType {
processData = responsibility.process
}
func process(_ data: ParameterType) -> ResultType? {
processData(data)
}
}
protocol ResponsibilityChain {
associatedtype ParameterType
associatedtype ResultType
var responsibilities: [AnyResponsibility<ParameterType, ResultType>] { get }
}
struct AnyResponsibilityChain<ParameterType, ResultType>: ResponsibilityChain {
init<Chain: ResponsibilityChain>(_ chain: Chain) where Chain.ParameterType == ParameterType, Chain.ResultType == ResultType {
responsibilities = chain.responsibilities
}
let responsibilities: [AnyResponsibility<ParameterType, ResultType>]
}
struct MyResponsibility: Responsibility {
func process(_ data: Int) -> Int? {
data + 1
}
}
struct MyChain: ResponsibilityChain {
init(_ responsibilities: [AnyResponsibility<Int, Int>]) {
self.responsibilities = responsibilities
}
let responsibilities: [AnyResponsibility<Int, Int>]
}
However, in example code from this article: https://www.donnywals.com/understanding-type-erasure-in-swift/ they seem to favor classes over structures. Is there any good reason why it would be better to use classes in my code?
I know that classes are reference types but I always favor let over var so it shouldn't matter in this case. Am I right? I have also read this article: https://docs.swift.org/swift-book/LanguageGuide/ClassesAndStructures.html but I didn't found any solution to my problem.
I'm coming to swift from C++ background and we tend to use classes when the object has some logic, and use structures when the object only holds some properties. Is it a case in swift?
Related
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> { ... }
Consider a factory method pattern implementation:
import UIKit
protocol TransportProtocol: CustomStringConvertible {
func techReview()
}
// Make default implemetation opposed to #objc optional methods and properties
extension TransportProtocol {
func techReview() {
print("Reviewing \(type(of: self))")
}
var description: String {
"This is a \(type(of: self))"
}
}
final class Car: TransportProtocol {
func changeOil() {
print("Changed oil")
}
}
final class Ship: TransportProtocol {
}
protocol LogisticProtocol {
associatedtype Transport: TransportProtocol
func createTransport() -> Transport
func delivery(from A: Any, to B: Any)
func techRewiew(for transport: TransportProtocol)
}
extension LogisticProtocol {
func delivery(from A: Any, to B: Any) {
print("Moving \(type(of: self)) from \(A) to \(B)")
}
func techRewiew(for transport: TransportProtocol) {
transport.techReview()
}
}
final class RoadLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Car()
}
}
final class SeaLogistics: LogisticProtocol {
func createTransport() -> some TransportProtocol {
Ship()
}
}
// Usage:
class Client {
// ...
static func someClientCode<L: LogisticProtocol>(creator: L) -> some TransportProtocol {
let transport = creator.createTransport()
print("I'm not aware of the creator's type, but it still works.\n"
+ transport.description + "\n")
creator.delivery(from: "Source", to: "Destination")
return transport
}
// ...
}
let someTransport = Client.someClientCode(creator: RoadLogistics())
type(of: someTransport.self) // Car
someTransport.changeOil() // Error: Value of type 'some TransportProtocol' has no member 'changeOil'
The question is why I can't call changeOil() on someTransport if the compiler knows it is a Car not just a TransportProtocol.
Are there any benefits we can get from using some directive, not just bare protocol types?
The return type of the method is some TransportProtocol, not Car. So even though the returned instance is of type Car, you cannot call any methods on it that only exist on Car, but not on TransportProtocol.
The runtime knows that the concrete type of someTransport is Car, but the compiler only knows that the return type conforms to TransportProtocol.
If you want to be able to access Car methods on someTransport, you need to downcast it to Car.
if let car = someTransport as? Car {
car.changeOil()
}
Using the some keyword has type-system benefits when used for opaque returns types (introduced in SE-0244 as part of Swift 5.1), since it actually enables returning a protocol with associated types without having to explicitly make the method generic. This is what drives SwiftUI, since it enables you to return some View.
On the other hand, using opaque return types for protocols without associated types holds no benefits, so in your particular example, there's no reason to use it.
There are a lot of questions out there that are similar, but I'm having a hard time finding something that explains exactly what I'm looking for
I have multiple Services, which handle a generic Data type. They currently all subclass an semi-"abstract" class, DataService
This is because all of these Services have the same stored properties:
class DataService<T: Data> {
let id: String
let eventDataProviders: [EventDataProvider]
private let storedDataProviders: [StoredDataProvider]
and their init are the same as well:
init(id: String, eventDataProviders: [EventDataProvider], storedDataProviders: [StoredDataProvider]) {
self.id = id
self.storedDataProviders = storedDataProviders
self.eventDataProviders = eventDataProviders
self.setupStoredDataProviders()
self.setupEventDataProviders()
}
the setup methods are also the same
The difference between these classes is how they handle data, currently defined in an "abstract" function
func eventReceivedHandler() -> ((T) -> Void) {
fatalError("DataService does not have eventReceivedHandler defined")
}
Most resources recommend Protocols and Protocol Extensions.
Which I would prefer as well, but I think the two things that make that difficult are:
Trying to reduce duplicate code by keeping the stored property declarations in one place, and by sharing an init
The generic type on the class, it doesn't seem straight-forward to maintain that through a protocol
But the problem with this current solution is
The "abstract" class is exposed, when we don't want anybody to actually instantiate an instance
The eventReceivedHandler isn't compiler-enforced
Is there a correct solution here? I thought this architecture may be common enough, but I've really been struggling finding anything around online, my search queries contain too many overused terms
You can create a protocol that has eventReceivedHandler() method, i.e.
protocol EventHandler {
func eventReceivedHandler() -> ((Data)->())
}
Since you're defining T: Data, I don't think there is even a need for generic type T since always Data is expected in that place.
Now, you can create your class DataService as already did,
class DataService {
let id: String
let eventDataProviders: [EventDataProvider]
private let storedDataProviders: [StoredDataProvider]
init(id: String, eventDataProviders: [EventDataProvider], storedDataProviders: [StoredDataProvider]) {
//....
}
func setupStoredDataProviders() {
//.....
}
func setupEventDataProviders() {
//.....
}
}
Now, the Service classes will be created like,
class Service1: DataService, EventHandler {
func eventReceivedHandler() -> ((Data) -> ()) {
//....
}
}
class Service2: DataService, EventHandler {
func eventReceivedHandler() -> ((Data) -> ()) {
//....
}
}
In case you still have any doubts regarding that, you can ask.
You might want to use a protocol with an associated type :
protocol Service {
associatedtype T
var eventDataProviders: [EventDataProvider<T>] { get }
var storedDataProviders: [StoredDataProvider<T>] { get }
}
Coupled with generic providers classes :
class EventDataProvider<T> {
}
class StoredDataProvider<T> {
}
And a concrete class :
class DataService<T>: Service {
let id: String
let eventDataProviders: [EventDataProvider<T>]
let storedDataProviders: [StoredDataProvider<T>]
init(id: String, eventDataProviders: [EventDataProvider<T>], storedDataProviders: [StoredDataProvider<T>]) {
self.id = id
self.storedDataProviders = storedDataProviders
self.eventDataProviders = eventDataProviders
self.setupStoredDataProviders()
self.setupEventDataProviders()
}
func setupStoredDataProviders() {
}
func setupEventDataProviders() {
}
}
Would allow you to have DataService instances handling different types of data eg. :
let data = DataService<Data>(id: "1", eventDataProviders: [EventDataProvider<Data>()], storedDataProviders: [StoredDataProvider<Data>()])
let data2 = DataService<Int>(id: "2", eventDataProviders: [EventDataProvider<Int>()], storedDataProviders: [StoredDataProvider<Int>()])
And you would also gain more type safety as :
let data3 = DataService<Data>(id: "3", eventDataProviders: [EventDataProvider<Data>()], storedDataProviders: [StoredDataProvider<Int>()])
would trigger :
Cannot convert value of type '[EventDataProvider]' to expected
argument type '[EventDataProvider<_>]'
Unfortunately, you loose access control as storedDataProviders is no longer private.
Is it possible to provide confirming protocol in generic functions of another protocol?
I tried make it work like this, but it's impossible, or I made some mistakes.
My code:
protocol DataModelProtocol {
associatedtype ObjectProtocol: Protocol
func fetchObjects<T: ObjectProtocol>() -> [T]?
func fetch<T: ObjectProtocol>(object: T) -> T?
func delete<T: ObjectProtocol>(allObjectOf type: T.Type)
func insert<T: ObjectProtocol>(_ object: T)
func save<T: ObjectProtocol>(_ object: T)
func update<T: ObjectProtocol>(_ object: T)
func delete<T: ObjectProtocol>(_ object: T)
}
Error message:
inheritance from non-protocol, non-class type 'Self.ObjectProtocol'
Image of Xcode error
It works only like this, but I want to make it more flexible:
protocol DataModelProtocol {
typealias ObjectProtocol = NSManagedObject
...
}
This would perhaps be easier is you gave responsibility of the return types to the object classes themselves.
You would need two protocols but it would avoid mixing protocols and generics:
// The first protocol is for the base class of data objects
protocol DataProtocol
{}
// The protocol provides the "typed" equivalents of the model's
// data manipulation methods.
// By using an extension to DataProtocol, this only needs to
// be done once for all models and data objects.
extension DataProtocol
{
static func fetchObjects(from model:DataModelProtocol) -> [Self]?
{ return model.fetchObjects(object: Self.self) as! [Self]? }
static func fetch(from model:DataModelProtocol) -> Self?
{ return model.fetch(object: Self.self) as! Self? }
// ...
}
// The second protocol is for the data models
// It requires implementation of the data manipulation methods
// using the general "DataProtocol" rather than any specific class
// The actual instances it produces must be of the appropriate class
// however because they will be type casted by the DataProtocol's
// default methods
protocol DataModelProtocol
{
func fetchObjects(object:DataProtocol.Type) -> [DataProtocol]?
func fetch(object:DataProtocol.Type) -> DataProtocol?
// ... and so on
}
...
Here is a simple (naive) example of how the protocols can be used. (I intentionally chose not to use core data to illustrate the generality of the solution)
...
// The base class (or each one) can be assigned the DataProtocol
// (it doesn't add any requirement)
class LibraryObject : DataProtocol
{}
class Author: LibraryObject
{
var name = ""
}
class Book: LibraryObject
{
var title = ""
}
// This is a simple class that implements a DataModelProtocol
// in a naive (and non-core-data way)
struct LibraryModel:DataModelProtocol
{
var authors:[Author] = [ Author(), Author() ]
var books:[Book] = [ Book(), Book(), Book(), Book(), Book() ]
func fetchObjects(object: DataProtocol.Type) -> [DataProtocol]?
{
return object == Book.self ? books
: object == Author.self ? authors
: nil
}
func fetch(object:DataProtocol.Type) -> DataProtocol?
{ return nil }
}
...
Using the protocols will be a bit different from your approach because you would be starting from the object classes rather than passing them as parameter to the model
...
var library = LibraryModel()
let allBooks = Book.fetchObjects(from:library) // this almost reads like english
If you want generic functions of another protocol to be conformed, just simply create a associatedType of T that conforms to the protocol, no need for making an extra ObjectProtocol:
protocol DataModelProtocol {
associatedtype T: Protocol
func fetchObjects<T>() -> [T]?
func fetch<T>(object: T) -> T?
func delete<T>(allObjectOf type: T.Type)
func insert<T>(_ object: T)
func save<T>(_ object: T)
func update<T>(_ object: T)
func delete<T>(_ object: T)
}
I want to create an interface (protocol) for Tree in Swift. This tree will use interface (protocol) for TreeNodes. But all these interfaces should be generic. Ideally I want to have something like this:
protocol TreeNodeInterface {
associatedtype T: ElementInterface
var value: T { get set }
// ... some other methods
}
protocol TreeInterface {
var rootNode: TreeNodeInterface<T>? { get }
func clean()
// .. some other methods
}
class Tree<T: ElementInterface>: TreeInterface {
var root: TreeNodeInterface<T>?
var rootNode: TreeNodeInterface<T>? {
get {
return root
}
}
func clean() {
}
}
So for example I will have class Tree inherited from TreeInterface and I can initialize that Tree with any type (Int, String, CustomClass etc), so that each node will have that type as a value.
I managed to do this with Object Oriented Programming, but cannot do it with Protocol Oriented Programming
Swift doesn't allow me to do this. Can someone help me here?
Thanks
I think you're trying to do too much with protocols. Your biggest problem here is that you cannot create a variable with a protocol type if that protocol has an associated type (or a Self requirement). By replacing these definitions with generics, I ended up with this:
protocol TreeNodeInterface {
associatedtype Element: ElementInterface
var value: Element { get set }
// ... some other methods
}
protocol TreeInterface {
associatedtype Node: TreeNodeInterface
var rootNode: Node? { get }
func clean()
// .. some other methods
}
class Tree<T: TreeNodeInterface>: TreeInterface {
typealias Node = T
var rootNode: T?
init() {}
func clean() {
}
}
This compiles, but now you have to figure out how to initialize it. Next step is to make a type which conforms to TreeNodeInterface:
struct TreeNode<T: ElementInterface>: TreeNodeInterface {
typealias Element = T
var value: T
}
This looks strikingly similar to the protocol, but that's alright. Now let's initialize Tree:
// Assuming that Int conforms to ElementInterface
let tree = Tree<TreeNode<Int>>()
Phew! That was a lot of work, most of which I consider unnecessary. Do you really need TreeNodeInterface and TreeInterface? I'd argue that you don't. Here's what it might look like if you used concrete types instead:
struct TreeNode<T: ElementInterface> {
var value: T
}
class Tree<T: ElementInterface> {
var root: TreeNode<T>?
init() {}
func clean() {
}
}
let tree = Tree<Int>()