Protocol inheritance with associated type - swift

I have a base protocol that describes the router behavior:
protocol BaseRouterProtocol: AnyObject {
associatedtype View: MainView
func dismiss(viewController: ViewController<View>?)
}
extension BaseRouterProtocol {
func dismiss(viewController: ViewController<View>?) {
viewController?.navigationController?.popViewController(animated: true)
}
}
I want to adopt this protocol to another like this:
protocol StartRouterProtocol: BaseRouterProtocol where View == StartView {
func showTermsVC()
func showSignInVC()
}
But when I create a variable of this type:
let router: StartRouterProtocol
Compiler throws me an error:
Protocol 'StartRouterProtocol' can only be used as a generic constraint because it has Self or associated type requirements
Why does this happening if I have described the type that I expect?

Once a protocol has an associated type, that protocol can't be used as a type by itself for instance declarations-- only for generic constraints and declaring conformance.
So in this case, Swift is saying "yeah, but what is the concrete type for StartRouterProtocol's associated type?"
In this case, it's asking you to either:
Use a concrete type directly, i.e. let router: MyStartViewClass with this conformance declaration, elsewhere: class MyStartViewClass: StartRouterProtocol { ... })
OR, push the need for a concrete type up one layer, as a generic constraint i.e.
class MyRouterController<T: StartRouterProtocol> {
let router: T
}
This is probably not what you were hoping for, but unfortunately associated types add complexity to how you use protocols, especially if you're familiar with generics & interfaces from other languages. (i.e. Java/C# interfaces)
You can work around some aspects of associated types by using a concept called "type erasure" -- but that can cause other problems and complexity.
Here's some further reading that may help: https://medium.com/monstar-lab-bangladesh-engineering/swift-from-protocol-to-associatedtype-then-type-erasure-a4093f6a2d08

Related

Can I require that a protocol's associated type is a protocol itself in Swift?

Question
I have a protocol defined as follows:
protocol MyProtocol {
associatedtype MySubprotocol
}
Can I require that the associated type MySubprotocol is a protocol as well?
So semantically it would be sth. like this:
protocol MyProtocol {
associatedtype MySubprotocol: AnyProtocol
}
Background
I know that I can do nothing with that protocol inside a given concrete type that implements MyProtocol, but I can use it "from outside" for protocol composition. My ultimate goal would be to define a "Sum protocol" from all the associated types of all MyProtocol implementations, e.g. when I have the following structs that implement MyProtocol,
struct MyImpl_1: MyProtocol {
typealias MySubProtocol = MySubProtocol_1
}
struct MyImpl_2: MyProtocol {
typealias MySubProtocol = MySubProtocol_2
}
...
I want to create a composite protocol like this:
protocol AllRequirements:
MyImpl_1.MySubprotocol,
MyImpl_2.MySubprotocol,
... {}
This requires MyImpl_1.MySubprotocol etc. to be a protocol, otherwise I can't compose it. (I know, I could use the concrete type MySubProtocol_1 instead, but I want compile-time safety when a developer creates a new implementation of MyProtocol (i.e. another MyImpl_n).
Example Use Case
I have a couple of "Worker" objects, each of which performs a specific task. As I want to enforce a common API, I create a common protocol for all of these types:
protocol: Worker {
associatedtype Toolset
func getTheJobDone()
}
Here's an example for a concrete type:
struct Cleaner: Worker {
typealias Toolset = AnyBrush
let brush: AnyBrush
init(brush: AnyBrush) {
self.brush = brush
}
func getTheJobDone() {
brush.applyToFloor()
}
}
In practice, I will have multiple types of this sort to perform different tasks. I want each of these Workers to define their own requirements, i.e. the interface (protocol) they require to get their job done. So in this case, I would create a protocol AnyBrush along with the Cleaner:
protocol AnyBrush {
func applyToFloor()
}
which I then require as the worker's Toolset (see above).
This approach works for me, but when I create a new Worker type, the current protocol definition will allow me to specify any type for the Toolset, so someone could (for some unknown reason) do this:
typealias Toolset = Int
which doesn't make sense at all.
So that's why I'm asking this question. For these two goals:
Force a common interface (API) for all workers.
Use the compiler to enforce that the associated type Toolset is always a protocol.
One might ask me to trust my co-workers and my future self that we're going to use a protocol because if we don't understand that, we got a much more serious problem, but I would still like to see if it's possible to require a protocol.

How to define a protocol that specializes a generic protocol, so that it can be used in type declarations?

I'm an Android developer learning iOS development and I'm facing this issue that is trivial with Kotlin/Java interfaces, but I can't get through it with Swift protocols.
Say we have this protocol:
protocol ValueStore {
associatedtype Value
var value: Value? { get set }
}
In Kotlin/Java, if I want to use a generic abstraction to define a variable type, I just use a generic interface with type parameter:
val stringStore: ValueStore<String>
Since this is not possible in Swift, I tried to create a specialized sub-protocol that defines the associated type:
protocol StringStore: ValueStore where Value == String { }
with the intent to use the latter like this:
let stringStore: StringStore
The above declaration is what I'm trying to achieve. Yet the compiler tells me Protocol 'StringStore' can only be used as a generic constraint because it has Self or associated type requirements.
Although in type declarations I can use a specialized generic concrete implementation, i.e. UserDefaultsValueStore<String>, this is against the dependency inversion principle.
Is it possible to specialize a protocol with associated type and still maintaining the level of abstraction?
If I understand the question correctly... Try this:
protocol ValueStoreType {
associatedtype Value
var value: Value? { get set }
}
struct ValueStore<T>: ValueStoreType {
var value: T?
}
Then you will be able to do:
var stringStore: ValueStore<String>

Swift Generics - Attempting to make a generic protocol concrete fails when attempting to use specialised sub-protocol as variable

I want to know why my SomeResourceRepository is still generic, even though it is only defined in one case only, which is when I set ResourceType = SomeResource, which XCode formats as below with the where clause. Code below which shows the exact setup I'm trying to achieve, written in a Playground.
I am trying to define a generic protocol for any given ResourceType such that the ResourceTypeRepository protocol then automatically requires the same set of functions, without having to copy-paste most of GenericRepository only to manually fill in the ResourceType for each Repository I make. The reason I need this as a protocol is because I want to be able to mock this for testing purposes later. So I'll provide an implementation of said protocol somewhere else in the actual app.
My interpretation of the code below is that it should work, because both SomeResourceLocalRepository and SomeResourceRemoteRepository are concrete, as I have eliminated the associated type by defining them "on top of" SomeResourceRepository, which is only defined where ResourceType == SomeResource.
import Foundation
struct SomeResource: Identifiable {
let id: String
let name: String
}
struct WhateverResource: Identifiable {
let id: UUID
let count: UInt
}
protocol GenericRepository: class where ResourceType: Identifiable {
associatedtype ResourceType
func index() -> Array<ResourceType>
func show(id: ResourceType.ID) -> ResourceType?
func update(resource: ResourceType)
func delete(id: ResourceType.ID)
}
protocol SomeResourceRepository: GenericRepository where ResourceType == SomeResource {}
protocol SomeResourceLocalRepository: SomeResourceRepository {}
protocol SomeResourceRemoteRepository: SomeResourceRepository {}
class SomeResourceLocalRepositoryImplementation: SomeResourceLocalRepository {
func index() -> Array<SomeResource> {
return []
}
func show(id: String) -> SomeResource? {
return nil
}
func update(resource: SomeResource) {
}
func delete(id: String) {
}
}
class SomeResourceService {
let local: SomeResourceLocalRepository
init(local: SomeResourceLocalRepository) {
self.local = local
}
}
// Some Dip code somewhere
// container.register(.singleton) { SomeResourceLocalRepositoryImplementation() as SomeResourceLocalRepository }
Errors:
error: Generic Protocols.xcplaygroundpage:45:16: error: protocol 'SomeResourceLocalRepository' can only be used as a generic constraint because it has Self or associated type requirements
let local: SomeResourceLocalRepository
^
error: Generic Protocols.xcplaygroundpage:47:17: error: protocol 'SomeResourceLocalRepository' can only be used as a generic constraint because it has Self or associated type requirements
init(local: SomeResourceLocalRepository) {
I will probably have to find another way to accomplish this, but it is tedious and quite annoying as we will produce a lot of duplicate code, and when we decide to change the API of our repositories, we will have to manually change it for all the protocols as we don't follow a generic "parent" protocol in this work-around.
I have read How to pass protocol with associated type as parameter in Swift and the related question found in an answer to this question, as well as Specializing Generic Protocol and others.
I feel like this should work, but it does not. The end goal is a concrete protocol that can be used for dependency injection, e.g. container.register(.singleton) { ProtocolImplementation() as Protocol } as per Dip - A simple Dependency Injection Container, BUT without copy-pasting when the protocol's interface clearly can be made generic, like in the above.
As swift provides a way to declare generic protocols (using associatedtype keyword) it's impossible to declare a generic protocol property without another generic constraint. So the easiest way would be to declare resource service class generic - class SomeResourceService<Repository: GenericRepository>.
But this solution has a big downside - you need to constraint generics everywhere this service would be involved.
You can drop generic constraint from the service declaration by declaring local as a concrete generic type. But how to transit from generic protocol to the concrete generic class?
There's a way. You can define a wrapper generic class which conforms to GenericRepository. It does not really implements its methods but rather passes to an object (which is real GenericRepository) it wraps.
class AnyGenericRepository<ResourceType: Identifiable>: GenericRepository {
// any usage of GenericRepository must be a generic argument
init<Base: GenericRepository>(_ base: Base) where Base.ResourceType == ResourceType {
// we cannot store Base as a class property without putting it in generics list
// but we can store closures instead
indexGetter = { base.index() }
// and same for other methods or properties
// if GenericRepository contained a generic method it would be impossible to make
}
private let indexGetter: () -> [ResourceType] {
indexGetter()
}
// ... other GenericRepository methods
}
So now we have a concrete type which wraps real GenericRepository. You can adopt it in SomeResourceService without any alarm.
class SomeResourceService {
let local: AnyGenericRepository<SomeResource>
}

How to define a Swift protocol that enforces its adopters themselves to conform to an associated type?

I need to work with objects that not only conform to a protocol, but also expose the type of a second protocol which they conform to. (This is for use with NSXPCConnection, where you must configurable not only an object to proxy, but also tell it which protocol should be exposed on that proxied object.)
I tried something like:
protocol Conformer where Self : Conformer.P {
associatedtype P : Protocol
static var interface : P {get}
init(info: String)
}
func exposeOverXPC<T:Conformer>(_ _: T.Type) {
let c : NSXPCConnection = …
c.exportedInterface = NSXPCInterface(with: T.interface)
c.exportedObject = T(info:"foo")
}
But it results in an error:
Associated type 'P' can only be used with a concrete type or generic parameter base
Specifically I want exposeOverXPC to only accept objects that:
Are initializeable in a particular way
Have a static property interface which references a protocol
Are themselves conformant to said interface
It's the last step that I'm getting stuck on, is there any way I can accomplish it?
You cannot restrict who conforms to a protocol, that goes against the concept of having protocols in the first place if you think about it. However you can use composed types, Swift4 feature, in your generic parameter in exposeOverXPC.
protocol Interface {
}
protocol XPCExposable {
associatedtype P: Interface
init(info: String)
static var interface: P { get }
}
func exposeOverXPC<T: XPCExposable & Interface>(_ : T.Type) {
// 1: T is initializeable in a particular way
// 2: Has a static property interface which references a protocol
// 3: Are themselves conformant to said interface
}
Yes this constraints T to conform to Interface and not P, your best bet is to make exposeOverXPC private/internal and provide APIs that expect Interface subtype. wherever you have access to the Interface subtype expose that api. e.g:
Solution 1
protocol InterfaceSubType: Interface {
fun test()
}
/// Create as many `API`s as the number of `Interface` subtypes you have.
func exposeOverXPC<T: XPCExposable & InterfaceSubType>(_ : T.Type) {
exposeOverXPC(T.self)
}
/// set to private, you only want to expose the APIs with `Interface` subtype.
private func exposeOverXPC<T: XPCExposable & Interface>(_ : T.Type) {
// Impl.
}
Solution 2
An alternative solution to have a function with parameters whose type is the associated type is to add that api (as static function if you wish) by extending the protocol. You must know all the expected subtypes of Interface in this extension.
extension XPCExposable {
static func exposeOverXPC<T>(_ interface: P, _ xpcType: T.Type) where T: XPCExposable {
// Expected subtype Interface
if let subInterface = interface as? InterfaceSubType {
subInterface.test()
}
// Other subtypes here.
}
}
Can be called as:
let impl = Impl(info: "")
Impl.exposeOverXPC(Impl.interface, Impl.self)
Its an extension on XPCExposable so you constrain the caller to be a conformer and the parameter requires XPCExposable.P so you're all set.
Downsides of this solution are:
You have two parameters instead of one.
It uses if conditions, I don't know if thats worth mentioning as downside other than that I'd like to push the first solution as favourite.

Swift: Is it possible to add a protocol extension to a protocol?

Lets say I have two protocols:
protocol TheirPcol {}
protocol MyPcol {
func extraFunc()
}
What I want to do is to create a protocol extension for 'TheirPcol' which lets extraFunc() work on anything which conforms to 'TheirPcol'. So something like this:
extension TheirPcol : MyPcol { // Error 'Extension of protocol 'TheirPcol' cannot have an inheritance clause.
func extraFunc() { /* do magic */}
}
struct TheirStruct:TheirPcol {}
let inst = TheirStruct()
inst.extraFunc()
The kicker in this is that 'TheirPcol', 'TheirStruct' are all handled by an external API which I do not control. So I'm passed the instance 'inst'.
Can this be done? Or am I going to have to do something like this:
struct TheirStruct:TheirPcol {}
let inst = TheirStruct() as! MyPcol
inst.extraFunc()
It seems there are two use-cases of why you may want to do what you are doing. In the first use-case, Swift will allow you to do what you want, but not very cleanly in the second use-case. I'm guessing you fall into the second category, but I'll go through both.
Extending the functionality of TheirPcol
One reason why you might want to do this is simply to give extra functionality to TheirPcol. Just like the compiler error says, you cannot extend Swift protocols to conform to other protocols. However, you can simply extend TheirPcol.
extension TheirPcol {
func extraFunc() { /* do magic */ }
}
Here, you are giving all objects that conform to TheirPcol the method extraFunc() and giving it a default implementation. This accomplishes the task of extending functionality for the objects conforming to TheirPcol, and if you want it to apply to your own objects as well then you could conform your objects to TheirPcol. In many situations, however, you want to keep MyPcol as your primary protocol and just treat TheirPcol as conforming to MyPcol. Unfortunately, Swift does not currently support protocol extensions declaring conformance to other protocols.
Using TheirPcol objects as if they were MyPcol
In the use case (most likely your use case) where you really do need the separate existence of MyPcol, then as far as I am aware there is no clean way to do what you want yet. Here's a few working but non-ideal solutions:
Wrapper around TheirPcol
One potentially messy approach would be to have a struct or class like the following:
struct TheirPcolWrapper<T: TheirPcol>: MyPcol {
var object: T
func extraFunc() { /* Do magic using object */ }
}
You could theoretically use this struct as an alternative to casting, as in your example, when you need to make an existing object instance conform to MyPcol. Or, if you have functions that accept MyPcol as a generic parameter, you could create equivalent functions that take in TheirPcol, then convert it to TheirPcolWrapper and send it off to the other function taking in MyPcol.
Another thing to note is if you are being passed an object of TheirPcol, then you won't be able to create a TheirPcolWrapper instance without first casting it down to an explicit type. This is due to some generics limitations of Swift. So, an object like this could be an alternative:
struct TheirPcolWrapper: MyPcol {
var object: MyPcol
func extraFunc() { /* Do magic using object */ }
}
This would mean you could create a TheirPcolWrapper instance without knowing the explicit type of the TheirPcol you are given.
For a large project, though, both of these could get messy really fast.
Extending individual objects using a child protocol
Yet another non-ideal solution is to extend each object that you know conforms to TheirPcol and that you know you wish to support. For example, suppose you know that ObjectA and ObjectB conform to TheirPcol. You could create a child protocol of MyPcol and then explicitly declare conformance for both objects, as below:
protocol BridgedToMyPcol: TheirPcol, MyPcol {}
extension BridgedToMyPcol {
func extraFunc() {
// Do magic here, given that the object is guaranteed to conform to TheirPcol
}
}
extension ObjectA: BridgedToMyPcol {}
extension ObjectB: BridgedToMyPcol {}
Unfortunately, this approach breaks down if there are a large number of objects that you wish to support, or if you cannot know ahead of time what the objects will be. It also becomes a problem when you don't know the explicit type of a TheirPcol you are given, although you can use type(of:) to get a metatype.
A note about Swift 4
You should check out Conditional conformances, a proposal accepted for inclusion in Swift 4. Specifically, this proposal outlines the ability to have the following extension:
extension Array: Equatable where Element: Equatable {
static func ==(lhs: Array<Element>, rhs: Array<Element>) -> Bool { ... }
}
While this is not quite what you are asking, at the bottom you'll find "Alternatives considered", which has a sub-section called "Extending protocols to conform to protocols", which is much more what you're trying to do. It provides the following example:
extension Collection: Equatable where Iterator.Element: Equatable {
static func ==(lhs: Self, rhs: Self) -> Bool {
// ...
}
}
Then states the following:
This protocol extension would make any Collection of Equatable elements Equatable, which is a powerful feature that could be put to good use. Introducing conditional conformances for protocol extensions would exacerbate the problem of overlapping conformances, because it would be unreasonable to say that the existence of the above protocol extension means that no type that conforms to Collection could declare its own conformance to Equatable, conditional or otherwise.
While I realize you're not asking for the ability to have conditional conformances, this is the closest thing I could find regarding discussion of protocols being extended to conform to other protocols.