How to create an instance that satisfies the generic requirements - swift

I have created the following code and everything works fine except the definition of TestDBAPI.
When I want to create a type that conforms to the DBAPIProtocol protocol, it is always impossible to generate a type instance that satisfies the generic constraints
Please, how can I define TestNoteFetcher to satisfy the protocol requirement of DBAPIProtocol.
ps: I hope the flexibility of generic definitions can be maintained in DBAPIProtocol
thanks
import Combine
// For Value
public enum WrappedID: Equatable, Identifiable, Sendable, Hashable {
case string(String)
case integer(Int)
public var id: Self {
self
}
}
public protocol BaseValueProtocol: Equatable, Identifiable, Sendable {
var id: WrappedID { get }
}
public struct Note: BaseValueProtocol {
public var id: WrappedID
public var index: Int
public init(id: WrappedID, index: Int) {
self.id = id
self.index = index
}
}
// For Object
public protocol ConvertibleValueObservableObject<Value>: ObservableObject, Equatable, Identifiable where ID == WrappedID {
associatedtype Value: BaseValueProtocol
func convertToValueType() -> Value
}
public final class TestNote: ConvertibleValueObservableObject {
public static func == (lhs: TestNote, rhs: TestNote) -> Bool {
true
}
public var id: WrappedID {
.integer(1)
}
public func convertToValueType() -> Note {
.init(id: .integer(1), index: 0)
}
}
// For Fetcher
public protocol ObjectFetcherProtocol<Object,ConvertValue> {
associatedtype ConvertValue: BaseValueProtocol
associatedtype Object: ConvertibleValueObservableObject<ConvertValue>
var stream: AsyncPublisher<AnyPublisher<[Object], Never>> { get }
}
public final class TestNoteFetcher: ObjectFetcherProtocol {
public typealias ConvertValue = Note
public typealias Object = TestNote
public var stream: AsyncPublisher<AnyPublisher<[TestNote], Never>> {
sender.eraseToAnyPublisher().values
}
public var sender: CurrentValueSubject<[TestNote], Never>
public init(_ notes: [TestNote] = []) {
sender = .init(notes)
}
}
// For API
public protocol DBAPIProtocol {
var notesFetcher: () async -> any ObjectFetcherProtocol<any ConvertibleValueObservableObject<Note>, Note> { get set }
}
// get error in here . Cannot convert value of type 'TestNoteFetcher.Object' (aka 'TestNote') to closure result type 'any ConvertibleValueObservableObject<Note>'
public final class TestDBAPI: DBAPIProtocol {
public var notesFetcher: () async -> any ObjectFetcherProtocol<any ConvertibleValueObservableObject<Note>, Note> = {
TestNoteFetcher([])
}
}

Since your closure returns too many anys, the compiler got confused & is telling you that TestNoteFetcher does not conform any ObjectFetcherProtocol. Generics are your friend, you can use associatedtype to skip all this code & fix the issue:
public protocol DBAPIProtocol {
associatedtype Fetcher: ObjectFetcherProtocol
var notesFetcher: () async -> Fetcher { get set }
}
public final class TestDBAPI: DBAPIProtocol {
public var notesFetcher: () async -> TestNoteFetcher = {
TestNoteFetcher([])
}
}

Sometimes the answer is right in front of me, but I do turn a blind eye to it. follow the Xcode warning, I made a change to this part of the code and it works now.
public protocol ObjectFetcherProtocol<ConvertValue> {
associatedtype ConvertValue: BaseValueProtocol
var stream: AsyncPublisher<AnyPublisher<[any ConvertibleValueObservableObject<ConvertValue>], Never>> { get }
}
public final class TestNoteFetcher: ObjectFetcherProtocol {
public var stream: AsyncPublisher<AnyPublisher<[any ConvertibleValueObservableObject<Note>], Never>> {
sender.eraseToAnyPublisher().values
}
public var sender: CurrentValueSubject<[any ConvertibleValueObservableObject<Note>], Never>
public init(_ notes: [any ConvertibleValueObservableObject<Note>] = []) {
sender = .init(notes)
}
}
This way, I can still maintain the flexibility defined in DBAPIProtocol without having to introduce association types

Related

Returning a protocol with associatedtype from another protocol API

I have a Session protocol with an Output associated type:
public protocol SessionAPI {
associatedtype Output: Equatable
var output: Output { get }
}
And a concrete implementation of the protocol that returns a String:
public final class StringSession: SessionAPI {
public typealias Output = String
public let output: String
}
Let's assume that the implementation of StringSession is very complex and touches many modules, and I don't want to add dependencies to those modules in classes that use the SessionAPI. So I have another protocol that vends StringSessions using a generic factory method:
public protocol SessionFactoryAPI {
func createStringFactory<T: SessionAPI>() -> T where T.Output == String
}
All of this compiles fine. However, when I try to implement the factory API, I get a compilation error:
public final class SessionFactory: SessionFactoryAPI {
public func createStringFactory<T: SessionAPI>() -> T where T.Output == String {
// Error: Cannot convert value of type 'StringSession' to expected argument type 'T'
return StringSession()
}
}
Does anyone have any suggestions on how to get this to work?
Error: Cannot convert value of type 'StringSession' to expected argument type 'T' return
Means that the compiler doesn't know that T should be SessionAPI.
In SessionFactoryAPI:
public protocol SessionFactoryAPI {
func createStringFactory<T: SessionAPI>() -> T where T.Output == String
}
you are only specifying what T.Output should be (ie. String)
If you really need the constraint Output == String, you can try declaring a StringSessionAPI protocol:
public protocol StringSessionAPI: SessionAPI where Output == String { }
public final class StringSession: StringSessionAPI {
public typealias Output = String
public let output: String
public init(output: String) {
self.output = output
}
}
and return any StringSessionAPI
public protocol SessionFactoryAPI {
func createStringFactory() -> any StringSessionAPI
}
struct MyFactory: SessionFactoryAPI {
func createStringFactory() -> any StringSessionAPI {
StringSession(output: "output")
}
}
let factory = MyFactory()
let stringSession: any StringSessionAPI = factory.createStringFactory()
print(stringSession.output) // prints "output"
Or you can try using by using an associated type, instead of a generic function:
public protocol SessionFactoryAPI {
associatedtype T: SessionAPI where T.Output == String
func createStringFactory() -> T
}
struct MyFactory: SessionFactoryAPI {
func createStringFactory() -> StringSession {
.init(output: "output")
}
}
let factory = MyFactory()
let stringSession: StringSession = factory.createStringFactory()
print(stringSession.output) // prints "output"

Implement a public method that rely on a private property in Swift using protocols

I'm building an API that uses ResultBuilder with structs as components and chaining methods as modifiers.
Sometimes different components have the same modifier, e.g.:
var resultBuilderContent = {
Component1()
.modifier(x: 1)
Component2()
.modifier(x: 2)
}
I'd like to implement 'modifier' method in a protocol to avoid duplicating the code. But the method relies on internal properties and if I implement it like this:
protocol SameModifierProtocol{
var x: Int { get set }
}
extension SameModifierProtocol{
public func modifier(x: Int)->SameModifierProtocol{
var s = self
s.x = x
return s
}
}
public struct Component: SameModifierProtocol{
var x: Int = 0
public init(){}
}
// in another module
let c = Component().modifier(x: 1)
I get the error: "'modifier' is inaccessible due to 'internal' protection level".
If I try to differentiate access levels between two protocols like this:
protocol SameModifierProtocol{
var x: Int { get set }
}
public protocol ReceivingSameModifier{
}
extension ReceivingSameModifier where Self: SameModifierProtocol{
public func modifier(x: Int)->ReceivingSameModifier{
var s = self
s.x = x
return s
}
}
I get the following error: "Cannot declare a public instance method in an extension with internal requirements".
This is where I stuck. What are my options?
Following this discussion from Swift forum, there are two ways to solve the issue.
First one is straightforward:
/// The publically visible capabilities.
public protocol SameModifierProtocol {
func modifier(x: Int) -> SameModifierProtocol
}
/// The internal requirements on which the default implementation relies.
internal protocol SynthesizedSameModifierProtocolConformance:
SameModifierProtocol {
var x: Int { get set }
}
/// The default implementation.
extension SynthesizedSameModifierProtocolConformance {
public func modifier(x: Int) -> SameModifierProtocol{
var s = self
s.x = x
return s
}
}
/// Conforms to the public protocol
/// and requests the default implementation from the internal one.
/// Clients can only see the public protocol.
public struct Component: SynthesizedSameModifierProtocolConformance {
internal var x: Int = 0
public init() {}
}
The second involves an unofficial feature #_spi, that allows to make implementation details unexposed in a public protocol:
public protocol SameModifierProtocol {
#_spi(SameModifier) var x: Int { get set }
}
extension SameModifierProtocol {
public func modifier(x: Int) -> Self {
var s = self
s.x = x
return s
}
}
public struct Component: SameModifierProtocol {
#_spi(SameModifier) public var x: Int = 0
public init() {}
}

Protocol Conformance Check

How can I perform conformance check against protocol with AssociatedType. Xcode shows error:
Protocol 'MyListener' can only be used as a generic constraint because
it has Self or associated type requirements
My ultimate goal is to extract "MyListener.section" from an array of weakObjects, where the handler matches the function argument.
Note. The NSPointerArray of weakObjects is suppose to capture different types of MyListeners.
public class MyHandler<O,E> {
var source = [O]()
var dest = [E]()
}
public protocol MyListener:class {
var section: Int {get}
associatedtype O
associatedtype E
var handler: MyHandler<O,E>? { get }
}
public class MyAnnouncer {
private let mapWeakObjects: NSPointerArray = NSPointerArray.weakObjects()
public func add<L: MyListener>(listener: L) {
let pointer = Unmanaged.passUnretained(listener).toOpaque()
mapWeakObjects.addPointer(pointer)
}
public func search<O, E> (h:MyHandler<O,E>) -> [Int] {
_ = mapWeakObjects.allObjects.filter { listener in
if listener is MyListener { // Compilation failed
}
if let _ = listener as? MyListener { //Compilation error
}
if listener is MyListener.Type { //Compilation failed
}
}
return [] // ultimate goal is to extract corresponding [MyListener.section].
}
}
Unfortunately, Swift doesn't support protocols with AssociatedType to conformance.
You should try to use Type Erasure. One of the way is to implement type erasure by creating new AnyType class.
Here is another way to release type erasure (example from the internet)
protocol SpecialValue { /* some code*/ }
protocol TypeErasedSpecialController {
var typeErasedCurrentValue: SpecialValue? { get }
}
protocol SpecialController : TypeErasedSpecialController {
associatedtype SpecialValueType : SpecialValue
var currentValue: SpecialValueType? { get }
}
extension SpecialController {
var typeErasedCurrentValue: SpecialValue? { return currentValue }
}
extension String : SpecialValue {}
struct S : SpecialController {
var currentValue: String?
}
var x: Any = S(currentValue: "Hello World!")
if let sc = x as? TypeErasedSpecialController { // Now we can perform conformance
print(sc.typeErasedCurrentValue)
}

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: Abstract base class/protocol with private members

I've created an abstract base class-like structure in Swift, using protocol extensions, as per this answer. This is a simplified example:
protocol AbstractBase {
var _constant: Int { get }
func _operation(_ val: Int) -> Int
}
public class ConcreteSub: AbstractBase {
let _constant: Int = 42
func _operation(_ val: Int) -> Int {
return val + 2
}
}
extension AbstractBase {
func mainOperation(_ val: Int) -> Int {
return _operation(val + _constant)
}
}
So basically, ConcreteSub provides the implementation details needed by AbstractBase, namely _constant and _operation.
I would like to hide those details from clients, and only expose mainOperation. However, Swift does not allow me to make the members fileprivate on the protocol -- if I do the following
protocol AbstractBase {
fileprivate var _constant: Int { get }
// etc
I get "error: 'fileprivate' modifier cannot be used in protocols".
Nor can I apply the modifier on the subclass -- when I try
public class ConcreteSub: AbstractBase {
fileprivate let _constant: Int = 42
// etc
I get "error: property '_constant' must be declared internal because it matches a requirement in internal protocol 'AbstractBase'".
Lastly, when I make the whole protocol fileprivate, I get no compile errors, but I consistently run into Linking errors, which I guess is because the protocol is private, but the subclass is public.
Is there another way I'm missing?
When I need an abstract base with some properties/functions hidden I use class with some additional fatalErrors and asserts to crash whenever someone is trying to use Base instead of implementation.
public class AbstractBase {
init() {
assert(type(of: self) != AbstractBase.self, "Abstract class")
}
fileprivate var _constant: Int {
fatalError("Abstract class")
}
fileprivate func _operation(_ val: Int) -> Int {
fatalError("Abstract class")
}
func mainOperation(_ val: Int) -> Int {
return _operation(val + _constant)
}
}
public class ConcreteSub: AbstractBase {
fileprivate override var _constant: Int {
return 42
}
fileprivate override func _operation(_ val: Int) -> Int {
return val + 2
}
}
I actually just ran into this issue. As of Swift 5.1, you can do this instead:
protocol MyProtocol {
var someVisibleVar: String { get }
func someVisibleFunc()
}
fileprivate extension MyProtocol {
var someFilePrivateVar: String {
"whatever"
}
func someFilePrivateFunc() {
print("someFilePrivateFunc() was called with \(someVisibleVar)")
}
}
class SomeClass: MyProtocol {
var someVisibleVar: String { "whatever" }
func someVisibleFunc() {
if someFilePrivateVar == someVisibleVar {
someFilePrivateFunc()
}
}
}
class SomeOtherClass: MyProtocol {
var someVisibleVar: String { "something else" }
func someVisibleFunc() {
if someFilePrivateVar == someVisibleVar {
someFilePrivateFunc()
}
}
}