Using swift protocols with associated types generically - swift

I have a class modelling question with Swift. I have a range of classes that each do the same task (in my example below, Decoding), but they are specialised and each produce a different type of object.
In some cases I will want to be able to talk about my Decoders generally such as getGeneralInfo() or getDecoderForIdentifier(). In other cases, such as where I am doing a decode operation, I will either instantiate the class directly or use as?.
The following code does not work because you can't use Decoder as a return type when it has an associated type.
My solution is to remove decode() from the protocol and have each class just implement its own. I then need to instantiate concrete classes directly where they are needed. This is workable but it makes me sad.
Is there any way I can rejuggle this to have the compiler enforce "all Decoders should have a decode() method according to their associatedtype"?
I have tried using a generic superclass but it requires me to provide a method body for decode(), which is pretty gnarly if your return type isn't optional.
protocol Decoder {
associatedtype Model
func getGeneralInfo() -> GeneralInfo
func decode(sourceData: Data) -> Model
}
// This return type is not allowed because Decoder has an associated type
func getDecoderForIdentifier(id: String) -> Decoder {
if id == "mp3" {
return Mp3Decoder()
}
if id == "wave" {
return WaveDecoder()
}
/* ... */
}
class Mp3Decoder: Decoder {
typealias Model = Mp3Info
func getGeneralInfo() -> GeneralInfo {
let info = GeneralInfo()
/* ... */
return info
}
func decode(sourceData: Data) -> Model {
let result = Mp3Info()
/* ... */
return result
}
}
class WaveDecoder: Decoder {
typealias Model = WaveInfo
/* ... similar to mp3 ... */
}

If you make Model a protocol then you can return Decoder because then it will not need associated types.
protocol Model {
// ...
}
protocol Decoder {
func getGeneralInfo() -> GeneralInfo
func decode(sourceData: Data) -> Model
}
class Mp3Decoder: Decoder {
func getGeneralInfo() -> GeneralInfo {
let info = GeneralInfo()
// ...
return info
}
func decode(sourceData: Data) -> Model {
let result = Mp3Info()
// ...
return result
}
}
func getDecoderForIdentifier(id: String) -> Decoder {
if id == "mp3" {
return Mp3Decoder()
}
// ...
}

Related

Structures vs classes in swift

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?

Why in Swift generics factory method does compiler warn Generic class 'X' requires that 'Object' conforms to 'Y'

As an overview, I'm trying to implement a data abstraction layer in Swift. I'm using two database SDKs but I'm trying to be able to isolate their specific APIs from the rest of the system.
I'm trying to implement a factory pattern that will return the correct object based on a protocol conformance of the supplied concrete type. But the compiler is giving me red flags that I can't wrap my head around.
The Thing class is a first class entity that moves freely through the logic and UI layers of the app, and certain objects persist on a Realm store (which shouldn't matter) and have a specific type that is a subclass of the Realm object. That type is returned by a static function that has to be there to conform with the protocol FromDataSourceA.
protocol FromDataSourceA {
static func A_objectType () -> A_Object.Type
}
class MyObject {
...
}
class Thing: MyObject, FromDataSourceA {
static func A_objectType () -> A_Thing.Type
...
}
// Example usage
let target = DataTarget<Thing>(url: "url")
let dataSource = target.dataSourceBuilder()
As you can see, the concrete type Thing conforms to FromDataSourceA and is passed into the DataTarget initializer. DataSourceBuilder needs to be able to check that Thing conforms to FromDataSourceA to be able to return the correct DataSource.
The DataSourceTypeA class looks like
class DataSourceTypeA<T: MyObject & FromDataSourceA>: DataSource<T> {
let db: DatabaseTypeA // All db-specific APIs contained here
override init (target: DataTarget<T>) {
self.db = try! DatabaseTypeA()
super.init(target: target)
}
override func create<T: MyObject & FromDataSourceA> (object: T) {
db.beginWrite()
db.create(T.A_objectType(), value: object.dict()) // <-- T.A_objectType() is what the conformance to FromDataSourceA is needed for
try! db.commitWrite()
}
override func get<T: MyObject & FromDataSourceA>(id: Int) -> T {
...
}
}
class DataSource<T>: AnyDataSource {
let target: DataTarget<T>
init (target: DataTarget<T>) {
self.target = target
}
func create<T> (object: T) { }
func get<T>(id: Int) -> T { fatalError("get(id:) has not been implemented") }
}
protocol AnyDataSource {
func create<T> (object: T)
func get<T> (id: Int) -> T
}
The problem I'm facing right now is when I check that the metatype conforms to FromDataSourceA, the compiler gives me this warning
class DataTarget<T: MyObject> {
...
func dataSourceBuilder () -> DataSource<T> {
if T.self is FromDataSourceA { // <-- Thing.self conforms to FromDataSourceA
// The Problem:
return DataSourceTypeA(target: self) // Generic class 'DataSourceTypeA' requires that 'MyObject' conform to 'FromDataSourceA'
} else {
...
}
}
}
Why won't the compiler let me return the DataSourceTypeA instance with argument self if the generic T passes the conditional statement and is proven as being in conformance with FromDataSourceA?
The problem is that the call
return DataSourceTypeA(target: self)
is resolved at compile time, therefore it does not help to check
if T.self is FromDataSourceA { }
at runtime. A possible solution is to make the conformance check a compile-time check by defining a restricted extension:
extension DataTarget where T: FromDataSourceA {
func dataSourceBuilder() -> DataSource<T> {
return DataSourceTypeA(target: self)
}
}
If necessary, you can add more implementations for other restrictions:
extension DataTarget where T: FromDataSourceB {
func dataSourceBuilder() -> DataSource<T> {
// ...
}
}
or add a default implementation:
extension DataTarget {
func dataSourceBuilder() -> DataSource<T> {
// ...
}
}
For a call
let dataSource = target.dataSourceBuilder()
the compiler will pick the most specific implementation, depending on the static (compile-time) type information of target.

How can I type check a generic then cast to it in swift?

I like the ideas presented in this post, about making database-agnostic, protocol-oriented code.
So say I have a protocol such as:
protocol Database {
func loadObjects<T>(matching query: Query) -> [T]
func loadObject<T>(withID id: String) -> T?
func save<T>(_ object: T)
}
where Query is a struct that has filter and sort specifiers.
Then, given a persistence framework, such as Realm or CoreData, I can just support this protocol, as such:
extension NSManagedObjectContext: Database {
...
}
extension Realm: Database {
...
}
extension MockedDatabase: Database {
...
}
extension UITestingDatabase: Database {
...
}
The issue arises when I would want to use CoreData.
If we look at the method:
func loadObjects<T>(matching query: Query) -> [T]
I have no way to 'cast' T to NSManagedObject.
For example, my desired implementation might be something like:
extension NSManagedObjectContext: Database {
func loadObjects<T>(matching query: Query<T>) -> [T] {
// you need a fetch request for these models. This guard statement compiles. How do we make it work with NSFetchRequestResult however?
guard T.self is NSManagedObject.Type else {
return []
}
// This line below Fails compiling. Type 'T' does not conform to protocol 'NSFetchRequestResult'
var request = NSFetchRequest<T>(entityName: String(describing: T))
// then set sortDescriptors and predicate, etc.
var objects: [T] = []
self.performAndWait {
do {
if let results = try self.fetch(request!) as? [T] {
objects = results
}
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}
So if I can determine if T's Type is a kind of NSManagedObject, then is it possible to instantiate a NSFetchRequest by 'casting' T to something that will work? It would seem I can't cast or force T to be anything.
Database is a technology-agnostic protocol and therefore shouldn't know anything about Core Data. I'd like to do this in the event I need to change my data persistence framework.
How might I accomplish this in Swift? Would I have to add to the Model protocol to return optionals that would be used for the given frameworks I would support? Or make them support NSFetchRequestResult? I would rather that only the implementation of the protocol need to care about the details of the persistence framework.
Rather than checking the type at runtime constrain the type at compile time, for example
extension NSManagedObjectContext: Database {
func loadObjects<T: NSManagedObject>(matching query: Query<T>) -> [T] {
let request = NSFetchRequest<T>(entityName: String(describing: T.self))
var objects = [T]()
self.performAndWait {
do {
objects = try self.fetch(request) // a generic fetch request returns an array of the generic type or throws an error
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}
It looks like I can answer my own question. One can further constrain generic types by overloading them, so that the calling code can remain the same, but what gets called is dependent on the type you pass into it.
This code below demonstrates this in a playground:
public protocol Action {
func doSomething<T>(to object: T)
}
public class MyActor {
}
extension MyActor: Action {
// works for any type
public func doSomething<T>(to object: T) {
print("was generic")
}
// but if you constrain the type and your object fits that constraint...
// this code is called (same method signature)
public func doSomething<T: NSObject>(to object: T) {
print("was an object")
}
}
class MyObject: NSObject {
var name: String = "Object"
}
struct MyStruct {
var name: String = "Struct"
}
let actor = MyActor()
let object = MyObject()
let value = MyStruct()
actor.doSomething(to: value) // prints 'was generic'
actor.doSomething(to: object) // prints 'was an object'
So in the original example, I would then support Database for CoreData with:
extension NSManagedObjectContext: Database {
func loadObjects<T>(matching query: Query<T>) -> [T] {
return [] // return an empty array because we only support NSManagedObject types
}
func loadObjects<T: NSManagedObject>(matching query: Query<T>) -> [T] {
let request = NSFetchRequest<T>(entityName: String(describing: T.self))
var objects = [T]()
self.performAndWait {
do {
objects = try self.fetch(request) // a generic fetch request returns an array of the generic type or throws an error
} catch let error {
print("Error fetching: \(error.localizedDescription)")
}
}
return objects
}
}

swift - Pass generic type to method with more specific extension requirements

So the title is a little weirdly worded, but here is the basis of what I am looking to do. I want to make a function that can determine if the generic type given extends from a specific protocol and then pass through the type to the more specific method for processing. This would be using the swift programming language to do so.
Psuedo code of what I want to achieve below:
func doStuff<T>(callback: Callback<T>) {
// Pseudo code of what I want to achieve as I'm not sure the syntax
// nor if it's even possible
if T extends Protocol {
let tExtendsProtocolType = T.Type as Protocol
mapStuffSpecific<tExtendsProtocolType>(callback: callback)
} else {
// Standard Use Case
}
}
func doStuffSpecific<T: Protocol>(callback: Callback<T> {
}
Thanks in advance
EDIT 1
typealias Callback<T> = (T) -> Void
protocol Protocol {}
struct A {}
struct B: Protocol {}
// I want to be able to use this to do some common set up then call into either doStuff<T> or doStuff<T: Protocol>
func tryDoStuff<T>(callback: Callback<T>) {
// Do some common setup then call this
doStuff(callback: callback)
}
func doStuff<T>(callback: Callback<T>) {
print("doStuff")
}
func doStuff<T: Protocol>(callback: Callback<T>) {
print("doStuffSpecific")
}
let callbackA: Callback<A> = { _ in } // Just an empty closure
let callbackB: Callback<B> = { _ in }
tryDoStuff(callback: callbackA) // prints doStuff
tryDoStuff(callback: callbackB) // prints doStuffSpecific
Swift's overload resolution algorithm already prioritizes the most specific overload available. Here's an example:
typealias Callback<T> = (T) -> Void
protocol Protocol {}
struct A {}
struct B: Protocol {}
func doStuff<T>(callback: Callback<T>) {
print("doStuff")
}
func doStuff<T: Protocol>(callback: Callback<T>) {
print("doStuffSpecific")
}
let callbackA: Callback<A> = { _ in } // Just an empty closure
let callbackB: Callback<B> = { _ in }
doStuff(callback: callbackA) // prints doStuff
doStuff(callback: callbackB) // prints doStuffSpecific

Swift protocol with constrained associated type error "Type is not convertible"

I have created 2 protocols with associated types. A type conforming to Reader should be able to produce an instance of a type conforming to Value.
The layer of complexity comes from a type conforming to Manager should be able to produce a concrete Reader instance which produces a specific type of Value (either Value1 or Value2).
With my concrete implementation of Manager1 I'd like it to always produce Reader1 which in turn produces instances of Value1.
Could someone explain why
"Reader1 is not convertible to ManagedReaderType?"
When the erroneous line is changed to (for now) return nil it all compiles just fine but now I can't instantiate either Reader1 or Reader2.
The following can be pasted into a Playground to see the error:
import Foundation
protocol Value {
var value: Int { get }
}
protocol Reader {
typealias ReaderValueType: Value
func value() -> ReaderValueType
}
protocol Manager {
typealias ManagerValueType: Value
func read<ManagerReaderType: Reader where ManagerReaderType.ReaderValueType == ManagerValueType>() -> ManagerReaderType?
}
struct Value1: Value {
let value: Int = 1
}
struct Value2: Value {
let value: Int = 2
}
struct Reader1: Reader {
func value() -> Value1 {
return Value1()
}
}
struct Reader2: Reader {
func value() -> Value2 {
return Value2()
}
}
class Manager1: Manager {
typealias ManagerValueType = Value1
let v = ManagerValueType()
func read<ManagerReaderType: Reader where ManagerReaderType.ReaderValueType == ManagerValueType>() -> ManagerReaderType? {
return Reader1()// Error: "Reader1 is not convertible to ManagedReaderType?" Try swapping to return nil which does compile.
}
}
let manager = Manager1()
let v = manager.v.value
let a: Reader1? = manager.read()
a.dynamicType
The error occurs because ManagerReaderType in the read function is only a generic placeholder for any type which conforms to Reader and its ReaderValueType is equal to the one of ManagerReaderType. So the actual type of ManagerReaderType is not determined by the function itself, instead the type of the variable which gets assigned declares the type:
let manager = Manager1()
let reader1: Reader1? = manager.read() // ManagerReaderType is of type Reader1
let reader2: Reader2? = manager.read() // ManagerReaderType is of type Reader2
if you return nil it can be converted to any optional type so it always works.
As an alternative you can return a specific type of type Reader:
protocol Manager {
// this is similar to the Generator of a SequenceType which has the Element type
// but it constraints the ManagerReaderType to one specific Reader
typealias ManagerReaderType: Reader
func read() -> ManagerReaderType?
}
class Manager1: Manager {
func read() -> Reader1? {
return Reader1()
}
}
This is the best approach with protocols due to the lack of "true" generics (the following isn't supported (yet)):
// this would perfectly match your requirements
protocol Reader<T: Value> {
fun value() -> T
}
protocol Manager<T: Value> {
func read() -> Reader<T>?
}
class Manager1: Manager<Value1> {
func read() -> Reader<Value1>? {
return Reader1()
}
}
So the best workaround would be to make Reader a generic class and Reader1 and Reader2 subclass a specific generic type of it:
class Reader<T: Value> {
func value() -> T {
// or provide a dummy value
fatalError("implement me")
}
}
// a small change in the function signature
protocol Manager {
typealias ManagerValueType: Value
func read() -> Reader<ManagerValueType>?
}
class Reader1: Reader<Value1> {
override func value() -> Value1 {
return Value1()
}
}
class Reader2: Reader<Value2> {
override func value() -> Value2 {
return Value2()
}
}
class Manager1: Manager {
typealias ManagerValueType = Value1
func read() -> Reader<ManagerValueType>? {
return Reader1()
}
}
let manager = Manager1()
// you have to cast it, otherwise it is of type Reader<Value1>
let a: Reader1? = manager.read() as! Reader1?
This implementation should solve you problem, but the Readers are now reference types and a copy function should be considered.